:root{
  --bg:#0e1116; --bg2:#151a22; --bg3:#1c232d; --line:#2a3441;
  --txt:#e6edf3; --txt2:#a7b4c2; --txt3:#7e8c9c; --txt4:#5d6b7a;
  --acc:#ff9f1c; --acc2:#4cc9f0;
  --s-micro:#b57edc; --s-ligne:#4cc9f0; --s-instrument:#ff9f1c;
  --s-casque:#8d99ae; --s-hp:#ef476f; --s-numerique:#06d6a0; --s-midi:#c77dff;
  --ok:#06d6a0; --warn:#ffd166; --err:#ef476f;
  --mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;

  /* LES RAYONS — CINQ VALEURS, UN RÔLE CHACUNE.

     Il y en avait DOUZE : 2, 3, 4, 5, 6, 7, 8, 10, 12, 20 px, 50 % et un
     `0 5px 5px 0`. Douze valeurs pour cinq intentions, c'est-à-dire que la
     différence entre 4 et 5 px ne voulait rien dire — personne ne l'avait
     choisie, elle avait été tapée. Un rayon n'a d'utilité que s'il CLASSE : à
     l'œil, il dit de quelle espèce est la boîte qu'on regarde, et il ne le dit
     que si les espèces se comptent sur une main.

     ⚠ CES VALEURS NE SUIVENT PAS `--fs`, et c'est la règle déjà posée plus bas
     pour les bordures : agrandir un rayon avec le texte n'améliore rien, ça
     épaissit le dessin. Elles sont donc en pixels nus, délibérément.

     La GÉLULE n'est pas une sixième valeur : un rayon est écrêté à la moitié de
     la hauteur, donc `--r-gelule` sur un élément de 20 px de haut donne un
     demi-cercle parfait sans qu'on ait à écrire 20 px. C'est ce qui a permis de
     faire disparaître le 20 px de `.chip` SANS rien changer à son rendu. */
  --r-pastille:3px;   /* le plus petit jeton plein : numéro de colonne, bouton
                         de retrait, bandeau de groupe, jauge, repère d'aide */
  --r-controle:6px;   /* ce qu'on clique ou saisit, et les blocs courts :
                         boutons, champs, fiche d'appareil, point de baie */
  --r-carte:8px;      /* un bloc de contenu autonome et posé : baie, carte,
                         popover, menu */
  --r-gelule:12px;    /* les grandes surfaces flottantes (modale, tiroir) et
                         les formes en gélule, par écrêtage (voir plus haut) */
  --r-cercle:50%;     /* ce qui est rond par nature : voyant, jack, prise */

  /* L'ÉCHELLE TYPOGRAPHIQUE — SIX ÉCHELONS, ET C'EST TOUT.

     Il y en avait QUINZE : 8 · 8,5 · 9 · 9,5 · 10 · 10,5 · 11 · 11,5 · 12 ·
     12,5 · 13 · 14 · 15 · 16 · 20. À l'échelle 1,3 — celle de Julien — ça
     donnait dix-huit tailles à l'écran, dont seize paires séparées de MOINS D'UN
     PIXEL. Une différence qu'on ne voit pas n'est pas une hiérarchie : c'est du
     bruit qui coûte quand même une décision à chaque nouvelle règle. Le demi-
     pixel n'avait jamais été choisi, il avait été hérité de proche en proche.

     Ce n'est pas non plus une gamme géométrique de manuel. Les pas sont serrés
     en bas (1 px) et larges en haut (2 px), pour une raison qui tient à l'œil :
     un pixel entre 9 et 10 px est un écart de 11 %, il se voit ; entre 14 et
     15 px il ne fait que 7 % et ne dit plus rien. La gamme suit donc l'écart
     RELATIF, pas l'écart absolu.

     Chaque échelon a un rôle, et le rôle est la question à se poser avant
     d'écrire une taille : « qu'est-ce que ce texte EST ? », jamais « combien de
     pixels ? ». S'il ne rentre dans aucun des six, c'est probablement qu'il
     n'avait pas besoin d'une taille à lui.

     Comme le reste du fichier, ces cotes passent par `--fs` : l'échelle de
     l'interface les multiplie toutes ensemble, et les six rapports sont donc
     tenus aux quatre crans (voir le bloc `--fs` juste en dessous). */
  --t-xs:calc(9px * var(--fs));    /* MICRO — l'étiquette mono en capitales et
      les jetons minuscules : sigle d'un point de baie, numéro de colonne,
      catégorie d'un appareil, compteur d'une pastille. Presque toujours du
      `--mono` ; se lit d'un coup d'œil, ne se lit pas en phrase. */
  --t-s:calc(10px * var(--fs));    /* PETIT — l'information secondaire qu'on lit
      vraiment : nom de port dans la baie, méta d'un en-tête, intitulé de champ,
      contenu d'un tableau d'aperçu. Le plancher du confortable. */
  --t-m:calc(11px * var(--fs));    /* COURANT SECONDAIRE — les libellés de
      l'interface qui ne sont pas des actions : bascule de vue, case à cocher,
      corps d'infobulle, titre de carte. */
  --t-l:calc(12px * var(--fs));    /* COURANT — les ACTIONS et la saisie :
      bouton, champ, onglet, ligne de menu, en-tête d'appareil. C'est l'échelon
      le plus chargé, et c'est normal : c'est celui où l'on clique. */
  --t-xl:calc(14px * var(--fs));   /* ACCENT — les titres de bloc et les phrases
      qu'on veut faire lire : en-tête de modale ou de tiroir, état vide, texte
      de l'écran d'entrée. C'est AUSSI la taille de `<body>`, donc la base dont
      hérite tout ce qui n'a pas de cote propre — la garder à 14 px était la
      condition pour ne rien déplacer par héritage. */
  --t-2xl:calc(16px * var(--fs)); /* TITRE — le nom de l'appli dans la barre, et
      rien d'autre. Un seul emploi, mais un échelon quand même : sans lui, le
      titre retomberait au niveau de son propre sous-titre. */

  /* L'ÉCHELLE DE L'INTERFACE — un multiplicateur, pas une taille.

     Les déclarations de police de ce fichier gardent leur cote en pixels,
     multipliée par `--fs`. Elles ne l'écrivent plus chacune : depuis l'échelle
     typographique ci-dessus, le `calc()` est posé UNE FOIS par échelon et les
     109 déclarations passent par `var(--t-…)`. Le mécanisme est le même, il
     n'est plus recopié. On a préféré ça à une conversion en `rem`, qui aurait
     demandé les mêmes réécritures pour un résultat identique, et à un `zoom`
     sur `body`, qui aurait été une seule
     ligne mais aurait faussé toute l'arithmétique de placement des popovers —
     `app.js` mélange des `getBoundingClientRect()` et des `window.innerWidth`,
     que `zoom` ne met pas dans le même repère.

     Ce qui NE change PAS avec l'échelle, et c'est voulu : les bordures, les
     rayons, les ombres et les pastilles d'icône. Une bordure de 1 px reste une
     bordure de 1 px — l'agrandir ne la rend pas plus lisible, elle épaissit le
     dessin. Seuls le texte et les boîtes qui le CONTIENNENT suivent.

     Deux exceptions à connaître : la géométrie du schéma est calculée en
     JavaScript (`disposer()` dans app.js) et lit cette variable pour se mettre
     d'accord avec elle ; et `impression.css` n'en dépend pas du tout — le papier
     a ses propres cotes en millimètres, et le réglage d'écran de l'un ne doit
     pas déplacer les étiquettes de l'autre.

     Valeur écrite par `appliquerEchelle()` depuis `setup.ui.echelle`, dont les
     crans sont la liste fermée `ECHELLES` de `coeur/modele.js`. */
  --fs:1;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0; background:var(--bg); color:var(--txt);
  font:var(--t-xl)/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  display:flex; flex-direction:column; overflow:hidden;
}

/* LES CONTRÔLES HÉRITENT DE LA POLICE, ET C'EST UNE SEULE LIGNE POUR 23 DÉFAUTS.

   Un `<button>` — comme `<input>`, `<select>`, `<textarea>` — n'hérite PAS de
   `font-family` : la feuille de style du navigateur lui en pose une, et cette
   propriété-là ne remonte pas au parent comme les autres. Conséquence mesurée
   avant correction : `Auto-placer`, `Relier…`, les onglets
   `Baie`/`Mes appareils`, le compteur d'avertissements, `?`, `⋯`, `⚙` et les
   boutons de zoom rendaient en **Arial** — 23 éléments textuels dans la vue
   baies, et 106 contrôles sur 117 dans tout le dépôt. Autrement dit : LES
   ACTIONS PRINCIPALES DE L'APPLI étaient dans une autre typographie que le
   reste de l'appli, sur toute son histoire, sans que ça se voie autrement qu'à
   la mesure — c'est le défaut d'un fond de page, on le lit sans le remarquer.

   POURQUOI ICI ET PAS SUR CHAQUE SÉLECTEUR. `.tab`, `.vue` et quatre autres
   posaient déjà `font-family:inherit` un par un — la preuve que le défaut avait
   été rencontré six fois et rustiné six fois, jamais nommé. Une règle par
   sélecteur laisse le SUIVANT retomber en Arial ; la règle générique, elle,
   couvre aussi les boutons qui n'existent pas encore. Les six déclarations
   individuelles sont retirées avec : elles ne font plus rien, et laisser une
   rustine à côté du correctif fait croire qu'elle sert.

   ⚠ On hérite la FAMILLE, pas la TAILLE. `font-size:inherit` aurait remonté à
   14 px les quelques contrôles sans taille propre — ils sont à 13,33 px, le
   défaut de Chrome —, donc élargi des boutons dans une barre dont la hauteur
   est vérifiée. La taille relève de l'échelle typographique, plus bas, où elle
   est traitée pour elle-même et avec ses cotes. */
button,input,select,textarea,optgroup{font-family:inherit}

/* ---------- topbar ---------- */
/* B7 — LA BARRE PASSE DEVANT LE TIROIR, ET LE TIROIR COMMENCE SOUS ELLE.
   `.topbar` n'avait aucun `position`, donc aucun `z-index` opposable : le
   tiroir (`z-index:40`, `position:fixed; top:0`) recouvrait le bouton
   `Contrôle` qui venait de l'ouvrir — cliquer deux fois au même endroit
   n'ouvrait puis ne refermait rien — et masquait l'indicateur
   d'enregistrement, le seul retour d'écriture de l'appli. C'est le défaut du
   §14.3 pt 3 (la fiche masque ce qu'elle décrit) transposé à la vue baies.
   Les deux moitiés sont indissociables : élever la barre seule aurait glissé
   l'en-tête du tiroir — et son bouton « Fermer » — dessous. */
.topbar{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:10px 16px; background:var(--bg2); border-bottom:1px solid var(--line); flex:none;
  /* z-index 38 — AU-DESSUS DU CONTENU QUI DÉFILE, SOUS TOUT CE QUI SE POSE.
     Elle était à 42, et c'était une SUR-CORRECTION de moi : le §14.7 B7 avait
     montré que le tiroir (40, `top:0`) recouvrait le bout droit de la barre,
     donc le bouton même qui l'ouvre. J'ai corrigé DEUX fois — en élevant la
     barre ET en faisant démarrer le tiroir sous elle (`top:var(--h-barre)`).
     Le second correctif suffisait ; le premier a fait passer la barre au-dessus
     du popover de détail (41), qui disparaissait donc derrière elle. Signalé
     par Julien sur capture.

     L'ordre juste, et le seul à respecter ici : le contenu qui défile, puis la
     barre, puis tout ce qui SE POSE PAR-DESSUS — tiroir 40, popovers 41-46,
     surcouche 47, modale 50, porte et infobulle 60. Une barre est du décor
     permanent : elle doit battre le contenu, jamais un panneau appelé. */
  position:relative; z-index:38;
}
.brand{display:flex; align-items:center; gap:10px}
.brand h1{font-size:var(--t-2xl); margin:0; font-weight:600; letter-spacing:.2px}
.brand h1 em{font-style:normal; color:var(--txt2); font-weight:400}

/* ---------- le fil d'Ariane de la barre du haut (#9, second morceau) ----------
   La marque, PUIS la configuration ouverte : `#studio` a quitté la ligne
   d'état (à droite, entre les actions et `#moi`) pour rejoindre ici la seule
   chose dont elle est la SUITE logique — « Patchbay », puis quelle
   configuration de Patchbay. `min-width:0` sur le groupe entier est ce qui
   permet à `.brand h1` ET à `.studio` de s'ellipser chacun de son côté plutôt
   que de forcer la barre à déborder — la même garantie que `.topbar .brand`
   portait seule avant que la marque n'ait un voisin. */
.topbar-gauche{display:flex; align-items:center; gap:8px; min-width:0}
/* Le séparateur du fil — un simple caractère, jamais un `.sep` de barre
   d'outils (`width:1px`) : ceux-là séparent des GESTES entre eux, celui-ci
   sépare deux ÉCHELONS d'un même repère. Sa visibilité suit celle de
   `#studio` (`majSelecteurStudio()`), jamais l'inverse. */
.crumb-sep{color:var(--txt4); font-size:var(--t-l)}

/* LA MARQUE DE LA BARRE NE S'ENROULE JAMAIS — et c'est une garantie, pas un
   réglage. C'est l'enroulement qui a fait le défaut de la bande 821–1250 px :
   `.topbar` est en `flex-wrap:nowrap`, ses items se RÉTRÉCISSENT en silence
   quand la place manque, et le premier à céder est le seul qui puisse céder
   sans déborder — le titre, qui passait à deux puis trois lignes. La barre
   montait alors à 111 px et le reste, incompressible, sortait de l'écran :
   173 px de glissement latéral à 900 px de large.

   Trois lignes de marque sont donc le SYMPTÔME VISIBLE d'une barre qui ne tient
   plus. En l'interdisant ici, on force le défaut à se manifester autrement — par
   un `scrollWidth` supérieur à la largeur de la boîte, c'est-à-dire par une
   grandeur que `majBarreEtroite()` peut lire et sur laquelle il peut décider.
   L'ellipse est le filet de dernier recours ; le plan, lui, est de compacter la
   barre AVANT d'en arriver là.

   Borné à `.topbar` : la porte d'entrée porte la même `.brand` sur un écran
   qu'elle occupe seule, et rien ne l'y presse. Le `min-width:0` est ce qui rend
   l'ellipse possible du tout — un item de flex ne descend pas sous sa largeur
   minimale de contenu sans lui. */
.topbar .brand{min-width:0}
.topbar .brand h1{min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.dot{width:9px;height:9px;border-radius:var(--r-cercle);background:var(--acc);box-shadow:0 0 10px var(--acc)}
/* LA MENTION DE LA MARQUE — « Bêta ». Elle disait « prototype » en couleur
   d'accent ET sous contour : l'élément le plus voyant de la barre, pour une
   chose qu'on lit une fois et qui n'est pas une action. Une mention n'a ni
   contour, ni fond, ni couleur d'accent ; elle prend l'échelon de l'information
   secondaire et la couleur du texte ténu.

   ⚠ ELLE A SA PROPRE CLASSE, ET C'EST TOUT LE POINT. Quand la mention l'a prise,
   `.tag` gardait un second usage — le marqueur `▸` d'un point PROPOSÉ — et la
   laisser intacte était le bon geste : l'alléger dans ce lot-là aurait changé
   l'aperçu de la baie, que personne n'avait demandé de changer.

   ⚠ LA RÈGLE `.tag` N'EXISTE PLUS (#40). Une fois seule avec le `▸`, elle ne
   lui donnait plus rien qu'il ait voulu : `.point.propose .tag` ne réécrivait
   que la position, la police et la couleur, et le marqueur héritait donc d'un
   contour ORANGE, de marges 3/6 px, d'un rayon et d'une `opacity:.8`. Mesuré
   sur la cascade réelle puis REGARDÉ à l'écran : un chevron cyan enfermé dans
   un cadre orange de 19×19 px pour un glyphe de 9 — plus voyant que le point
   qu'il annote, et à contre-emploi d'une prévisualisation dont tout le langage
   est en `--acc2` pointillé. Personne ne l'avait décidé : c'était l'héritage
   d'une classe partagée avec un badge. Ce que le `▸` utilise vraiment vit
   désormais dans `.point.propose .tag`, et rien d'autre. */
.mention{font:var(--t-s) var(--mono); color:var(--txt3); letter-spacing:.3px}
/* `.topbar-droite` — ex-`.topbar-actions` (#9, second morceau). Le nom mentait
   depuis que l'aide et les préférences l'ont rejoint : ce ne sont pas des
   actions sur le document, et les appeler ainsi aurait fait dire à la feuille
   le contraire de ce que la barre affirme désormais. `min-width:0` pour la
   même raison que `.topbar-gauche` : `#moi` doit pouvoir s'ellipser. */
.topbar-droite{display:flex; align-items:center; gap:10px; min-width:0}

/* ---------- la barre d'outils (#9, second morceau) ----------

   LA BANDE DU MILIEU : LES GESTES SUR LE DOCUMENT, ET RIEN D'AUTRE. Elle
   accueille `#btnAnnuler`, `#btnRefaire` et `#btnPlus` — ex-`.topbar-actions`
   — et, sur le Schéma seulement, `.schema-bar` — ex-`#vueSchema` — voir les
   deux commentaires laissés à leurs anciennes places dans `studio.html`.
   ⚠ `#btnAuto` A QUITTÉ CETTE BANDE AVEC #80 : il vit désormais dans l'en-tête
   de la carte « À placer » du tiroir de Contrôle, pas dans la barre.

   MÊME PEINTURE, MÊME HAUTEUR DE DÉCOR QUE `.topbar` : ce sont les deux
   moitiés d'un même bandeau fixe, et une différence de fond ou de bordure
   aurait dit qu'elles sont deux choses plutôt qu'une continuation. */
.toolbar{
  display:flex; align-items:center; gap:16px; flex-wrap:wrap; row-gap:6px;
  padding:8px 16px; background:var(--bg2); border-bottom:1px solid var(--line);
  flex:none; position:relative; z-index:38;
}
/* ⚠ `margin-left:auto` SUR LA MOITIÉ DROITE, ET NON `justify-content` SUR LE
   CONTENEUR. La moitié gauche est `hidden` en entier sur les Baies (voir
   `poserVue()`, app.js) : avec `justify-content:space-between`, un SEUL enfant
   visible se serait retrouvé collé au bord GAUCHE — l'exact inverse du point
   fixe que ce lot doit garantir. `margin-left:auto` pousse toujours la moitié
   droite au bout, que sa voisine soit vide, pleine, ou absente du rendu. */
.toolbar-gauche{display:flex; align-items:center; gap:12px; flex-wrap:wrap; min-width:0}
.toolbar-droite{display:flex; align-items:center; gap:10px; margin-left:auto; flex:none}
/* #139 : l'ajout reste lisible et touchable, y compris quand la barre se replie. */
.toolbar #btnNewItem{min-height:44px; white-space:nowrap; flex:none}
.field{display:flex;align-items:center;gap:6px;font-size:var(--t-l);color:var(--txt2)}
/* `type=email` et `type=number` sont dans la liste depuis la porte d'entrée :
   un champ non listé retombe sur le style natif du navigateur, c'est-à-dire
   un rectangle blanc au milieu d'une interface sombre. */
/* `type=search` est entré avec la recherche de la réserve, et pour exactement la
   raison que dit le commentaire ci-dessus : sans lui, le champ tombait sur le
   style natif — rectangle blanc, coins carrés — au milieu du bandeau sombre. */
select,input[type=text],input[type=email],input[type=number],input[type=search]{
  background:var(--bg3); color:var(--txt); border:1px solid var(--line);
  border-radius:var(--r-controle); padding:5px 8px; font-size:var(--t-l);
}
.btn{
  background:var(--bg3); color:var(--txt); border:1px solid var(--line);
  border-radius:var(--r-controle); padding:6px 11px; font-size:var(--t-l); cursor:pointer;
}
.btn:hover{border-color:var(--acc); color:var(--acc)}
.btn.ghost{color:var(--txt3)}
/* LES BOUTONS PUREMENT ICONIQUES — `?` `↶` `↷` `⋯` `⚙` de la barre, et le `⋯` du
   panneau de contrôle. Leurs glyphes étaient rendus à `--t-l`, c'est-à-dire
   dimensionnés comme du TEXTE dans des boîtes de 32 px : mesuré à l'échelle 1,
   l'engrenage occupait 7,2 px d'encre. La cote d'un pictogramme ne se prend pas
   dans la gamme typographique — la question « qu'est-ce que ce texte EST ? »
   n'a pas de réponse pour un engrenage. Elle vaut 16 px, et suit `--fs` comme
   tout le reste du fichier.

   ⚠ POURQUOI LA RÈGLE PORTE SUR `.btn.discret` ET NON SUR UNE CLASSE À CÔTÉ.
   Mesuré avant de choisir : la classe a SIX usages dans tout `public/`, et les
   six sont des boutons SANS LIBELLÉ — quatre glyphes, deux SVG. Il n'existe
   aucun `.btn.discret` portant du texte que gonfler abîmerait. Une seconde
   classe ne distinguerait donc rien : elle laisserait deux noms pour une seule
   chose, et le premier `.btn.discret` écrit sans elle retomberait à 12 px.

   `svg{width:1em}` : `ico()` écrit `width="13" height="13"` en dur, une cote qui
   ne suit ni ses voisines ni l'échelle. Sans cette ligne, `↶` et `↷`
   devenaient les plus PETITES icônes de la rangée le jour où les glyphes ont
   grandi : le défaut corrigé à un bouton, réintroduit trois boutons plus loin. */
.btn.discret{color:var(--txt2); padding:6px 10px; min-width:32px; font-size:calc(16px * var(--fs))}
.btn.discret svg{width:1em; height:1em}
.btn.discret:hover{color:var(--acc)}
/* `min-width:88px` A ÉTÉ RETIRÉ AVEC #9 — LE 4ᵉ DÉFAUT DE L'AUDIT DE #9,
   LAISSÉ EXPRÈS JUSQU'À CE LOT. Il immobilisait une largeur pensée pour
   « enregistrement… » même au repos, où le texte n'est que « à jour » : posé à
   côté d'un Contrôle réduit à un point vert et un mot dans la bande du bas, le
   bloc vide qui restait se voyait bien plus qu'il ne se voyait dans la barre
   du haut, dense, d'où le défaut venait. Sans largeur figée, le texte occupe
   ce qu'il vaut et rien de plus ; ce qui le suit dans `.bande-bas` est un
   `.spacer` élastique, jamais un voisin que sa largeur pourrait bousculer. */
.save-state{font:var(--t-m) var(--mono); color:var(--txt2)}
.save-state.repos{color:var(--txt3)}
.save-state.on{color:var(--ok)}
.save-state.ext{color:var(--acc2)}
/* ⚠ L'AVERTISSEMENT QUI DURE (#78), ET LA SEULE CLASSE DE CETTE LISTE QUI NE
   S'EFFACE PAS TOUTE SEULE. Les quatre au-dessus sont des états passagers de
   2,5 s : une nuance de couleur y suffit, on les regarde ou on les rate sans
   conséquence. Celle-ci reste à l'écran tant que le travail n'est pas
   enregistré, et la rater coûte le travail — d'où la pastille pleine plutôt
   qu'une couleur de plus. `--err` porte déjà le refus partout ailleurs dans la
   feuille ; le fond, le gras et l'espacement sont ce qui la fait sortir d'une
   bande de 22 px où tout le reste est gris.
   ⚠ ET AUCUN CLIGNOTEMENT : ce voyant peut rester des minutes sous les yeux
   pendant qu'on continue de saisir. Une animation permanente se fait masquer
   mentalement en trente secondes, et elle est hostile à qui la supporte mal. */
.save-state.echec{
  color:#fff; background:var(--err); font-weight:700;
  padding:1px 7px; border-radius:999px; letter-spacing:.2px;
}
/* Qui est connecté. Discret par nature — on ne le lit qu'une fois par séance —
   mais TOUJOURS présent en ligne : c'est le seul endroit qui distingue un studio
   neuf d'une session expirée, les deux étant vides à l'écran. */
.moi{font:var(--t-m) var(--mono); color:var(--txt3); background:none; border:0; padding:2px 6px;
     max-width:190px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer}
.moi:hover{color:var(--txt2)}
.moi.hs{color:var(--acc); cursor:default}
/* ---------- le rond d'initiales ----------

   L'ADRESSE E-MAIL A QUITTÉ LE BANDEAU (voir `initialesDe` dans `app.js`) : ce
   qui reste est un jeton de 26 px, c'est-à-dire la taille d'un repère qu'on
   cherche du coin de l'œil et jamais d'une chose qu'on lit. Il RÉTRÉCIT la
   barre — `.moi` pouvait aller jusqu'à 190 px — donc `majBarreEtroite()` la
   repliera plus tard qu'avant, jamais plus tôt : le changement ne peut pas
   faire déborder une fenêtre qui tenait.

   `.hs` (« session inconnue ») garde sa forme de texte, et c'est voulu : ce
   n'est pas une identité, c'est un avertissement, et un avertissement rond de
   26 px ne se lirait pas. D'où `max-width` remis à sa valeur de texte là. */
.moi.rond{
  display:inline-grid; place-items:center; width:26px; height:26px; padding:0;
  max-width:none; border-radius:50%; background:var(--bg3);
  border:1px solid var(--line); color:var(--txt2); letter-spacing:.5px;
}
.moi.rond:hover{color:var(--txt); border-color:var(--acc)}

/* ---------- le studio ouvert ----------

   LE PENDANT DE `.moi`, ET IL LUI RESSEMBLE EXPRÈS : l'un dit QUI, l'autre dit
   OÙ, tous deux se lisent d'un coup d'œil et ne se cliquent qu'occasionnellement.
   Deux formes différentes pour deux repères de même nature auraient fabriqué une
   hiérarchie qui n'existe pas. Il est un peu plus lisible que `.moi` (`--txt2`
   contre `--txt3`) pour une seule raison : on change de studio plus souvent
   qu'on ne change de compte, et le nom du studio est le seul rempart contre le
   fait de câbler le mauvais.

   ⚠ LE MOT N'EST PLUS ÉCRIT ICI, ET C'EST LE CORRECTIF DE L'ISSUE #34. Il l'a
   longtemps été, comme étiquette fixe : `content:"studio "`. Une feuille de
   style n'atteint aucune constante JavaScript, donc ce mot-là échappait à
   `MOT_ESPACE` — et le jour du renommage, toute l'interface aurait dit
   « config » pendant que la barre aurait continué d'annoncer « studio ».

   Il vient donc de `data-mot`, posé par `majSelecteurStudio()` depuis la table.
   Le `::before` est CONSERVÉ plutôt que replié dans le `textContent` : c'est ce
   qui garde le préfixe en `--txt4` et le nom en `--txt2` — et surtout, le JS
   n'écrit toujours dans ce bouton que par `textContent` et `dataset`, jamais par
   un gabarit. Un nom d'entité n'est pas échappé par le serveur (§2
   d'`api/espaces.js`), et `attr()` ne l'interprète pas davantage.

   `⇅` ET NON `▾` (#9, second morceau) — décision de Julien, tranchée sur la
   maquette du 4 septembre 2026 : `▾` dit « déplie un menu », `⇅` dit « bascule
   entre plusieurs ». C'est la pluralité qui manquait — sans elle, rien dans
   l'interface ne disait qu'un compte pouvait porter plus d'une configuration,
   et c'est le défaut qui a coûté trois séances à un testeur (voir #9). */
.studio{
  font:var(--t-m) var(--mono); color:var(--txt2); background:none;
  border:1px solid transparent; border-radius:var(--r-controle);
  padding:2px 7px; max-width:190px; min-width:0; cursor:pointer;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.studio::before{content:attr(data-mot) " "; color:var(--txt4)}
.studio::after{content:" ⇅"; color:var(--txt4)}
.studio:hover{color:var(--acc); border-color:var(--line)}

/* La ligne d'un studio dans le sélecteur, et la marque du studio OUVERT.

   ⚠ LA GOUTTIÈRE RÉSERVÉE A ÉTÉ RETIRÉE AVEC LA MARQUE QUI PASSE À DROITE, et
   c'est le point de ce bloc. Le rond était à GAUCHE et occupait sa colonne même
   vide (`width:1ch`), sans quoi les noms des autres lignes ne s'alignaient pas
   sur celui du studio ouvert. C'était payer un décalage sur TOUTE la liste pour
   marquer UNE ligne. À droite il n'y a rien à réserver : les noms démarrent tous
   au même bord, et `margin-left:auto` pousse la marque au bout.

   La forme vient des sélecteurs de Supabase et de Vercel, qui cochent à droite.
   Laisser la règle de largeur en place aurait fait mentir la feuille — elle
   aurait décrit une gouttière que plus personne n'occupe.

   `align-items:baseline` reste : la marque et le nom n'ont pas la même taille de
   police, et c'est la ligne de base qui les fait paraître sur le même rang. */
.studio-item{display:flex; align-items:baseline; gap:7px}
.studio-marque{flex:none; margin-left:auto; color:var(--acc); font-size:var(--t-s)}
.studio-nom{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.menu-item.on{color:var(--txt)}

/* ⚠ LA LISTE EST ENVELOPPÉE, DONC ELLE PERD LE `gap` DU MENU. `.menu-pop` est
   une colonne de flex à `gap:2px` : ses items sont ses ENFANTS DIRECTS, et
   depuis que les lignes de studio vivent dans un `<div>` — pour que le filtre
   les masque sans redessiner —, l'espacement ne s'applique plus entre elles.
   Sans cette règle, les lignes se touchent et leurs fonds de survol se
   rejoignent, alors que les quatre commandes du bas gardent leurs 2 px. Relevé
   par la revue de code : un changement visuel que personne n'avait demandé.
   `display:flex` en colonne, et non `block` : c'est ce qui blockifie les
   boutons, qui seraient sinon des `inline-block` posés côte à côte. */
#studioListe{display:flex; flex-direction:column; gap:2px}

/* ==========================================================================
   LA BANDE D'ÉTAT DU BAS (#9) — premier morceau de la refonte de la navigation

   Elle réunit ce qui dit COMMENT ÇA VA — Contrôle, l'enregistrement, le
   marqueur de nouveautés, le zoom — et qui vivait mêlé aux ACTIONS de la barre
   du haut ou de la sous-barre du Schéma, sans que rien les distingue des
   commandes qui les entouraient. Le raisonnement complet est en tête de
   `<footer class="bande-bas">` dans `studio.html`.

   `flex-wrap:wrap`, ET NON `nowrap` COMME `.topbar`. La barre du haut mesure sa
   largeur et se replie par PALIERS (`.etroite`, posée par `majBarreEtroite()`)
   parce qu'un mot manquant sur une ACTION se lit comme un bouton cassé. Ici,
   rien n'est une action à faire aboutir coûte que coûte : un élément qui passe
   à la ligne au doigt reste lisible tel quel, et ça évite de porter la même
   mesure dynamique à un second endroit de la page pour un gain qu'un simple
   repli couvre déjà. C'est le choix assumé pour le mobile, qui n'a pas
   vocation à être parfait ici (#62 — le conflit avec la zone du pouce reste
   à trancher ailleurs, et n'est pas rouvert par ce lot). */
.bande-bas{
  display:flex; align-items:center; flex-wrap:wrap;
  gap:10px; row-gap:6px; padding:8px 16px;
  background:var(--bg2); border-top:1px solid var(--line); flex:none;
}
.bande-bas .sep{width:1px; height:16px; background:var(--line); flex:none}
/* Pousse « Une remarque ? » (et le zoom, s'il est là) à l'extrême droite —
   `.moi` / `.studio` le sont déjà en haut, même geste, même raison. */
.bande-bas .spacer{flex:1 1 auto; min-width:0}

/* « Une remarque ? » (#37) — UN LIEN, PAS UN BOUTON D'ACTION. Texte nu, couleur
   ténue : proéminent, il annoncerait une fonction centrale de l'appli ; ici il
   doit être VU sans être CRIANT — décision de Julien, et une proéminence pensée
   comme temporaire, liée à la bêta. `font-size` seul, JAMAIS le raccourci
   `font` : ce dernier réinitialiserait `font-family`, que ce bouton hérite déjà
   de la règle générique `button{font-family:inherit}` en tête de feuille. */
/* #38 — LE SIGNAL DES FICHES CHANGÉES. Même famille que `.lien-remarque` juste
   dessous — texte nu, pas de fond, pas de bordure : il ouvre le Contrôle, il ne
   décide de rien. Il porte l'accent secondaire là où « Une remarque ? » reste en
   `--txt3` : celui-ci annonce quelque chose à faire, l'autre est une invitation
   permanente. Il reste très en dessous de la pastille rouge d'échec, qui est la
   seule chose criante de cette bande.

   ⚠ `font-size` SEUL, JAMAIS le raccourci `font` — même piège que pour
   `.lien-remarque` : `font` réinitialiserait `font-family`, que ce bouton hérite
   de la règle générique `button{font-family:inherit}` en tête de feuille. */
.lien-fiches{
  background:none; border:0; color:var(--acc2); font-size:var(--t-m);
  cursor:pointer; padding:0; white-space:nowrap;
}
.lien-fiches b{font-weight:600}
/* Les deux formes, sur le patron EXACT du marqueur du journal (`.marq-long` /
   `.marq-court`) : la phrase au bureau, le chiffre seul au doigt. Le seuil est
   le même que le sien, pour que la bande bascule d'un coup et non par morceaux. */
.fic-court{display:none}
@media (max-width:820px){
  .lien-fiches .fic-long{display:none}
  .lien-fiches .fic-court{display:inline}
}
.lien-remarque{
  background:none; border:0; color:var(--txt3); font-size:var(--t-m);
  cursor:pointer; padding:0; white-space:nowrap;
}
.lien-remarque:hover{color:var(--txt2); text-decoration:underline}

/* Le zoom et son séparateur, groupés dans UN SEUL élément que `poserVue()`
   bascule d'un coup (`#bandeBasZoom`) : jamais de trait vertical orphelin sur
   la vue Baies, jamais deux bascules à tenir synchronisées à la main. */
.zoom-groupe{display:flex; align-items:center; gap:10px}

/* Le champ de recherche du sélecteur. `.rech` porte déjà tout ce qu'il faut ;
   ce qui se règle ici est sa place DANS un popover — la marge du bas de `.rech`
   (10 px) est trop lâche entre deux lignes de menu espacées de 2 px.
   ⚠ LE SÉLECTEUR EST `.menu-pop .studio-rech` ET NON `.studio-rech` SEUL :
   `.rech` est déclarée PLUS BAS dans cette feuille, à spécificité égale — donc
   c'est elle qui gagnait, et cette règle-ci n'obtenait que sa marge du haut. Sa
   propre explication décrivait un effet qu'elle n'avait pas. */
.menu-pop .studio-rech{margin:2px 0 4px}

/* Une entrée de menu qui ne peut pas aboutir MAINTENANT : estompée, jamais
   `disabled`. La règle vient de `.sp-port.sourd`, plus bas — « un bouton
   désactivé ne peut pas dire pourquoi » —, et elle mord ici pour deux raisons
   mesurables : un contrôle `disabled` ne délivre ni `mouseover` ni `focusin`,
   donc `montrerTip` ne le voit jamais ; et ce menu n'a aucune règle `:disabled`,
   donc l'entrée éteinte aurait la couleur, le curseur et le survol d'une entrée
   vivante. Estompée, elle se voit ; survolable, elle s'explique. */
.menu-item.sourd{opacity:.45}
.menu-item.sourd:hover{background:none; color:var(--txt2)}

/* Le glyphe des deux entrées de création. Largeur FIXE : sans elle, « + » et
   « ⧉ » n'ont pas la même chasse et les deux libellés ne s'alignent pas — c'est
   le seul endroit du menu où deux entrées portent un signe. */
.menu-glyphe{display:inline-block; width:1.4em; color:var(--txt4)}
.menu-item:hover .menu-glyphe{color:var(--acc)}

/* « 3 studios sur 20 » — une MENTION, jamais un contrôle : ni fond au survol, ni
   curseur, ni cible de clic. Elle est là pour qu'on sache où l'on en est du
   plafond avant de buter dessus, pas pour qu'on la lise. */
.menu-mention{
  margin:2px 10px 4px; font:var(--t-xs) var(--mono); color:var(--txt4);
  letter-spacing:.03em;
}

/* ---------- « Donner mon avis » ----------
   `textarea` n'est pris par aucune règle de contrôle de cette feuille (elles
   énumèrent `select` et les `input[type=…]`) : sans ce bloc, c'est un rectangle
   blanc à coins carrés au milieu d'une interface sombre — le défaut que le
   commentaire de `select,input[type=text]…` décrit plus haut. */
.avis-champ{
  width:100%; box-sizing:border-box; resize:vertical; min-height:120px;
  background:var(--bg3); color:var(--txt); border:1px solid var(--line);
  border-radius:var(--r-controle); padding:8px 10px; font-size:var(--t-l);
  line-height:1.5;
}
.avis-champ::placeholder{color:var(--txt4)}
.avis-champ:focus{border-color:var(--acc); outline:none}
.avis-compte{margin:4px 0 12px; font:var(--t-s) var(--mono); color:var(--txt4); text-align:right}
/* Au-delà de 2 000 la saisie n'est PAS bloquée — le serveur tronque et le dit
   (`tronque` dans sa réponse). Le compteur prévient donc en jaune plutôt que le
   champ ne coupe en silence : perdre la fin d'une phrase sans le savoir est pire
   que de la voir dépasser. */
.avis-compte.trop{color:var(--warn)}

/* ---------- la porte d'entrée ---------- */
/* Un calque OPAQUE par-dessus toute l'application, et non un remplacement du
   contenu : rien du studio n'est derrière — `demarrer()` ne charge pas —, mais
   la topbar et la mise en page existent déjà dans le HTML, et les masquer une à
   une aurait été une liste à tenir à jour à chaque élément ajouté. */
/* `overflow:auto` + `margin:auto` sur la boîte, ET NON `align-items:center`.
   La boîte a gagné le bouton Google, la phrase de bêta, la bascule et le pied
   juridique : sur 375 × 812 elle dépasse la fenêtre. Un `align-items:center`
   qui déborde COUPE des deux côtés et la partie haute devient inatteignable —
   c'est le piège classique du centrage en flex. Le centrage par marge
   automatique, lui, cède au défilement dès qu'il n'y a plus la place. */
.porte{
  position:fixed; inset:0; z-index:60; background:var(--bg);
  display:flex; overflow:auto; padding:24px;
}
.porte-box{
  width:100%; max-width:420px; margin:auto; background:var(--bg2); border:1px solid var(--line);
  border-radius:var(--r-gelule); padding:26px 24px 20px; box-shadow:0 24px 60px rgba(0,0,0,.5);
}
.porte-box .brand{margin-bottom:16px}
.porte-t{color:var(--txt2); font-size:var(--t-xl); line-height:1.5; margin:0 0 14px}
.porte-t b{color:var(--txt)}
.porte-form{display:flex; gap:6px}
.porte-form .rech{flex:1; min-width:0}
/* ─── LE MESSAGE EST UNE ALERTE, PAS UN PARAGRAPHE ──────────────────────────
   Le 2 septembre 2026, Julien n'a pas vu le message d'échec de la connexion
   Google sur sa propre porte : « il est noyé dans le reste ». Il avait raison,
   et le défaut était STRUCTUREL : `.porte-msg` et `.porte-beta` portaient le
   MÊME fond et le MÊME liseré de 2 px. Deux blocs de poids identique, dont l'un
   parle MAINTENANT et l'autre est du texte permanent — l'œil n'avait aucune
   raison de préférer le premier.

   LE LISERÉ DEVIENT LA SIGNATURE DE CE QUE L'APPLICATION DIT À L'INSTANT, et
   plus rien d'autre ne le porte. Le fond teinté par l'état fait le reste : on
   distingue un succès d'un échec AVANT de lire, ce qui est précisément ce qu'on
   demande à un message qui vient d'être manqué. */
.porte-msg{
  margin:12px 0 0; padding:11px 13px; border-radius:var(--r-controle);
  border-left:4px solid var(--line);
  background:var(--bg3); color:var(--txt); font-size:var(--t-l); line-height:1.5;
}
.porte-msg b{color:var(--txt)}
.porte-msg.ok{border-left-color:var(--ok);   background:color-mix(in srgb, var(--ok) 10%, var(--bg3))}
.porte-msg.ko{border-left-color:var(--warn); background:color-mix(in srgb, var(--warn) 14%, var(--bg3))}
/* F5 — `--txt3` et non `--txt4`. `--txt4` est LA VARIABLE DU DÉCORATIF (3,44:1
   sur le fond) et ce pied-là ne l'est pas : c'est la seule phrase de l'écran de
   connexion qui dise ce qui va se passer, sur le seul écran d'une personne qui
   n'est pas encore entrée. Même correction que `.pport.libre .pnom` et que
   `.ajout text` — trois emplois où le décoratif portait de l'information. */
.porte-pied{margin:16px 0 0; color:var(--txt3); font-size:var(--t-m)}

/* ---------- ce que la porte a gagné avec l'ouverture publique ----------

   Aucun jeton nouveau, aucune seconde famille de boutons : `.btn.primary`,
   `.rech`, `.mini-t` et les couleurs de `.note` font tout le travail. Ce bloc
   ne pose que des mises en page. */

/* Le bouton Google est un `<a>` : il lui manque juste la forme d'un bouton
   pleine largeur. `text-decoration` explicite — `.btn` n'a jamais eu à la
   nier, aucun autre bouton du projet n'étant un lien. */
.porte-google{display:block; width:100%; text-align:center; text-decoration:none; padding:9px 11px}

/* Le filet « ou ». Les deux traits sont des pseudo-éléments du paragraphe :
   deux `<span>` de décor dans le balisage se seraient fait lire à voix haute. */
.porte-ou{
  display:flex; align-items:center; gap:10px; margin:14px 0;
  color:var(--txt4); font:var(--t-s) var(--mono); text-transform:uppercase; letter-spacing:.08em;
}
.porte-ou::before,.porte-ou::after{content:""; flex:1; height:1px; background:var(--line)}

/* La phrase sur la gratuité — MÊME FORME QUE `.note`, qui est déjà la forme de
   « lis ceci » dans toute l'appli, et `--txt2` : ce n'est pas du décor, c'est
   la seule phrase de l'écran qui engage l'avenir. Le liseré est `--acc2`
   (informer) et non `--warn` (avertir) : la bêta gratuite n'est pas un danger. */
/* ⚠ LE BLOC BÊTA PERD SON LISERÉ, ET GARDE TOUT LE RESTE. Il cesse de se
   disputer l'attention avec les messages, sans être rétrogradé : le §13.24
   veut cette phrase lue AVANT le premier compte, « dans le corps de texte et
   non en pied de page ». Elle reste donc à `--t-l` et `--txt2`, taille et
   contraste inchangés — c'est le cadre qui change, pas le rang. La rétrograder
   en petite ligne grise serait la faire sauter, c'est-à-dire annuler la
   décision au lieu de l'appliquer. */
.porte-beta{
  margin:16px 0 0; padding:10px 12px; background:var(--bg3);
  border:1px solid var(--line); border-radius:var(--r-controle);
  color:var(--txt2); font-size:var(--t-l); line-height:1.5;
}
.porte-beta b{color:var(--txt)}
.porte-beta a{color:var(--acc)}

.porte-h2{margin:0 0 12px; font-size:var(--t-xl); color:var(--txt)}
/* le formulaire d'attente empile ses champs : `.rech` porte déjà
   `width:100%` et sa marque de bas, il n'y a rien à ajouter par champ */
.porte-form-col{display:flex; flex-direction:column}
.porte-form-col .mini-t{margin:6px 0 3px}
.porte-form-col .btn{margin-top:6px}

.porte-bascule{margin:14px 0 0; color:var(--txt3); font-size:var(--t-m)}
/* La note d'état de la bêta. Elle est SOUS le texte d'explication et AU-DESSUS
   du formulaire : c'est le seul endroit où elle est lue avant le geste, et elle
   ne prend qu'une ligne pour ne pas repousser le champ d'adresse. */
.porte-note{margin:-6px 0 12px; color:var(--txt3); font-size:var(--t-m); line-height:1.5}
.porte-note b{color:var(--txt2)}
/* L'accroche prolonge le titre : elle est donc plus grande que le corps de
   texte et plus discrète que le `h1`, et elle se colle à la marque plutôt que
   de flotter entre les deux. */
.porte-accroche{margin:-8px 0 18px; color:var(--txt2); font-size:var(--t-xl); line-height:1.45}
/* Le bloc « ce que ça fait » est SÉPARÉ par un filet et non par un simple
   écart : il change d'adresse — au-dessus on parle à qui entre, ici on parle à
   qui découvre. Sans le filet, ses trois lignes se liraient comme la suite du
   texte sur la gratuité, qui ne les concerne pas. */
.porte-quoi{margin:18px 0 0; padding-top:14px; border-top:1px solid var(--line)}
.porte-quoi p{margin:0 0 9px; color:var(--txt3); font-size:var(--t-m); line-height:1.5}
.porte-quoi p:last-child{margin-bottom:0}
.porte-quoi b{color:var(--txt2)}

/* Un bouton qui se lit comme un lien. C'est un `<button>` et pas un `<a href>`
   parce qu'il ne va NULLE PART : il bascule un volet dans la même page, et un
   lien qui ne navigue pas ment au clic droit comme au lecteur d'écran. */
.lien-txt{
  background:none; border:0; padding:0; font:inherit; color:var(--acc);
  text-decoration:underline; cursor:pointer;
}
.lien-txt:hover{filter:brightness(1.15)}

/* Les trois pages, en pied, hors des deux volets. `--txt3` et non `--txt4` :
   même correction que `.porte-pied` juste au-dessus — un lien qu'on doit
   pouvoir trouver n'est pas du décoratif. */
.porte-legal{
  display:flex; flex-wrap:wrap; gap:4px 14px;
  margin:20px 0 0; padding-top:12px; border-top:1px solid var(--line); font-size:var(--t-m);
}
.porte-legal a{color:var(--txt3); text-decoration:none}
.porte-legal a:hover{color:var(--acc)}

/* Sur un téléphone, 24 px de calque + 24 px de boîte mangent 96 px de largeur
   sur 375 — le formulaire de connexion (champ + bouton sur une ligne) y perd
   sa lisibilité avant tout le reste. */
@media (max-width:420px){
  .porte{padding:12px}
  .porte-box{padding:20px 16px 16px}
}

/* ---------- mise en page : baies en pleine largeur ---------- */
.layout{flex:1; display:flex; flex-direction:column; min-height:0}
.zone-baies{flex:1; display:flex; flex-direction:column; min-height:0; background:var(--bg)}
.baies-scroll{flex:1; overflow:auto; padding:14px 16px; min-height:0}
#baies{display:flex; flex-direction:column; gap:16px}

.bandeau{
  flex:none; height:300px; display:grid; grid-template-columns:1fr;
  background:var(--bg); min-height:120px;
}
.splitter{
  flex:none; height:7px; cursor:row-resize; background:var(--bg);
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  display:flex; align-items:center; justify-content:center;
}
.splitter i{width:38px; height:2px; border-radius:var(--r-pastille); background:var(--line)}
.splitter:hover{background:var(--bg3)}
.splitter:hover i,.splitter.actif i{background:var(--acc)}
body.redim{cursor:row-resize; user-select:none}
.panel{display:flex; flex-direction:column; min-height:0; min-width:0}


.panel-head{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:9px 14px; border-bottom:1px solid var(--line); flex:none;
}
.panel-head h2{font-size:var(--t-l); margin:0; text-transform:uppercase; letter-spacing:1px; color:var(--txt2); font-weight:600; white-space:nowrap}
.head-actions{display:flex; align-items:center; gap:9px; flex:none}
/* LA RECHERCHE DE LA RÉSERVE, dans le bandeau et non sous lui.

   Une deuxième rangée aurait mangé une dizaine de pixels de baie en permanence,
   or c'est la baie qui est le sujet (§14.5, étape 1) ; le bandeau, lui, a de la
   place à sa droite dès qu'on rend `h2` et `.head-actions` non compressibles.
   Le champ est le SEUL élément élastique de la rangée, donc le seul qui rétrécit
   quand la fenêtre le fait — c'est le bon ordre de sacrifice : on peut chercher
   dans un champ étroit, on ne peut pas cliquer un bouton écrasé à zéro.

   `min-width` plutôt que rien : sous ce seuil, le libellé « Rechercher » se
   tronque et le champ cesse de dire ce qu'il est. Le plancher est en `ch`, donc
   il suit `--fs` comme le texte qu'il doit contenir.

   `type="search"` pour la croix native de vidage — la seule commande de l'appli
   qu'on ne dessine pas, parce que le navigateur la place mieux au doigt qu'un
   bouton de plus dans une rangée déjà pleine. Son apparence est neutralisée
   sous WebKit, qui l'assortirait au thème clair du système. */
.rech-inv{
  flex:1 1 auto; min-width:11ch; max-width:260px; width:11ch;
  padding:4px 8px; -webkit-appearance:none; appearance:none;
}
.rech-inv::placeholder{color:var(--txt4)}
.rech-inv:focus{border-color:var(--acc)}
.rech-inv::-webkit-search-cancel-button{-webkit-appearance:none; appearance:none;
  width:11px; height:11px; cursor:pointer; opacity:.6;
  background:currentColor;
  -webkit-mask:linear-gradient(currentColor,currentColor) center/11px 1.5px no-repeat,
               linear-gradient(currentColor,currentColor) center/1.5px 11px no-repeat;
  mask:linear-gradient(currentColor,currentColor) center/11px 1.5px no-repeat,
       linear-gradient(currentColor,currentColor) center/1.5px 11px no-repeat;
  rotate:45deg;
}
.rech-inv::-webkit-search-cancel-button:hover{opacity:1}
.check{font-size:var(--t-m);color:var(--txt3);display:flex;align-items:center;gap:5px;cursor:pointer}
.check.big{font-size:var(--t-l);color:var(--txt2)}

/* LA CASE « ENVOYER CHEZ MOI », ENCADRÉE — et ce n'est pas de la décoration.
   Depuis que le bouton dit « Créer un appareil » / « Créer une baie » (et non
   plus « + Nouveau modèle »), c'est ELLE qui rend le libellé honnête : cochée,
   le geste fait ce qu'il annonce ; décochée, il ne remplit que la bibliothèque.
   Une case qui décide de ça ne peut pas avoir l'air d'une option de plus.

   Côté baies elle en avait pourtant tout l'air : `.modes-pick` aligne juste
   au-dessus trois cases de MÊME CLASSE (`check big`) qui parlent d'autre chose,
   et la quatrième s'y noyait. Le cadre la sort de la pile ; le `margin-top`
   qu'elle portait en style en ligne rentre ici avec le reste.

   Sobre exprès : un fond levé d'un cran et un liseré, pas d'accent orange.
   L'orange plein ne signifie qu'une chose dans cette interface — où je suis —
   et b38cc44 vient de le débarrasser de ses derniers emplois décoratifs. */
.check.envoi{margin-top:12px; padding:9px 12px; background:var(--bg3);
  border:1px solid var(--line); border-radius:var(--r-carte); color:var(--txt)}
.check.envoi input[type=checkbox]{accent-color:var(--acc)}
.scroll{overflow-y:auto; min-height:0}
.hint{
  font-size:var(--t-m); color:var(--txt2); margin:0; line-height:1.5; flex:none; background:var(--bg2);
  max-height:0; padding:0 14px; border-top:0 solid var(--line); overflow:hidden;
  transition:max-height .12s, padding .12s;
}
.hint:not(:empty){max-height:40px; padding:8px 14px; border-top-width:1px}
.hint b{color:var(--txt2)} .hint em{font-style:italic}
/* un refus : même couleur que le voyant `ext` qui l'annonce, pour qu'on lise
   les deux comme un seul message.
   LES DEUX BANDES, ET C'EST UNE SEULE RÈGLE : la vue baies a `.hint`, la vue
   schéma a `.schema-bar .aide`. Elles font le même travail — dire l'état du
   geste en cours — et un refus doit s'y lire pareil. Deux déclarations de la
   même couleur finiraient par diverger. */
.hint .refus, .schema-bar .aide .refus{color:var(--acc2); font-style:normal}
.grow{flex:1}
/* `flex-wrap:wrap` — ET IL MANQUAIT. Signalé par Julien : un point de baie qui
   porte un CORDON offre sept contrôles (les quatre icônes d'appareil du lot
   93edfa5, plus « Libérer 7B », « Débrancher le cordon » et « Voir dans le
   schéma »), là où un point ordinaire en offre trois. Sans repli, un flex ne
   rend pas la place qu'il n'a pas : les boutons débordaient des 360 px du
   popover, et comme `.detail-pop` ne borne que son `overflow-y`, le navigateur
   ouvrait un DÉFILEMENT HORIZONTAL dans une fenêtre de 360 px.

   Le repli est la bonne réponse et non un plafond de largeur : ces boutons
   portent des verbes de longueur très inégale (« Fermer » contre « Repasser en
   câble direct »), et les comprimer les rendrait illisibles avant de les rendre
   étroits. La variante `.pile` juste dessous traite le cas extrême — quand ce
   ne sont plus des boutons mais des actions nommées, la rangée devient une
   liste. Entre les deux, le repli suffit.

   `overflow-x:hidden` sur le popover est la ceinture : une rangée qui déborde
   quand même ne doit jamais produire un défilement latéral dans un panneau de
   360 px, où il est invisible et introuvable. */
.row-btn{display:flex; flex-wrap:wrap; gap:6px; margin-top:9px}
/* `.pile` — LA RANGÉE DEVIENT UNE LISTE quand ce ne sont plus des boutons mais
   des ACTIONS NOMMÉES. Le popover de colonne en porte cinq, dont trois dont le
   nom dit à la fois le verbe et ce qui arrive aux voisines (« Libérer · décaler
   à gauche ») : dans une rangée de 360 px, `flex` les comprimait à quatre lignes
   de deux mots chacune, illisibles, et poussait les deux dernières hors du
   popover. Une liste verticale, alignée à gauche, est la forme qu'a déjà le menu
   `⋯` pour exactement le même contenu — des phrases d'action, pas des étiquettes
   de validation. Modificateur et non nouvelle classe : `.row-btn` garde ses
   marges, son écart et tous ses usages en rangée. */
.row-btn.pile{flex-direction:column; align-items:stretch}
.row-btn.pile .btn{text-align:left}
/* Un rang de choix mutuellement exclusifs dans une modale : le cran retenu
   porte `.primary`, les autres non. Pas de `<select>` — on compare des tailles
   de texte en les VOYANT, et chaque bouton s'affiche à la sienne. */
.choix{display:flex; gap:6px; flex-wrap:wrap; margin:2px 0 10px}
.choix .btn{display:flex; align-items:center; min-height:34px}

/* ---------- inventaire ---------- */
.panel-inv > #inventaire{flex:1 1 auto; min-height:0; overflow-y:auto}
/* MULTI-COLONNES, ET NON UNE GRILLE. Une grille aligne les cartes en RANGÉES :
   chaque rangée prend la hauteur de sa carte la plus grande, et une console à
   15 ports laissait donc un trou de 600 px sous la guitare qui n'en a qu'un.
   Sur un inventaire réel, la moitié basse du bandeau était du vide.

   `columns` empile au contraire les cartes VERTICALEMENT, l'une contre l'autre,
   et le navigateur équilibre les colonnes tout seul — c'est le rendu « maçonné »
   qu'on veut, sans une ligne de JavaScript ni une mesure au chargement.
   `grid-template-rows:masonry` ferait la même chose en gardant l'ordre par
   rangées, mais il n'est encore derrière un drapeau dans aucun navigateur
   utilisable, et ce panneau doit marcher aujourd'hui.

   Le prix, assumé : la lecture devient colonne par colonne (de haut en bas, puis
   la colonne suivante) au lieu de rangée par rangée. Les cartes sortent dans
   l'ordre du FLUX (`renderInventaire`), le même que les colonnes du schéma :
   deux appareils de même nature se suivent, et l'équilibrage les garde voisins
   dans la même colonne ou dans celle d'à côté. Ce qu'on perd, c'est la garantie
   qu'une catégorie ne soit jamais coupée entre deux colonnes — et on l'accepte :
   c'est une réserve de ports, pas une carte du studio. */
.grille{
  columns:212px; column-gap:8px; padding:10px;
}
.dev{
  border:1px solid var(--line); border-left:3px solid var(--it); border-radius:var(--r-controle);
  background:var(--bg3); overflow:hidden;
  /* une carte ne se coupe JAMAIS entre deux colonnes : un appareil dont les
     ports se poursuivent dans la colonne d'à côté serait illisible */
  break-inside:avoid; -webkit-column-break-inside:avoid;
  display:inline-block; width:100%; vertical-align:top; margin:0 0 8px;
}
/* PLUS DE `cursor:pointer` : L'EN-TÊTE N'EST PLUS UNE BASCULE. Il repliait la
   carte entière — ports libres compris, c'est-à-dire le sujet même de la vue —
   et c'était l'un des trois remèdes partiels que la ligne `.depli` du bas
   remplace à elle seule. Le fond au survol reste : il n'annonce plus un clic sur
   l'en-tête, il révèle la rangée d'icônes (règle juste en dessous), qui, elles,
   sont bien des cibles. */
.dev-head{display:flex; align-items:flex-start; gap:6px; padding:6px 7px; font-size:var(--t-l); font-weight:600; color:var(--txt)}
.dev-head{background:rgba(255,255,255,.03)}
.dev-head:hover{background:rgba(255,255,255,.07)}
.dev-id{display:flex; flex-direction:column; gap:1px; min-width:0; flex:1}
.dev-head .devnom{line-height:1.25; overflow-wrap:anywhere}
/* B2 — `nowrap` SANS `overflow` N'EST PAS UNE TRONCATURE, C'EST UN DÉBORDEMENT.
   « interface · 12 ports libres » sortait de la carte par la droite, SOUS les
   quatre icônes d'action, sur du contenu parfaitement ordinaire. Et ce qu'on y
   perdait est le compte de ports libres, c'est-à-dire l'information même que le
   filtre du panneau produit. `.dev-id` porte déjà `min-width:0`, le piège du
   flex était donc évité — il ne manquait que de dire où couper.

   ET LA LIGNE SE REPLIE PLUTÔT QUE DE SE TRONQUER, ce qui s'écarte de la lettre
   de la revue (« overflow + text-overflow, 1 ligne ») mais suit son motif. Elle
   dit que ce qui se perd est LE COMPTE DE PORTS LIBRES, « l'information même que
   le filtre produit » — or une ellipse le perd tout autant qu'un débordement,
   proprement. Mesuré : le texte réclame 190 px dans une boîte de 92, sur les dix
   cartes du studio réel ; « instrument · … » n'aurait rien dit de plus que le
   débordement, et il n'y a pas d'infobulle sur cette ligne pour rattraper.
   Replier coûte une ligne de 12 px sur les cartes concernées et rend le nombre.
   `overflow:hidden` reste, en filet : un mot insécable de 60 caractères ne doit
   pas ressortir sous les icônes par une autre porte. */
.dev-head .cat{font:var(--t-xs) var(--mono); color:var(--txt3); font-weight:400;
  white-space:normal; overflow-wrap:anywhere; overflow:hidden; line-height:1.35}
/* Le `.caret` (▾/▸) de l'en-tête est parti avec la bascule qu'il annonçait.
   Sa règle est retirée plutôt que laissée à traîner : une règle qui ne vise plus
   rien se relit six mois plus tard comme un élément qu'on aurait oublié de
   rendre. */
.dev-head .acts{display:flex; gap:1px; opacity:.7; flex:none; margin-top:-2px}
.dev-head:hover .acts{opacity:1}
.dev-head .acts .ia.danger{margin-left:5px}
/* Une redéclaration à l'identique de la règle six lignes plus haut vivait ici.
   Elle n'ajoutait rien — et elle NUISAIT, discrètement : la forme raccourcie
   `font:` remet `line-height` à `normal`, donc elle écrasait l'interligne que
   B2 vient de poser sur cette même ligne. Retirée. */
.ia{
  background:none; border:0; color:var(--txt2); cursor:pointer; font-size:var(--t-m);
  padding:4px 5px; border-radius:var(--r-pastille); line-height:1; display:inline-flex; align-items:center;
}
.ia svg,.kill svg{pointer-events:none; display:block}
/* B1 — LE COMPTEUR EST DU DÉCOR, PAS UNE CIBLE. Le `<em>` de « poser tout »
   porte le nombre de ports posables ; un clic qui tombait dessus avait pour
   cible l'`<em>` et non le bouton, remontait donc au `.dev-head` et REPLIAIT
   la fiche au lieu de poser les ports. Le `svg` de l'icône était déjà
   neutralisé juste au-dessus — le chiffre avait été oublié, et il grossit
   avec le bloc `pointer:coarse` (padding 6 px), donc la zone fautive aussi.
   Depuis que l'en-tête ne replie plus, le symptôme a changé — le clic ne ferait
   plus rien du tout au lieu de replier — mais la règle sert autant : « poser
   tout » resterait inatteignable par son propre chiffre. */
.ia em{pointer-events:none}
.ia:hover{background:var(--line); color:var(--txt)}
.ia.danger:hover{background:var(--err); color:#fff}
.ports{
  padding:5px; display:flex; flex-direction:column; gap:3px;
  /* UN PLAFOND SUR LA HAUTEUR D'UNE CARTE, et c'est ce qui rend le maçonnage
     ci-dessus utilisable pour de vrai. `columns` équilibre les colonnes sur une
     hauteur au moins égale à celle du plus grand bloc INSÉCABLE. Une console à
     52 ports mesure ~1500 px : elle imposait donc 1500 px à toutes les colonnes,
     et l'inventaire entier se tassait dans les trois premières pendant que la
     moitié droite du bandeau restait vide. Le trou ne venait pas de l'ordre des
     cartes, il venait d'une seule carte.

     Plafonner la liste de ports ramène la plus grande carte de ~1500 px à ~450 px
     (mesuré sur le studio de Julien, à l'échelle 1,3) : la hauteur d'équilibrage
     retombe d'autant et le bandeau reprend des colonnes. Tous les ports restent
     là, atteignables et glissables — on défile DANS la carte.

     Ça ne remplit pas la largeur pour autant : `columns` remplit ses colonnes
     SÉQUENTIELLEMENT et ne sait pas ranger des blocs de hauteurs inégales, donc
     il en laisse toujours à droite. Combler ça demande `column-fill:auto` et un
     bandeau qui défile HORIZONTALEMENT — un autre geste, pas un réglage —, et le
     bandeau étant redimensionnable jusqu'à 120 px, une carte plus haute que sa
     colonne y déborderait. Pas fait, et sciemment.

     Replier les grosses cartes par défaut ne marchait pas : un repli ne plafonne
     rien, la carte rouverte reprend ses 1500 px et le trou revient.

     ── LE PLAFOND RESTE, MÊME DEPUIS LE PLI PAR CARTE (« + N ports utilisés ») ──
     La question s'est reposée quand l'inventaire a reçu son pli, et la réponse
     est non, pour une raison qui tient en une phrase : LE PLI DE L'INVENTAIRE
     CACHE LES PORTS POSÉS, ET UN STUDIO QUI COMMENCE N'EN A AUCUN. Le pli ne
     rend donc RIEN précisément dans l'état où le plafond a été écrit — la
     réserve pleine, tout à poser. C'est la symétrie du schéma retournée contre
     nous : là-bas le pli cache les ports LIBRES, et un studio qui commence n'a
     que ça, donc le schéma se replie tout seul au maximum quand l'inventaire ne
     se replie pas du tout.

     Mesuré au navigateur sur le studio réel (10 appareils, 48 points posés,
     1440 px, échelle 1,3), avant et après le pli : au repos la plus haute carte
     fait 261 px puis 293 px (la ligne de pli coûte une rangée), et AUCUNE des
     `.ports` n'atteint le plafond de 364 px — il ne mord pas, il n'y a donc pas
     de troisième défilement imbriqué à supprimer, ni avant ni après. Il mord
     sous « tout déplier » : la carte du Clarett y réclame 614 px de ports et la
     Spirit E 445, ramenées à 364. Retirer le plafond ne gagnerait rien au repos
     et rendrait le mur de 1500 px dans les deux cas qui comptent — le studio
     neuf, et « tout déplier ». */
  max-height:calc(280px * var(--fs));
  overflow-y:auto;
}
/* LA LIGNE DE REPLI D'UNE CARTE — « + 13 ports utilisés ». Elle reprend trait
   pour trait celle du schéma (`.depli`, plus bas dans cette feuille) : le
   pointillé de ce qui attend, le mono, `--txt3`, et l'accent au survol. Même
   geste sur l'autre vue, donc même dessin — c'est ce qui fait qu'on le
   reconnaît en changeant de vue sans le réapprendre.

   ELLE EST DEHORS DE `.ports`, ET C'EST LA SEULE DIVERGENCE. La liste ci-dessus
   est plafonnée et défile ; une ligne posée dedans passerait sous le pli du
   défilement dès que la carte est ouverte, c'est-à-dire au moment exact où l'on
   cherche à la refermer. Le schéma n'a pas de plafond et n'a pas ce problème.

   `.dev-head + .depli` : quand la carte n'a aucun port visible (tout est posé et
   replié), il n'y a pas de `.ports` pour fournir l'écart du haut. */
.dev .depli{
  display:block; width:calc(100% - 10px); margin:0 5px 5px; text-align:left;
  background:none; border:1px dashed var(--line); border-radius:var(--r-pastille);
  padding:5px 7px; font:var(--t-s) var(--mono); color:var(--txt3);
}
.dev-head + .depli{margin-top:5px}
.dev .depli:hover{border-style:solid; border-color:var(--acc); background:rgba(255,159,28,.07); color:var(--acc)}
/* dépliée, la ligne n'annonce plus rien : elle constate. Le pointillé s'efface
   comme le `stroke:transparent` de `.depli.ouvert` côté schéma — mais la cible,
   elle, reste entière. */
.dev .depli.ouvert{border-color:transparent; color:var(--txt4)}
.dev .depli.ouvert:hover{border-color:var(--acc); color:var(--acc)}
.port{
  display:flex; align-items:center; gap:6px; padding:4px 7px; border-radius:var(--r-pastille);
  background:var(--bg2); border:1px solid transparent; cursor:grab; font-size:var(--t-m);
}
.port:hover{border-color:var(--sig)}
.port.placed{opacity:.35; cursor:default}
.port.held{border-color:var(--acc); background:rgba(255,159,28,.16); box-shadow:0 0 0 1px var(--acc) inset}
.port .dir{font:var(--t-xs) var(--mono); color:var(--txt3); text-transform:uppercase; flex:none}
/* ⚠ `tabular-nums` EST LA RÉPONSE AU BRICOLAGE D'ESPACES DU §25.2. Un testeur
   tapait « Out  1 » avec deux espaces pour que sa colonne de numéros tombe
   droite : c'est à l'affichage de le faire, jamais à la donnée. Les chiffres
   prennent ici tous la même largeur, donc « Out 1 » et « Out 9 » se superposent
   exactement. Ce que ça ne fait PAS, et il faut le dire : « Out 9 » reste plus
   court qu'« Out 10 » — un chiffre de moins est un chiffre de moins, et seule
   une colonne alignée à droite y répondrait, ce que cette liste ne demande
   pas. */
.port .pnom{overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-variant-numeric:tabular-nums}
.port .at{margin-left:auto; font:var(--t-s) var(--mono); color:var(--acc); white-space:nowrap}
/* LE JETON « DIRECT » — à la place du `⚠`, pour les QUATRE niveaux qu'aucune baie
   ne transporte : casque, puissance, numérique et MIDI. Il se peint comme une
   ÉTIQUETTE et non comme une alerte, à l'emplacement exact qu'occupait le `⚠`
   pour que la rangée ne bouge pas. Voir `portChip` pour le pourquoi.

   ⚠ NEUTRE, ET C'EST LE FOND DE L'AFFAIRE. Il a d'abord été violet — la couleur
   du signal MIDI —, parce qu'il ne servait qu'au MIDI et se distinguait ainsi du
   `⚠` laissé aux trois autres. Cette distinction est tombée : les quatre nomment
   le même geste, et une couleur de signal sur trois d'entre eux mentirait. Le
   liseré de l'appareil (`--sig`) porte déjà la couleur de la ligne ; ce jeton dit
   une DESTINATION, qui n'a pas de teinte à elle.

   Ni `--warn`, surtout : un port sain qui se relie en direct n'a rien à corriger,
   et c'est précisément la peinture d'alerte sur un fait ordinaire qui a fait
   cesser de lire le `⚠`. */
.port .jeton-direct{
  margin-left:auto; flex:none; font:var(--t-xs) var(--mono); letter-spacing:.4px;
  color:var(--txt3); background:rgba(255,255,255,.05);
  border:1px solid var(--line); border-radius:var(--r-pastille);
  padding:0 5px; line-height:1.5;
}
.panel-inv.dropzone{background:rgba(239,71,111,.10); box-shadow:inset 0 0 0 2px var(--err)}

/* ---------- une baie ---------- */
.baie{border:1px solid #333c48; border-radius:var(--r-carte); background:linear-gradient(180deg,#232a34,#1a2027); box-shadow:0 6px 24px rgba(0,0,0,.4)}

/* LA CARTE SUIT SON CONTENU, ET NON LA FENÊTRE — le fond de baie s'arrêtait au
   bord de l'écran pendant que les colonnes continuaient derrière lui.

   LE MÉCANISME, VÉRIFIÉ À 1152 px : `#baies` est un conteneur de flex en
   colonne dans `.baies-scroll{overflow:auto}`. Un item de flex s'étire par
   défaut (`align-items:stretch`) à la largeur de son CONTENEUR, c'est-à-dire à
   la largeur VISIBLE — 1120 px mesurés — alors que la grille `.bay` de la baie
   TRS 24 paires en réclame 1776. La bordure, le dégradé, le rayon et l'ombre
   sont portés par `.baie` : ils s'arrêtaient donc à 1120, et les colonnes 21 à
   24, l'en-tête « Baie 1 … 39/48 » et le trait sous lui n'étaient plus dans
   aucune carte. On voyait le fond de page à travers la baie.

   C'est le même angle mort que le §14, d'un cran plus bas : `body.scrollWidth`
   ne dit RIEN de ce qui se passe DANS un conteneur défilant. La page ne
   débordait pas ; la carte, si.

   `width:max-content` DONNE À L'ITEM UNE LARGEUR DÉFINIE, ce qui suffit à
   défaire l'étirement — inutile de toucher à l'`align-items` de `#baies`, qui
   reste juste pour tout le reste. `min-width:100%` est l'autre moitié : la baie
   XLR 12 paires, elle, ne déborde pas, et sans plancher elle rétrécirait à sa
   propre largeur en laissant du vide à droite. Les deux ensemble disent « au
   moins la fenêtre, sinon le contenu ». Mesuré après : TRS 1778 px, XLR 1120 px
   à une fenêtre de 1152.

   `#baies > .baie` ET NON `.baie` : le schéma porte un `<g class="baie">` dans
   son SVG. `width` n'y ferait rien, mais un sélecteur qui vise deux choses sans
   le dire est un piège qu'on paie au prochain réglage. */
#baies > .baie{min-width:100%; width:max-content}
.baie-head{display:flex; align-items:center; gap:10px; padding:7px 12px; border-bottom:1px solid #333c48}
.baie-head b{font-size:var(--t-l)}
.baie-head .meta{font:var(--t-s) var(--mono); color:var(--txt3)}
.baie-head .occ{margin-left:auto}
.baie-head .acts{display:flex; align-items:center; gap:2px}
/* #80 — le séparateur entre « ranger cette baie » (touche du câblage) et
   « configurer/retirer » (touche la baie elle-même) : deux natures de geste,
   pas une liste plate de quatre icônes. */
.baie-head .acts .spr{width:1px; height:16px; background:var(--line); margin:0 3px; flex:none}
/* LA PORTE D'AJOUT DE BAIE, sous la dernière baie. Discrète — `.btn` nu, pas
   `.primary` : ce n'est pas le geste principal de l'écran, c'est le geste qu'on
   veut trouver SANS le chercher quand il vient. `align-self:flex-start` et
   `sticky` : `#baies` prend la largeur de la baie la plus large, le bouton doit
   rester au bord gauche et y rester pendant le défilement horizontal. */
.ajout-baie{position:sticky; left:0; align-self:flex-start}
.empty-bay{padding:40px; text-align:center; color:var(--txt3); font-size:var(--t-xl); display:flex; flex-direction:column; align-items:center; gap:12px}

/* La gouttière de tête et la largeur d'une colonne suivent l'échelle : elles
   portent des numéros et des noms de port, donc du texte à lire. */
.bay{
  /* LA HAUTEUR D'UN POINT N'EST PLUS ÉCRITE QU'ICI. C'est la leçon de B6
     (§14.7) prise au mot : la cote vivait en DEUX exemplaires — `.point` et
     `.col.gouttiere .rang`, le libellé « A / sorties » qui nomme la rangée —, et
     deux exemplaires d'une même cote finissent par décrocher au premier réglage.
     C'est justement ce que le bloc tactile plus bas allait provoquer : il change
     la hauteur d'un point, et le libellé serait resté sur l'ancienne.

     52 → 58, ET LE CHIFFRE EST LE RÉSULTAT D'UNE INÉQUATION, PAS D'UN GOÛT.
     Depuis que le nom de port s'enroule, le texte d'un point mesure au plus
     `2 + 37,8 × fs` de haut, tandis que le `✕` de libération commence à
     `h − 15` — 15 px EN DUR, eux, puisque ni sa taille ni son écart au bord ne
     portent `--fs`. Avec `h = 52 × fs`, la condition « le texte finit avant le
     bouton » se réduit à `fs ≥ 1,20` : elle n'était donc vraie qu'au cran
     « Grande », et « Ctrl Room R » perdait son « R » aux trois autres —
     mesuré, pas supposé. Avec 58 elle devient `fs ≥ 0,84`, donc vraie aux
     quatre crans, y compris « Compacte ». Mettre le `✕` à l'échelle n'aurait
     RIEN réglé : le texte grandirait avec lui et le rapport ne bougerait pas.
     Ce sont bien 6 px de hauteur qu'il fallait, soit 12 px par baie. */
  --h-point:calc(58px * var(--fs));
  display:grid; grid-template-columns:calc(58px * var(--fs)) repeat(var(--n),var(--w,calc(58px * var(--fs))));
  padding:10px 8px; gap:0; justify-content:start;
}
/* Les marges latérales d'un point suivent l'échelle, elles aussi — même famille
   que B5 et B6 du §14.7 : une cote en dur face à une piste de grille qui, elle,
   est en `calc(… * var(--fs))`. Ici la conséquence est ARITHMÉTIQUE et non
   seulement visuelle : la largeur de texte d'un point vaut « largeur de colonne
   moins 16 px de chrome ». Le chrome figé, cette largeur ne se met pas à
   l'échelle proportionnellement — 54 px de texte à 1,3 mais 32,6 px à 0,9 —, et
   le budget de caractères du sigle (`BUDGET_SIGLE`, sept) ne tiendrait qu'aux
   grandes échelles. Mis à l'échelle, le rapport devient constant et le budget
   vaut aux quatre crans. */
.col{display:grid; grid-template-rows:auto 1fr auto 1fr; gap:3px; padding:0 calc(2px * var(--fs)); min-width:0}
.num{text-align:center; font:var(--t-xs) var(--mono); color:var(--txt3)}
.point{
  /* la hauteur suit l'échelle : un point contient deux lignes de texte, et
     une police plus grande dans une boîte figée déborderait */
  position:relative; height:var(--h-point); border-radius:var(--r-controle);
  border:1px dashed #2b333d; background:#12171e; cursor:pointer;
  /* LE CONTENU EST CALÉ EN HAUT, ET NON PLUS CENTRÉ. Deux raisons, et la
     seconde n'est pas de l'esthétique :
     — un nom de port occupe désormais une ou deux lignes selon sa longueur ;
       centré, le sigle de chaque point se pose à une hauteur différente de son
       voisin, et l'œil qui balaie une façade de 24 colonnes n'a plus de ligne de
       base commune. Calé en haut, les sigles s'alignent, les noms de port
       commencent tous au même `y`, et la baie se lit en deux bandes ;
     — le `✕` de libération vit dans le coin bas-droit. Texte centré, la
       deuxième ligne d'un nom de port descendait DESSOUS : « Ctrl Room R »
       perdait son « R » — la lettre même qui le sépare de son voisin. Caler en
       haut rend les 5 px qui séparent le bas du texte du haut du bouton. */
  display:flex; flex-direction:column; justify-content:flex-start; gap:1px;
  /* 3 px et non plus 4 en horizontal : les DEUX bordures, elles, ne sont pas à
     l'échelle — `border-left:3px` porte la couleur d'appareil (jeton unifié,
     `a23be15`) et n'a pas à bouger. Ces 4 px fixes rognent le texte d'autant
     plus que l'échelle est petite ; les rendre à `.pd` est ce qui fait tenir les
     sept caractères du sigle au cran « Compacte » aussi (35,6 px de texte pour
     34,1 px de sigle) et non seulement au cran « Grande » (53,2 pour 49,3). */
  padding:calc(3px * var(--fs));
  overflow:hidden; transition:border-color .12s, background .12s;
}
.point:hover{border-color:var(--acc)}
.point[draggable="true"]{cursor:grab}
.point.dragging{opacity:.35}
.point.drop{border-color:var(--acc); background:rgba(255,159,28,.12)}
.point.filled{border:1px solid #29323d; border-left:3px solid var(--sig); background:#171e26}
.point.sel{outline:2px solid var(--acc); outline-offset:1px}
.point.curseur{outline:2px dashed var(--acc); outline-offset:1px; background:rgba(255,159,28,.09)}
.point.curseur::before{content:'▸'; position:absolute; left:3px; top:50%; transform:translateY(-50%); color:var(--acc); font-size:var(--t-m); line-height:1}
.point.c-err{border-color:var(--err)}
.point.c-warn{border-color:var(--warn)}
.point.c-info{}   /* une info ne change pas la bordure : seul le badge ℹ la signale */
/* 8 → 7 px, et 3 → 2 px de bord : DEUX pixels rendus au texte, qui décident
   d'un cas limite entier. La pastille de jack vit DANS la zone de texte du
   point, dans le coin haut-droit, et depuis que le contenu est calé en haut
   c'est la ligne d'appareil qui passe dessous. Elle tient partout — sauf quand
   la colonne tombe sur son PLANCHER (`54 × fs`, ce qui arrive dès que le
   bandeau rétrécit, donc sur téléphone) ET que l'échelle est « Compacte » ou
   « Normale » : le sixième caractère du sigle finissait alors sous le point,
   rogné de 2 px. Les deux autres issues coûtaient plus cher pour moins :
   ramener le budget à CINQ caractères abîmait tous les sigles à toutes les
   échelles pour un cas de coin (« Spiri », « JamMa »), et mettre la pastille à
   l'échelle l'aggravait — le texte grandit avec elle, et 11 px fixes valent
   proportionnellement MOINS quand tout est grand, pas plus.
   Un point de 7 px reste un point : ce qu'il dit — la prise, et son orange
   `branche` quand un cordon de façade y arrive — se lit à l'identique. */
.point .jack{position:absolute; top:2px; right:2px; width:7px; height:7px; border-radius:var(--r-cercle); background:#0a0d11; border:1px solid #46525f}
.point .jack.branche{background:var(--acc); border-color:var(--acc); box-shadow:0 0 6px var(--acc)}
.point .badge{position:absolute; top:2px; left:3px; font-size:var(--t-xs); line-height:1; pointer-events:none; opacity:.8}
.point.c-err .badge,.point.c-warn .badge{opacity:1}
/* UN POINT SIGNALÉ A UNE LIGNE D'APPAREIL PLUS COURTE, ET C'EST ASSUMÉ.
   Le badge de diagnostic occupe le coin haut-GAUCHE ; depuis que le contenu est
   calé en haut, il se posait sur la première lettre du sigle — « ⚠laret » au
   lieu de « Claret », vu en séance sur 2 des 48 points. L'arithmétique du point
   ne laisse pas le choix : la zone de texte va de 6,9 à 60,1 px, le badge en
   prend 11,3 à gauche et la pastille de jack 9 à droite, il reste 39 px, soit
   CINQ caractères et non six. On borne donc la boîte des deux côtés et on laisse
   l'ellipse dire le sixième — « Clar… ». C'est la même règle que partout
   ailleurs dans ce lot : une étiquette qui tient, ou une ellipse qui avoue.
   Ce qu'on ne fait PAS : appliquer le retrait à tous les points. 46 sur 48 ne
   portent aucun badge et paieraient une place pour une marque qu'ils n'ont pas ;
   et raccourcir le sigle partout pour deux points signalés reviendrait à laisser
   le cas rare commander le cas ordinaire. */
.point.c-err .pd,.point.c-warn .pd,.point.c-info .pd{
  padding-left:calc(8px * var(--fs)); padding-right:calc(4px * var(--fs));
}
.point .kill{
  position:absolute; bottom:1px; right:2px; width:14px; height:14px; padding:0; border:0; border-radius:var(--r-pastille);
  background:rgba(239,71,111,.2); color:var(--err); font-size:var(--t-xs); line-height:1; cursor:pointer; opacity:0; transition:opacity .12s;
}
.point:hover .kill{opacity:1}
.point .kill:hover{background:var(--err); color:#fff}
/* LE NOM DE PORT NE SE COUPE PLUS — IL S'ENROULE. (B9 avait aligné `.pn` sur
   `.pd` en le mettant à l'ellipse ; on prend ici le problème par l'autre bout.)

   CE QUI A ÉTÉ MESURÉ, sur le studio réel rempli à 48 points sur 72, à
   1440 × 900 : la colonne tombe sur son plancher, le texte d'un point dispose de
   54 px, et **24 des 48 noms de port** demandaient davantage — jusqu'à 88 px
   pour « Insert 1 Return ». Or ces 88 px ne sont pas gagnables : à 24 paires
   dans 1408 px de bandeau, la part de chaque colonne est de 55 px. Élargir
   jusqu'à ce que « Insert 1 Return » tienne sur UNE ligne demanderait ~3070 px
   de baie, soit deux écrans à faire défiler pour lire une façade qu'on veut
   embrasser d'un coup d'œil. Ce n'est donc pas de la largeur qu'il manquait.

   C'EST DE LA HAUTEUR, ET ELLE ÉTAIT LÀ. `.pd` et `.pn` sont empilés dans une
   colonne flex : ils ont DÉJÀ chacun toute la largeur du point — le nom
   d'appareil ne volait pas la place du nom de port, il lui volait des LIGNES.
   Un point mesure 61,6 px de contenu utile là où deux lignes de port et un sigle
   en consomment 45. Rendu à `.pn`, cet espace suffit : le mot le plus large de
   tout le studio est « Monitor » (47 px < 54), donc l'enroulement se fait sur
   des mots entiers, sans césure, et les 24 troncatures tombent à ZÉRO.

   `line-clamp:3` est un FILET, pas un réglage : trois lignes couvrent tout le
   contenu observé avec une marge, et bornent le débordement d'un nom de port
   absurdement long au lieu de le laisser pousser la boîte. `overflow-wrap`
   couvre l'autre cas limite, le mot unique plus large que le point : il se coupe
   au lieu de sortir du cadre. Aucun des deux ne se déclenche sur les données du
   dépôt — ils sont là pour celles des autres. */
/* `text-wrap:balance` n'est PAS de la cosmétique ici : sans lui, « 1/4" Out L »
   se coupe en « 1/4" Out » + « L », et l'orpheline est justement la lettre qui
   dit le CANAL — la seule chose qui sépare ce point du suivant. Équilibré, il
   rend « 1/4" » + « Out L », où le côté reste collé à ce qu'il qualifie. Même
   effet sur « Insert 1 Return » et « Monitor Out R ». Là où la propriété n'est
   pas connue, elle est ignorée et l'enroulement ordinaire reprend : le pire cas
   est l'orpheline, jamais une troncature. */
.point .pn{font-size:var(--t-s); line-height:1.2; color:var(--txt);
  white-space:normal; overflow-wrap:break-word; text-wrap:balance; overflow:hidden;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; line-clamp:3}
/* `.pd` NE PORTE PLUS UN NOM MAIS UN SIGLE (`siglesAppareils`, dans `coeur/`).
   Le nom d'appareil était tronqué 48 fois sur 48 — 100 % —, et c'est structurel :
   54 px ne tiennent aucun nom d'appareil, à aucune taille lisible. Restait à
   choisir entre un nom coupé qui promet un nom, et un sigle court qui annonce
   qu'il abrège. Le second tient toujours, il est UNIQUE dans le studio, et il
   libère la ligne qu'il n'utilise plus au profit du nom de port.
   L'ellipse reste, en filet : un `nom` de la taille d'un paragraphe, écrit à la
   main dans le JSON, sortirait par la fenêtre du budget en caractères. */
.point .pd{font:var(--t-xs) var(--mono); color:var(--txt2); line-height:1.2;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.point .pd b{color:var(--txt3); font-weight:400}
/* P6 — `.point .empty` a été retiré : plus aucune ligne d'`app.js` n'émet
   cette classe (le point libre ne porte plus qu'un `.jack`), et ses deux règles
   — celle-ci et `.point.curseur .empty` — décrivaient un élément qui n'existe
   plus. Du CSS mort dans une feuille qu'on relit pour savoir ce qui est
   dessiné. */
/* ---------- LE SENS INVERSE : LES POINTS QUI PEUVENT RECEVOIR ----------

   LA RÈGLE D'AVANT ÉTAIT `.bay.arming .point` — les 96 points d'une baie
   compatible teintés d'orange. Elle répondait à « QUELLE BAIE peut recevoir
   ça ? », et laissait croire que tous ses points le pouvaient. La moitié ne le
   peut pas : la rangée A porte les sorties, la rangée B les entrées, et un point
   occupé demanderait un remplacement avant d'accepter quoi que ce soit. Sur le
   studio réel, un XLR en main invitait 24 points là où 11 seulement l'attendent.

   La teinte est donc portée par `.recevable`, calculée par `pointsRecevables` —
   le miroir exact du filtre que la feuille applique dans l'autre sens. La baie
   reste identifiée, mais par son propre corps (`.bay.arming`) et par le rouge
   de celle qui refuse, pas en peignant des cases qui ne recevront rien.

   Le survol, lui, reste offert à TOUS les points de la baie armée : un point
   occupé n'est pas invité, il n'est pas interdit — le cliquer ouvre la fenêtre
   de remplacement, et un geste possible doit répondre au doigt qui le vise. */
.point.recevable{border-color:var(--acc); background:rgba(255,159,28,.10)}
.point.recevable::after{
  content:''; position:absolute; inset:2px; border-radius:var(--r-controle);
  outline:1px dashed rgba(255,159,28,.75); outline-offset:-2px; pointer-events:none;
}
.bay.arming .point:hover{border-color:var(--acc); background:rgba(255,159,28,.12)}
.bay.arming .point.recevable:hover{background:rgba(255,159,28,.2)}

/* ---------- CE QU'ON TIENT EN MAIN, AU CLIC ----------
   Un curseur armé qu'on ne voit pas est un piège : le clic suivant écrit
   ailleurs qu'on ne croit. `.armee` est la marque de « pris en main par un
   clic », et elle est écrite en PLEIN — accent, fond, contour — là où
   `.dragging` et `.saisie` effacent à 35 %. Ce n'est pas une nuance de goût :
   un glisser montre ce qu'il tient au bout du curseur, donc la source doit
   s'estomper ; un clic ne montre rien nulle part, donc la source EST l'unique
   image de ce qu'on tient. Même patron que `.file-item.armee`, qui dit déjà la
   même chose pour une intention.

   `arming-col` invite les COLONNES quand c'est une colonne qu'on tient, en
   pointillé — le trait d'attente de cette feuille — là où `arming` invite les
   points. Les deux ne s'allument jamais ensemble : `lacherLaMain()` garantit un
   seul curseur armé à la fois. */
.point.armee{
  border-color:var(--acc); background:rgba(255,159,28,.16);
  box-shadow:0 0 0 1px var(--acc) inset;
}
.col.armee{outline:2px solid var(--acc); outline-offset:-1px; border-radius:var(--r-controle)}
.col.armee .num[data-colonne]{background:var(--acc); color:#0e1116}
.bay.arming-col .col{outline:1px dashed rgba(255,159,28,.45); outline-offset:-1px; border-radius:var(--r-controle)}
.bay.arming-col .col:hover{outline:1px solid var(--acc)}
.bay.arming-col .col.armee{outline:2px solid var(--acc)}
.mode{
  height:15px; border-radius:var(--r-pastille); border:1px solid transparent; background:rgba(255,255,255,.035);
  cursor:pointer; display:flex; align-items:center; justify-content:center;
  font:var(--t-xs) var(--mono); color:var(--txt3); overflow:hidden; transition:background .12s, color .12s;
}
.mode:hover{background:var(--bg3); border-color:var(--acc); color:var(--acc)}
/* F5 — UNE COULEUR, PAS UN VOILE. Mesuré : `opacity:.55` sur `--txt3` donnait
   2,31:1 sur le fond de baie, très en dessous des 4,5:1 d'un texte. Or ce n'est
   pas un cas rare : c'est le mode de CHAQUE colonne de la baie XLR de Julien,
   bloquée en « ouvert » par le §3 — vingt-quatre libellés illisibles, dans la
   vue qui est le sujet de l'application.

   Le voile éteignait le texte ET le fond, donc il ne pouvait pas dire « figé »
   sans devenir illisible. On rend donc au texte sa couleur pleine (`--txt3`,
   5,5:1 sur le fond de baie) et on retire l'apparence de BOUTON — plus de fond,
   plus de bordure au survol. Ce qui reste se lit comme une étiquette, ce qu'un
   mode figé est : « un contrôle qui ne peut pas changer n'en est pas un » est
   déjà la règle de la case à cocher de port désactivée. La distinction d'avec un
   mode réglable est donc portée par la forme, qui la porte mieux, et plus par
   l'opacité, qui la portait au prix de la lisibilité. */
.mode.fige{cursor:default; color:var(--txt3); background:none; border-color:transparent}
.mode.fige:hover{background:none; border-color:transparent; color:var(--txt3)}
.mode.mseminormal{color:var(--acc2)}
.mode.mnormal{color:var(--acc2); opacity:.75}
.mode.mmult{color:var(--s-numerique)}
/* B3 + F7 — LE PANNEAU D'AIDE SE POSE SOUS LA BARRE, ET IL DÉFILE.

   Deux défauts, une seule cause : deux cotes écrites en dur devant une barre
   dont la hauteur varie.

   · `top:48px` était la hauteur de la barre d'un jour, ni multipliée par `--fs`
     ni reprise dans les deux `@media`. À l'échelle 1,3 la barre fait 66 px et le
     panneau se posait PAR-DESSUS ; sous 820 px elle en fait 109, et le panneau
     couvrait les onglets de vue — or l'aide reste atteignable au doigt, repliée
     dans `⋯`. `--h-barre` est mesurée et publiée par `publierHauteurBarre()`
     (app.js) : une seule vérité, valable aux trois échelles et aux deux mises en
     page. Le repli à 57 px est la valeur de bureau, pour l'instant qui précède
     la première mesure.
   · Ni `max-height` ni `overflow`, contrairement à `.detail-pop` et `.menu-pop`
     qui en ont : à 1,3 le panneau dépasse 650 px et ses trois dernières
     rubriques — dont « Repères de lecture », la seule qui explique le code
     couleur — sortaient de l'écran sans qu'aucune barre ne dise qu'il y avait
     autre chose. `dvh` d'abord pour les navigateurs mobiles dont la barre
     d'outils rétracte le viewport ; `vh` reste comme repli. */
.aide-pop{
  position:fixed; top:calc(var(--h-barre,57px) + 8px); right:16px; z-index:45;
  width:min(520px,92vw); padding:12px 14px; background:var(--bg2);
  border:1px solid var(--line); border-radius:var(--r-carte); box-shadow:0 16px 40px rgba(0,0,0,.55);
  display:flex; flex-direction:column; gap:6px; font-size:var(--t-l); color:var(--txt3);
  max-height:calc(100vh - var(--h-barre,57px) - 24px);
  max-height:calc(100dvh - var(--h-barre,57px) - 24px);
  overflow-y:auto;
}
.aide-pop{color:var(--txt2)}
.aide-pop h4{margin:4px 0 2px; font:var(--t-s) var(--mono); text-transform:uppercase; letter-spacing:1px; color:var(--txt2)}
.aide-pop h4:first-child{margin-top:0}
.aide-pop > span{display:flex; align-items:flex-start; gap:8px; line-height:1.45}
.aide-pop .t{flex:1; min-width:0}
.aide-pop b{color:var(--txt2); font-weight:600}
.aide-pop em{font-style:italic}
.aide-pop .rep{width:14px; height:14px; flex:none; border-radius:var(--r-pastille); margin-top:2px}
.aide-pop .m{margin-top:7px}
.aide-pop .rep.lis{border-left:3px solid var(--s-instrument); background:#171e26}
.aide-pop .rep.prise{border-radius:var(--r-cercle); width:9px; height:9px; background:var(--acc); box-shadow:0 0 6px var(--acc)}
.aide-pop .rep.rang{background:linear-gradient(180deg,#2f3a47 50%,#1a212a 50%)}
.m{width:14px;height:3px;border-radius:var(--r-pastille);display:inline-block;background:var(--acc2)}
.m.semi{background:linear-gradient(90deg,var(--acc2) 60%,transparent 60%)}
.m.ouvert{background:#3a4552}
.m.mult{background:var(--s-numerique)}

/* ---------- cartes du bandeau ---------- */
.card{background:var(--bg3); border:1px solid var(--line); border-radius:var(--r-carte); padding:11px}
.card h3{margin:0 0 8px; font-size:var(--t-m); text-transform:uppercase; letter-spacing:1px; color:var(--txt2)}
.pill{background:transparent; padding:0; display:inline-flex; gap:7px; font:var(--t-s) var(--mono)}
.pill .err{color:var(--err)} .pill .warn{color:var(--warn)} .pill .info{color:var(--acc2)}
.kv{display:flex; justify-content:space-between; gap:8px; font-size:var(--t-l); padding:3px 0; border-bottom:1px solid rgba(255,255,255,.04)}
.kv:last-child{border:0}
.kv span:first-child{color:var(--txt3)}
.sim{margin-top:8px; padding:8px; background:var(--bg2); border-radius:var(--r-controle); border-left:2px solid var(--acc); font-size:var(--t-l); line-height:1.5; color:var(--txt2)}
.sim b{color:var(--txt)}
.conf{margin-top:8px; padding:6px 8px; background:var(--bg2); border-radius:var(--r-controle); border-left:2px solid var(--warn)}
.conf.err{border-left-color:var(--err)}
.conf .diag{border:0; padding:3px 0}
.empty-card{color:var(--txt3); font-size:var(--t-l); text-align:center; padding:14px 0}
/* Dans l'inventaire, la grille coule en colonnes : sans cette règle la carte
   d'état vide se répartit dans le flux comme du texte, et son bouton part
   flotter dans la colonne d'à côté. */
.grille .empty-card{display:inline-block; width:100%; break-inside:avoid}
.grille .empty-card .btn{margin-top:8px}
.diag{display:flex; gap:8px; padding:6px 0; border-bottom:1px solid rgba(255,255,255,.05); font-size:var(--t-l); line-height:1.45}
.diag:last-child{border:0}
.diag .ic{flex:none; width:14px; text-align:center}
.diag.err .ic{color:var(--err)} .diag.warn .ic{color:var(--warn)} .diag.info .ic{color:var(--acc2)}
.diag .where{font:var(--t-s) var(--mono); color:var(--acc); cursor:pointer; white-space:nowrap}
/* LE REPÈRE DE PORTÉE — pourquoi une ligne qui nomme un AUTRE appareil paraît
   sur la fiche de celui-ci. C'est la moitié qu'il ne fallait pas perdre : le
   diagnostic du Clarett sous le POD sélectionné est lié au POD, c'est leur
   liaison, et le supprimer aurait été aussi faux que le noyer. On le garde, et
   on l'annonce. Discret et non cliquable : il explique, il n'agit pas. */
.diag .via{
  font:var(--t-xs) var(--mono); font-style:normal; color:var(--txt3);
  border:1px solid var(--line); border-radius:var(--r-pastille);
  padding:0 5px; margin-left:6px; white-space:nowrap;
}
/* Le titre de carte dit la portée quand elle n'est plus celle du studio. Même
   place et même poids que le compteur de « À placer » — `.drawer .card h3` est
   déjà en `space-between`, l'`<em>` s'y range tout seul. */
/* LE BLOC D'ACTIONS D'APPAREIL, hors carte d'inventaire — tiroir et popover de
   point. Il porte le nom parce que son contexte ne le porte pas : sur une carte,
   l'en-tête juste au-dessus le dit ; ici l'objet sélectionné est un point ou une
   boîte, et « Retirer » doit dire de quoi.

   Le liseré gauche est le jeton de couleur d'appareil du §3 — le bord plein,
   celui de la carte d'inventaire, de la file d'attente et du schéma —, et pas un
   aplat : une seule langue pour une seule chose.

   `.acts` est repris tel quel : mêmes icônes, mêmes écarts, même `.ia.danger`
   détaché sur la gauche, même bloc tactile de `@media (pointer:coarse)`. C'est
   le point de tout l'exercice — si cette règle-ci redéfinissait l'espacement,
   les deux surfaces auraient recommencé à diverger le jour même. */
.acts-app{
  display:flex; align-items:center; gap:8px; margin-top:4px;
  padding:5px 7px; background:var(--bg2); border-radius:var(--r-controle);
  border-left:3px solid var(--it, var(--line)); min-width:0;
}
.acts-app .acts-nom{
  flex:1; min-width:0; font-size:var(--t-l); font-weight:600; color:var(--txt);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.acts-app .acts{display:flex; gap:1px; flex:none}
.acts-app .acts .ia.danger{margin-left:5px}
.acts-app .acts .ia.pose{color:var(--acc)}
@media (pointer:coarse){ .acts-app .acts{gap:6px} }
.card h3 .portee{
  font:var(--t-xs) var(--mono); font-style:normal; font-weight:400; color:var(--acc2);
  text-transform:none; letter-spacing:0; white-space:nowrap; cursor:help;
}
.bar{height:5px; background:var(--bg2); border-radius:var(--r-pastille); overflow:hidden; margin:6px 0 4px}
.bar i{display:block; height:100%; background:var(--acc); border-radius:var(--r-pastille)}
.reco{font-size:var(--t-l); color:var(--txt2); line-height:1.5; margin-top:6px; padding-top:6px; border-top:1px solid rgba(255,255,255,.06)}
.reco b{color:var(--acc)}

/* ---------- modale ---------- */
.modal{position:fixed; inset:0; background:rgba(6,9,13,.72); display:flex; align-items:center; justify-content:center; z-index:50; padding:24px}
.modal[hidden]{display:none}
.modal-box{background:var(--bg2); border:1px solid var(--line); border-radius:var(--r-gelule); width:min(940px,100%); max-height:88vh; display:flex; flex-direction:column; box-shadow:0 24px 60px rgba(0,0,0,.6)}
.modal-head{display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid var(--line)}
.modal-head h2{margin:0; font-size:var(--t-xl); text-transform:uppercase; letter-spacing:1px; color:var(--txt2)}
.modal-body{padding:16px; overflow-y:auto; flex:1; min-height:0}
.modal-foot{display:flex; align-items:center; gap:8px; padding:11px 16px; border-top:1px solid var(--line)}
/* F1 — L'AVERTISSEMENT D'ABANDON. Au PIED de la fenêtre, à côté des boutons,
   parce que c'est là que le regard est quand on décide de sortir — et parce
   qu'un message posé dans le corps aurait défilé hors de vue sur un éditeur de
   52 ports. `--warn` et non `--err` : rien n'est cassé, on prévient d'un coût.
   `min-width:0` + ellipse : le pied est un flex qui porte jusqu'à cinq boutons,
   et c'est le message qui cède, jamais une commande. */
.modal-avert{
  color:var(--warn); font-size:var(--t-m); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.btn.mini{padding:3px 8px; font-size:var(--t-m)}
.btn.primary{background:var(--acc); border-color:var(--acc); color:#161a1f; font-weight:600}
.btn.primary:hover{filter:brightness(1.1); color:#161a1f}
.btn.danger{color:var(--err); border-color:rgba(239,71,111,.4)}
.btn.danger:hover{background:var(--err); color:#fff; border-color:var(--err)}
/* « Supprimer définitivement » reste éteint tant que l'adresse retapée ne
   correspond pas, et il doit LE MONTRER : un bouton rouge d'aspect normal qui
   ne répond pas au clic se lit comme une panne, pas comme une condition. Règle
   bornée à `.danger` — le seul `.btn` non `.discret` que le projet désactive —
   plutôt qu'un `.btn:disabled` général, qui repeindrait des boutons dont
   personne n'a examiné l'état. */
.btn.danger:disabled{color:var(--txt4); border-color:var(--line); cursor:default}
.btn.danger:disabled:hover{background:none; color:var(--txt4); border-color:var(--line)}
/* `.btn.sourd` (#9, second morceau, #14) — MÊME RÈGLE QUE `.menu-item.sourd` et
   `.sp-port.sourd`, portée ici sur un bouton de MODALE : « Supprimer cette
   config… » dans les Préférences ne peut aboutir qu'à partir de deux
   configurations, et un bouton `disabled` ne délivre ni `mouseover` ni
   `focusin` — l'infobulle qui explique pourquoi ne s'ouvrirait jamais.
   Estompé plutôt que désactivé, il reste survolable. */
.btn.sourd{opacity:.5}
.btn.danger.sourd:hover{background:none; color:var(--err); border-color:rgba(239,71,111,.4)}
.note{font-size:var(--t-l); color:var(--txt3); line-height:1.5; margin:0 0 12px; padding:8px 10px; background:var(--bg3); border-left:2px solid var(--acc2); border-radius:0 var(--r-controle) var(--r-controle) 0}
.note b{color:var(--txt2)}
.tabs{display:flex; gap:4px; margin-bottom:12px; border-bottom:1px solid var(--line)}
.tab{background:none; border:0; border-bottom:2px solid transparent; color:var(--txt3); padding:7px 12px; font-size:var(--t-l); cursor:pointer}
.tab:hover{color:var(--txt2)}
.tab.on{color:var(--acc); border-bottom-color:var(--acc)}
.rech{width:100%; margin-bottom:10px}
.rech::placeholder{color:var(--txt4)}
.mlist{display:flex; flex-direction:column; gap:4px}
.mrow{display:flex; align-items:center; gap:10px; padding:8px 10px; background:var(--bg3); border:1px solid var(--line); border-radius:var(--r-controle)}
.mrow:hover{border-color:var(--txt3)}
.mmeta{display:flex; flex-direction:column; gap:2px; min-width:0; flex:1}
.mmeta b{font-size:var(--t-xl)}
.mmeta span{font:var(--t-s) var(--mono); color:var(--txt3)}
.mmeta em{color:var(--acc); font-style:normal}
/* Le SEUL `font-family:inherit` qui reste, et il n'est pas une rustine de
   contrôle : `.mnote` est un <span>, donc pris par `.mmeta span` juste au-dessus
   qui l'aurait mis en mono. `inherit` le ramène à la police de la carte. Ne pas
   le retirer avec les six autres — il ne fait pas le même travail. */
.mmeta .mnote{font-family:inherit; font-size:var(--t-m); color:var(--txt3); font-style:italic}
.mrow .acts{display:flex; align-items:center; gap:3px; flex:none}
/* `.mmeta` est une colonne flex : un <b> nu y devient un flex item et la
   pastille tomberait à la ligne (piège du §8). Le titre et son étage forment
   donc UNE ligne, qui est elle-même l'item. */
.mmeta .ligne-titre{display:flex; align-items:baseline; gap:6px; min-width:0}
.mmeta .ligne-titre b{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* L'étage d'un modèle. Discret par construction : c'est un repère de lecture,
   pas un avertissement — rien n'est en faute quand un modèle vient de la
   générale, c'est même le cas ordinaire. */
.etage{flex:none; font:var(--t-xs) var(--mono); text-transform:uppercase; letter-spacing:.06em;
       padding:1px 5px; border-radius:var(--r-pastille); border:1px solid var(--line); color:var(--txt3)}
.etage.perso{border-color:rgba(255,159,28,.4); color:var(--acc)}

/* Le repère de divergence, dans l'inventaire. DISCRET, et c'est une décision :
   rien n'est en faute quand un modèle a changé, rien n'est cassé, et aucune
   action n'est exigée — un rouge d'erreur mentirait sur l'urgence. */
.ia.diverge{color:var(--acc2)}
.ia.diverge:hover{color:var(--acc2); border-color:var(--acc2)}

/* Le diff port par port de la modale d'adoption. */
.dlist{display:flex; flex-direction:column; gap:3px; margin:10px 0}
.dport{display:flex; align-items:baseline; gap:8px; padding:5px 8px; border-radius:var(--r-controle);
       background:var(--bg2); border-left:2px solid var(--line)}
.dport b{font-size:var(--t-l); flex:none}
.dport span{font:var(--t-s) var(--mono); color:var(--txt3)}
.dport em{margin-left:auto; font:var(--t-s) var(--mono); font-style:normal; color:var(--txt3); text-align:right}
.dport .dsigne{flex:none; width:10px; font:var(--t-l) var(--mono); text-align:center}
.dport.add{border-left-color:var(--ok)} .dport.add .dsigne{color:var(--ok)}
.dport.del{border-left-color:var(--err)} .dport.del .dsigne{color:var(--err)}
.dport.mod{border-left-color:var(--warn)} .dport.mod .dsigne{color:var(--warn)}
.fgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:10px; margin-bottom:14px}
.fgrid label,label.wide{display:flex; flex-direction:column; gap:4px}
label.wide{margin-top:12px}
.fgrid label span,label.wide span{font:var(--t-s) var(--mono); color:var(--txt3); text-transform:uppercase; letter-spacing:.5px}
.fgrid input,.fgrid select,label.wide input{width:100%}
.modes-pick{display:flex; align-items:center; gap:14px; flex-wrap:wrap; padding:10px; background:var(--bg3); border-radius:var(--r-controle); margin-bottom:6px}
.modes-pick > span{font:var(--t-s) var(--mono); color:var(--txt3); text-transform:uppercase}

/* éditeur de ports */
/* ⚠ LE DÉFILEMENT HORIZONTAL EST GÉNÉRAL, PLUS SEULEMENT SOUS 820 px (#32).
   Il vivait dans un `@media (max-width:820px)`, c'est-à-dire conditionné à la
   largeur de la FENÊTRE — or le débordement dépend de la largeur du CONTENEUR,
   et les deux ne se suivent pas. Mesuré : à 830 px de fenêtre, juste au-dessus
   du seuil, la modale ne laisse que 746 px à `.pe`, qui restait alors en
   `overflow:hidden` — la grille y était rognée sans barre de défilement, et
   sans recours. Le `y` reste caché : c'est lui qui tient les coins arrondis. */
/* ⚠ UNE GRILLE, ET NON UN BLOC (#48). Les rangées portent `min-width:max-content`
   plus bas : elles font la largeur du CONTENU, pendant qu'un enfant en bloc fait
   celle du CONTENEUR. La carte d'état vide restait donc à la largeur visible, et
   défiler vers la droite laissait le fond de l'en-tête flotter sur le fond nu de
   la modale — le défaut « le tableau défilé se dépeuple » que #32/#35 a fermé
   pour `.pe-r` et laissé ouvert pour l'état vide.

   ⚠ RECOPIER `min-width:max-content` SUR LA CARTE NE MARCHE PAS, et c'est mesuré,
   pas déduit : son contenu est court, `max-content` la laisse à 588 quand
   l'en-tête en fait 958. Ce qu'il faut est la largeur du PLUS LARGE DE SES
   FRÈRES — qu'un enfant en bloc ne peut pas connaître. Une grille à une colonne
   la donne : la colonne prend le `max-content` du plus large, et tous les enfants
   s'y étirent.

   ⚠ ET LE CAS ORDINAIRE NE BOUGE PAS — mesuré au navigateur avec deux rangées :
   largeurs et hauteur identiques avant et après. Un remède qui répare l'état vide
   en déplaçant le cas normal serait pire que le défaut. */
.pe{border:1px solid var(--line); border-radius:var(--r-controle);
    display:grid; overflow-x:auto; overflow-y:hidden}
/* La première colonne est la POIGNÉE de réordonnancement — d'où le décalage de
   `nth-child(n+5)` à `n+6` juste dessous. */
/* L'avant-dernière colonne — 92 px — est le RÔLE MIDI (`selectDeRoleMidi`).
   Elle est là pour toutes les rangées, y compris celles qui n'affichent qu'un
   tiret : la grille est à colonnes fixes, une cellule omise décalerait la
   rangée entière et Sym / 48 V iraient se ranger sous « Coupure ». */
/* ⚠ `minmax(140px, 1fr)` ET NON `1fr` — LE NOM DU PORT NE PEUT PLUS S'ÉCRASER.
   Les neuf autres colonnes sont en pixels fixes : elles pèsent 680 px avec les
   gouttières et le remplissage, et le `1fr` ne recevait que ce qui restait.
   Mesuré sur un sommateur (dix rangées) dans une fenêtre de 592 px : la grille
   calculait `18px 14px 84px …` — QUATORZE pixels pour la colonne qui identifie
   la rangée. Les valeurs étaient là, aucune n'était lisible, et c'est justement
   celle qu'on relit pour corriger d'après sa face arrière.
   Un plancher plutôt qu'un repli des colonnes secondaires : la décision de
   défiler plutôt que d'empiler est déjà prise plus bas, et elle tient.

   ⚠ CHIFFRES REPRIS LE 6 SEPTEMBRE 2026 — la colonne « Porte » (#101) ajoute une
   piste de 132 px, et deux mesures écrites ici devenaient fausses SANS RIEN
   CASSER : un commentaire chiffré ne rougit pas. Les autres colonnes pesaient
   680 px (610 + 54 de gouttières + 16 de marge), elles en pèsent 818 ; le seuil
   de défilement passe de 820 à 958 px. La fenêtre 830-958 px, que le paragraphe
   suivant présente comme réparée, redéfile donc horizontalement — c'est un
   défaut connu et assumé, pas une régression cachée.

   ⚠ `min-width:max-content` VA AVEC, sans quoi le tableau défilé se dépeuple.
   Une rangée est un bloc : sa boîte reste large comme le CONTENEUR pendant que
   ses pistes débordent. Défilé vers la droite, on sortait donc du fond de
   l'en-tête, du zébrage une rangée sur deux et des filets — la colonne MIDI et
   le bouton de suppression flottaient sur le fond nu de la modale. Le défaut
   existait déjà sous 820 px — 958 depuis #101, voir ci-dessus ; sans cette ligne
   le lot en aurait fait le chemin NORMAL entre 830 et 1000 px, où le débordement
   devient la règle.

   ⚠ ET `width:0; min-width:100%` SUR LE CHAMP DU NOM N'EST PAS UNE COQUETTERIE.
   Sans lui, en-tête et rangées ne tombent pas sur la même largeur : un `input`
   apporte sa largeur intrinsèque (une vingtaine de signes) au calcul de
   `max-content`, l'en-tête non. Mesuré : en-tête 820, rangées 829 — neuf pixels
   de DÉSALIGNEMENT entre les colonnes de l'en-tête et celles des rangées, soit
   pire que le défaut qu'on venait fermer. `width:0` retire l'`input` du calcul
   intrinsèque ; `min-width:100%` le fait remplir sa case une fois la piste
   connue. Vérifié à 375, 830 et 1280 px : les deux largeurs sont égales aux
   trois. */
.pe-h,.pe-r{display:grid; grid-template-columns:18px minmax(140px, 1fr) 84px 100px 100px 132px 34px 34px 118px 92px 30px; gap:6px; align-items:center; padding:5px 8px; min-width:max-content}
.pe-h{background:var(--bg3); font:var(--t-s) var(--mono); color:var(--txt3); text-transform:uppercase; letter-spacing:.5px}
.pe-h span:nth-child(n+6){text-align:center}
.pe-r{border-top:1px solid var(--line)}
.pe-r:nth-child(odd){background:rgba(255,255,255,.015)}

/* La poignée. Discrète au repos — c'est une liste qu'on vient LIRE la plupart du
   temps —, franche dès que la rangée est survolée : le repère apparaît là où la
   main est déjà, sans qu'il faille le chercher. */
.pe-grip{
  background:none; border:0; padding:0; cursor:grab; line-height:1;
  color:var(--txt4); font-size:var(--t-m);
  transition:color .12s;
}
.pe-r:hover .pe-grip{color:var(--txt2)}
.pe-grip:hover{color:var(--acc)}
.pe-grip:active{cursor:grabbing}
.pe-grip:focus-visible{outline:2px solid var(--acc); outline-offset:2px; border-radius:var(--r-pastille); color:var(--acc)}

/* La rangée qu'on tient s'efface, comme `.point.dragging` et `.col.saisie` */
.pe-r.saisie{opacity:.35}
/* OÙ ÇA TOMBE — un trait sur le bord franchi, et non un contour : entre deux
   rangées collées, un contour ne dirait pas de quel côté le port va se poser. */
.pe-r.avant{box-shadow:inset 0 2px 0 var(--acc)}
.pe-r.apres{box-shadow:inset 0 -2px 0 var(--acc)}
.pe-r input[type=checkbox]{justify-self:center}
.pe-r .pe-case{display:flex; justify-content:center; align-items:center}
/* Éteinte, elle reste LISIBLE : on doit voir qu'il y a une case, et que
   c'est le connecteur choisi qui l'interdit — pas qu'elle a disparu. */
.pe-r .pe-case input:disabled{opacity:.3; cursor:not-allowed}
/* la colonne « coupure » quand elle n'offre aucun choix : un tiret ou la clé
   du modèle, au même endroit que le sélecteur, pour que la grille ne bouge pas */
.pe-r .pe-cp{text-align:center; font:var(--t-s) var(--mono); color:var(--txt3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.pe-r input[type=text],.pe-r select{width:100%; padding:4px 6px; font-size:var(--t-l)}
/* voir le bloc de la grille : `width:0` retire le champ du calcul de
   `max-content`, `min-width:100%` le fait remplir sa piste une fois celle-ci
   résolue. C'est ce qui garde l'en-tête et les rangées à la MÊME largeur. */
.pe-r input[type=text]{width:0; min-width:100%}
/* ⚠ `flex-wrap` N'EST PAS DÉCORATIF : la barre porte désormais quatre gestes,
   dont un groupe de trois contrôles. Sans le retour à la ligne, le dernier —
   « Compléter l'autre sens », c'est-à-dire celui qui commande tout ce lot —
   débordait de la largeur de la modale.
   ⚠ ET IL NE SE RÉGLERAIT PAS COMME LA GRILLE JUSTE AU-DESSUS : `.pe-add` vit
   HORS de `.pe`, il n'a donc ni son écrêtage vertical ni le défilement
   horizontal que `.pe` porte désormais dans sa règle de base (#32 : la media
   query de 820 px qui le donnait a disparu, sa condition était fausse). Le débordement partait ici dans le
   défilement du corps de modale, qui poussait toute la fenêtre de côté au lieu
   de rétrécir la barre. Le retour à la ligne est le seul remède local. */
.pe-add{display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; align-items:center}
/* Le lot se lit comme UNE PHRASE — « Ajouter 8 sorties numérotées » — et non
   comme trois réglages côte à côte : d'où le fond commun, qui dit que les trois
   contrôles n'en font qu'un. */
/* ⚠ LE GROUPE PASSE À LA LIGNE LUI AUSSI. `.pe-add` seul ne suffisait pas :
   mesuré à 375 px de large, le groupe faisait 318 px pour 293 disponibles, et
   « numérotées » était COUPÉ — `.pe-add` vit hors de `.pe`, il ne bénéficie donc
   pas du défilement horizontal que `.pe` donne à la grille. */
.pe-lot{display:inline-flex; flex-wrap:wrap; align-items:center; gap:6px; padding:3px 8px 3px 3px;
  background:var(--bg3); border-radius:var(--r-pastille)}
.pe-lot input[type=number]{width:56px; padding:4px 6px; font-size:var(--t-l); text-align:right}
.pe-lot select{padding:4px 6px; font-size:var(--t-l)}
.pe-mot{font-size:var(--t-m); color:var(--txt2)}
/* Le compte-rendu du dernier geste de lot. Il vit DANS la fenêtre parce que la
   barre du haut, où `flag()` écrit, est recouverte par la modale. */
.pe-dit{margin-top:6px}
/* ⚠ LA GRILLE NE TIENT PAS SUR UN TÉLÉPHONE, ET ELLE NE TENAIT DÉJÀ PAS. Ses
   colonnes sont en pixels FIXES : sous cette largeur, les dernières n'étaient
   pas rétrécies, elles étaient COUPÉES, bouton « supprimer ce port » compris.
   On défile plutôt qu'on n'empile : cette grille est un TABLEAU — dix champs
   par port, comparés d'une rangée à l'autre —, et un tableau se défile. La
   règle des 44 px du §14.2 porte sur la CIBLE, pas sur la largeur du tableau
   qui la contient.
   La déclaration qui vivait ici est remontée dans `.pe` : le remède était bon,
   sa CONDITION était fausse — voir le commentaire de la règle de base. */

/* ---------- bascule de vue + schéma ---------- */
.vues{display:flex; gap:2px; padding:2px; background:var(--bg3); border:1px solid var(--line); border-radius:var(--r-controle)}
/* `--t-l` ET NON `--t-m` : les onglets de vue étaient à 11 px face à des boutons
   d'action à 12 px. Le seul geste qui change CE QU'ON REGARDE était écrit plus
   petit que celui qui déplace un câble. Ils sont désormais au même échelon —
   `.vue` garde son gras et son interlettrage, qui la posent au-dessus à taille
   égale. ⚠ `.seg` (« Audio · MIDI ») ne suit PAS : ses cotes sont celles de
   `.btn.mini`, ses voisines de barre, et le commentaire plus bas le dit déjà. */
.vue{background:none; border:0; color:var(--txt3); padding:4px 12px; font-size:var(--t-l); cursor:pointer; border-radius:var(--r-controle); letter-spacing:.4px; font-weight:600}
.vue:hover{color:var(--txt2); background:rgba(255,255,255,.04)}
.vue.on{color:#161a1f; background:var(--acc)}

/* `padding` RETIRÉ AVEC #9 (second morceau) : cette barre vivait en tête du
   contenu qui défile et réclamait sa propre marge basse ; elle vit maintenant
   dans `.toolbar`, dont le `padding` fait déjà ce travail — `#vueSchema
   .schema-bar` plus bas dans cette feuille est retirée avec elle, sa
   condition n'existant plus. */
.schema-bar{display:flex; align-items:center; gap:12px; flex-wrap:wrap}
.schema-bar .sep{width:1px; height:14px; background:var(--line)}

.schema-bar .aide{font-size:var(--t-m); color:var(--txt3)}
.schema-bar .aide b{color:var(--txt2)}

/* LE SEGMENTÉ « AUDIO · MIDI » — le réseau que le schéma dessine.
   Même peinture que `.vues` / `.vue`, la bascule Baies ↔ Schéma de la barre du
   haut : ce sont deux exclusifs à deux positions, et l'appli n'a aucune raison
   d'avoir deux vocabulaires visuels pour un même geste. Les cotes sont celles
   de `.btn.mini`, ses voisines de barre, et non celles de `.vue`, qui vit dans
   un bandeau plus haut. */
.segmente{display:flex; gap:2px; padding:2px; background:var(--bg3); border:1px solid var(--line); border-radius:var(--r-controle)}
.seg{background:none; border:0; color:var(--txt3); padding:3px 10px; font-size:var(--t-m); cursor:pointer;
  border-radius:var(--r-controle); letter-spacing:.4px; font-weight:600}
.seg:hover{color:var(--txt2); background:rgba(255,255,255,.04)}
.seg.on{color:#161a1f; background:var(--acc)}
.seg:focus-visible{outline:2px solid var(--acc); outline-offset:2px}
/* Le zoom du schéma — DÉMÉNAGÉ DANS LA BANDE DU BAS AVEC #9, mais la règle
   reste générique et vit ici par simple habitude de rangement : rien dans ce
   sélecteur ne dépend de `.schema-bar`. Il porte `--fs` comme tout le chrome de
   l'appli : c'est un contrôle, pas le dessin. Ce qu'il commande, lui, en est
   indépendant. */
.zoom{display:flex; align-items:center; gap:4px}
.zoom em{font:var(--t-s) var(--mono); font-style:normal; color:var(--txt3);
  min-width:44px; text-align:center}
.btn.mini{padding:3px 9px; font-size:var(--t-m)}

/* LA PISTE DES INTITULÉS D'ÉTAPE — hors du SVG depuis le §14.3 point 4.
   `overflow:hidden` sur le cadre, `translateX` sur la piste : le seul montage
   qui suive le défilement HORIZONTAL du canevas sans suivre le vertical. Les
   intitulés sont en absolu, posés sur les `col.x` que `disposer()` a calculés,
   multipliés par le zoom du schéma — c'est ce qui les garde alignés sur les
   colonnes à tous les crans. */
.schema-etapes{flex:none; position:relative; overflow:hidden; height:16px; margin-bottom:4px}
.etapes-piste{position:absolute; inset:0; will-change:transform}
.etape-t{
  position:absolute; top:0; color:var(--txt3); font-size:var(--t-s);
  font-family:var(--mono); text-transform:uppercase; letter-spacing:1px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
#schema{min-width:100%}
#schema svg{display:block; max-width:none}
/* LES TEXTES DU SCHÉMA NE PORTENT PAS `--fs`, ET C'EST VOLONTAIRE. Le schéma est
   un SVG dont la géométrie — largeur des boîtes, hauteur d'une ligne de port,
   position des ancres — est calculée en JavaScript (`disposer()`). Grossir la
   police sans grossir les boîtes ferait déborder les noms ; recalculer toute la
   géométrie à chaque cran serait une arithmétique de plus à tenir juste.
   L'échelle est donc appliquée là où le SVG la prend pour rien : `renderSchema`
   multiplie les attributs `width`/`height` en gardant le `viewBox` intact, et le
   navigateur agrandit le dessin ENTIER — texte, cadres et traits ensemble, dans
   les mêmes proportions. Les tailles ci-dessous sont donc des tailles internes
   au dessin, pas des tailles à l'écran.

   ⚠ ELLES SONT DONC AUSSI HORS DE L'ÉCHELLE TYPOGRAPHIQUE (`--t-*`), et il ne
   faut PAS les y ramener : ces variables portent `--fs`, or c'est exactement ce
   que ce bloc refuse. Les y brancher rendrait au schéma le débordement que le
   commentaire ci-dessus décrit. Sept déclarations sont dans ce cas — les deux
   textes d'un nœud, les deux d'un port, l'étiquette d'un lien, le libellé
   d'ajout et celui du repli — et elles y restent délibérément. */

.noeud .cadre{fill:var(--bg3); stroke:var(--line); stroke-width:1}
/* Le jeton de couleur, UN SEUL POUR TOUTE L'APPLI : le bord plein à pleine
   saturation, comme `.dev` (inventaire), `.file-item` (file d'attente) et
   `.puce` (appareils posés). Le pourquoi est dans `renderSchema`, au-dessus
   du tracé ; l'essentiel tient en une phrase : la couleur DÉSIGNE l'appareil,
   elle ne le remplit pas. */
.noeud .liseret{fill:none; stroke:var(--it); stroke-width:3; vector-effect:non-scaling-stroke}
.noeud .logement{fill:var(--bg2); stroke:var(--line); stroke-width:1}
.noeud:hover .logement{stroke:#4d5b6b}
.noeud .nnom{fill:var(--txt); font-size:11px; font-weight:600}
.noeud .ncat{fill:var(--txt3); font-size:8.5px; font-family:var(--mono)}
.noeud:hover .cadre{stroke:var(--acc)}
.noeud.sel .cadre{stroke:var(--acc); stroke-width:2}
.noeud.pale{opacity:.28}
.noeud{cursor:pointer}
.pport rect{fill:transparent}
.pport:hover rect{fill:rgba(255,159,28,.14)}
.pport.enmain rect{fill:rgba(255,159,28,.3)}
.pport{cursor:crosshair}
.pport .pnom{fill:var(--txt2); font-size:9.5px}
.pport .ppt{fill:var(--acc); font-size:8px; font-family:var(--mono)}

.lien .hit{stroke:transparent; stroke-width:14; fill:none; cursor:pointer}
.lien .trait{fill:none; stroke-width:1.4; opacity:.5}
.lien.direct .trait{stroke-width:1.4; stroke-dasharray:6 4; opacity:.45}
.lien:hover .trait{opacity:1; stroke-width:2.8}
.lien.sel .trait{opacity:1; stroke-width:3.6; filter:drop-shadow(0 0 5px var(--acc))}
.lien.sel .hit{stroke:rgba(255,159,28,.12); stroke-width:18}
.lien.pale{opacity:.12}
.lien .etiq{fill:var(--txt3); font-size:8px; font-family:var(--mono)}
.lien.sel .etiq{fill:var(--acc)}

.mini-t{font:var(--t-s) var(--mono); color:var(--txt3); text-transform:uppercase; letter-spacing:.8px; margin:10px 0 3px}

[hidden]{display:none !important}

.hint .curs{color:var(--acc); font-weight:600}
.hint .lien-arret{color:var(--txt3); text-decoration:underline; cursor:pointer}
.dev-head .acts .ia.pose{color:var(--acc)}

/* ---------- cordons en façade ---------- */

.lien.cordon .trait{stroke-dasharray:2 3; stroke-width:2.8; opacity:1}

/* ---------- l'intention : déclarée, pas encore placée ---------- */

/* Pointillé FIN, à ne confondre ni avec les tirets longs du câble direct ni
   avec le pointillé épais du cordon : les trois natures physiques se lisent
   d'un coup d'oeil, et celle-ci dit qu'il n'y a encore aucun câble. */
.lien.intention .trait{stroke-dasharray:1 4; stroke-linecap:round; stroke-width:2; opacity:.55}

/* La pastille de la bascule « Baies » : elle n'existe QUE peuplée — aucun
   élément n'est émis quand la file est vide, et le bouton retrouve alors son
   gabarit exact. Même discipline que la bande « Déjà posés » et la ligne d'aide.

   ⚠ ELLE NE SE CLIQUE PLUS. Elle ouvrait le tiroir sur la file, à l'intérieur
   d'un onglet qui, lui, change de vue : deux gestes dans une cible, dont un que
   personne ne découvrait — « je n'avais même pas capté qu'on pouvait cliquer
   dessus ». Un onglet de VUE ne porte pas de seconde action. `cursor:inherit`
   et non `default` : la pastille se comporte désormais comme le reste du
   bouton, c'est-à-dire comme un morceau de l'onglet, ce qu'elle est. */
.vue em,.onglet em{font:var(--t-xs) var(--mono); font-style:normal; color:var(--acc); margin-left:5px;
        cursor:inherit; padding:1px 4px; border-radius:var(--r-carte); background:rgba(255,159,28,.12)}
/* SUR L'ONGLET ACTIF, LA PASTILLE S'INVERSE. L'onglet retenu a le fond d'accent
   (`.vue.on`) et la pastille prenait cette même couleur : un chiffre orange sur
   un fond orange, illisible exactement dans la vue où il sert le plus — celle
   des baies, où l'on place. Elle reprend donc le fond sombre du texte de
   l'onglet, qui est déjà le contraste retenu ici. */
.vue.on em,.onglet.on em{color:var(--acc); background:#161a1f}
.vue.on em:hover,.onglet.on em:hover{background:#0b0e12}

/* Une entrée de file : deux extrémités, un état, deux gestes. */
.file-item{border:1px solid var(--line); border-left:3px solid var(--it,var(--line));
  border-radius:var(--r-controle); padding:5px 6px; margin-bottom:5px; cursor:pointer}
.file-item:hover{background:rgba(255,255,255,.03)}
.file-item.armee{border-color:var(--acc); background:rgba(255,159,28,.08)}
.file-ports{display:flex; align-items:center; gap:4px; flex-wrap:wrap}
.file-ports .port{cursor:inherit}
.file-ports .fl{color:var(--txt3); font-size:var(--t-m)}
.file-bas{display:flex; align-items:center; gap:4px; margin-top:4px}
.file-bas .grow{flex:1}
.etat-file{font:var(--t-s) var(--mono); color:var(--txt3)}
.etat-file.incoherente,.etat-file.incompatible{color:var(--warn)}
.etat-file.partielle{color:var(--acc)}

/* « À CORRIGER » — même gabarit d'entrée que « À placer », et c'est voulu : ce
   sont les mêmes objets (des liaisons déclarées), lus au même endroit, avec les
   mêmes deux boutons. Ce qui les sépare est la CARTE qui les contient et la
   phrase de section, pas une seconde peinture. Deux styles pour une même chose
   auraient fait croire à deux natures d'objet, alors que la différence est la
   cause de l'attente. La note de section reste en retrait — elle explique la
   règle, elle n'est pas une entrée. */
#fileACorriger .mini-t{margin-top:2px}
#fileACorriger .mini-t + .note{margin:0 0 7px}
#fileACorriger .file-item + .mini-t{margin-top:12px}
/* `--txt3` et non `--txt4` : le nom d'un port libre est une INFORMATION — c'est
   ce qu'on lit pour choisir où brancher —, et `--txt4` est la variable du
   décoratif, à 3,44:1 sur le fond. Voir `.ajout text`, même correction. */
.pport.libre .pnom{fill:var(--txt3)}
.pport.libre .plot{opacity:.5}
.pport.libre .logement{stroke:#2b333d}
.pport.libre:hover .pnom{fill:var(--txt)}
.pport.libre:hover .plot{opacity:1}
.pport.relie .pnom{fill:var(--txt)}
.point .cord{position:absolute; bottom:1px; left:3px; font-size:var(--t-s); line-height:1; color:var(--acc)}
.point.cordon{border-left-style:dotted; border-left-width:4px}
.opt{padding:8px 10px; margin-bottom:6px; background:var(--bg3); border-radius:var(--r-controle); border-left:2px solid var(--line)}
/* `> b` et non `b` : le titre de la puce est un ENFANT DIRECT, et la règle
   attrapait aussi les gras de la description — chaque mot mis en avant y
   passait à la ligne, ce qui hachait le texte en escalier. */
.opt > b{display:block; font-size:var(--t-l); margin-bottom:2px}
.opt span{font-size:var(--t-l); color:var(--txt3); line-height:1.45}
.opt span b{color:var(--txt2); font-weight:600}
.opt.vide{opacity:.5}
.kv em.pt{font:var(--t-s) var(--mono); color:var(--acc); font-style:normal}

/* ---------- LE SÉLECTEUR DE PORT — la réserve appelée AU point ----------

   Une liste de CIBLES, pas un tableau de lecture. Chaque entrée est un
   `<button>` de 44 px au moins — le seuil Apple, celui que le §14.2 a mesuré
   comme manquant 124 fois dans cette appli —, ce qui est le seul chiffre non
   négociable de ce bloc : la feuille existe pour le doigt.

   La COULEUR et le SIGLE sont ceux du point une fois posé (`.point .pd`,
   `--sig`). Ce n'est pas une décoration : c'est la seule façon de reconnaître,
   avant de choisir, ce qui va apparaître dans la baie. Deux vues, un seul
   codage.

   Une seule COLONNE, jamais une grille : sur 375 px une grille à deux colonnes
   couperait les noms de port au tiers — c'est exactement le défaut qui a coûté
   son nom d'appareil à `.pd`. Le corps de la modale défile déjà tout seul
   (`.modal-body{overflow-y:auto}`), et la fenêtre est bornée à 88 vh. */
.sp-liste{display:flex; flex-direction:column; gap:10px; margin:4px 0 12px}
.sp-grp{display:flex; flex-direction:column; gap:3px}
.sp-grp-t{display:flex; align-items:center; flex-wrap:wrap; gap:2px 7px; min-width:0; padding:0 2px 2px}
.sp-grp-t b{font-size:var(--t-l); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.sp-cat{font:var(--t-s) var(--mono); color:var(--txt4); text-transform:uppercase; letter-spacing:.6px; flex:none}
.sp-sig{
  font:var(--t-xs) var(--mono); color:#0e1116; background:var(--it, var(--acc2));
  border-radius:var(--r-pastille); padding:1px 6px; flex:none; letter-spacing:.4px;
}
.sp-port{
  display:flex; align-items:center; gap:8px; width:100%; min-height:44px; text-align:left;
  padding:6px 10px 6px 12px; background:var(--bg3); border:1px solid var(--line);
  border-left:3px solid var(--it, var(--line)); border-radius:var(--r-controle);
  color:var(--txt); font-family:inherit; cursor:pointer;
}
.sp-port:hover{border-color:var(--acc); border-left-color:var(--it, var(--acc)); background:rgba(255,159,28,.09)}
.sp-port:focus-visible{outline:2px solid var(--acc); outline-offset:1px}
/* DEUX ÉTAGES DANS LA RANGÉE. Sur une ligne unique, à 375 px, le nom du port
   était le seul élément élastique et se faisait comprimer à zéro par la
   connectique et l'étiquette de file, toutes deux de largeur fixe : on lisait
   tout SAUF le nom, c'est-à-dire tout sauf ce qui sert à choisir. */
.sp-txt{display:flex; flex-direction:column; gap:1px; flex:1; min-width:0}
.sp-nom{font-size:var(--t-xl); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-variant-numeric:tabular-nums}
/* Le nom d'appareil DANS la ligne : réservé à la section de tête, où les
   appareils sont mélangés et où l'en-tête ne peut donc pas le porter. En gris et
   en italique, pour que la lecture reste guidée par le nom du PORT — c'est lui
   qu'on cherche, l'appareil n'est que sa provenance. */
.sp-nom em{color:var(--txt3); font-style:italic}
.sp-bas{display:flex; align-items:center; gap:6px; min-width:0}
.sp-grp.file .sp-grp-t b{color:var(--acc); white-space:normal}
.sp-grp.file{padding:8px; background:rgba(255,159,28,.05);
  border:1px solid rgba(255,159,28,.22); border-radius:var(--r-controle)}
/* LA TÊTE DE « RELIER… », ET ELLE N'EST PAS EN ACCENT — c'est tout l'écart avec
   `.file` juste au-dessus. Celle-là dit « commence par là, ça attend depuis
   hier » ; celle-ci dit seulement « ces entrées-là ne coûtent pas de câble ».
   Un cadre orange sur ce qui est simplement le cas ORDINAIRE ferait de la
   moitié de la liste une urgence, et l'accent cesserait de vouloir dire quelque
   chose ailleurs. Même géométrie, teinte neutre. */
.sp-grp.tete{padding:8px; background:var(--bg3);
  border:1px solid var(--line); border-radius:var(--r-controle)}
.sp-grp.tete .sp-grp-t b{white-space:normal}
/* C'est la CONNECTIQUE qui cède, jamais l'étiquette : « ligne · XLR · sym. » se
   relit dans l'infobulle du port et sur sa fiche, tandis que « attend d'être
   posé » n'est écrit qu'ici et porte tout l'intérêt de l'ordre choisi. */
.sp-meta{font:var(--t-s) var(--mono); color:var(--txt3); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* La tête de liste se DIT, elle ne se devine pas à la position : un rang qui
   n'existe que dans l'ordre est invisible dès qu'on lit la deuxième moitié de
   l'écran. `complete` est un fait vérifié sur CE point, `attend` un fait lu dans
   la file — deux forces différentes, deux couleurs. */
.sp-tag{font:var(--t-s) var(--mono); font-style:normal; flex:none; padding:1px 7px;
  border-radius:var(--r-pastille); letter-spacing:.3px}
.sp-tag.complete{background:var(--acc); color:#161a1f}
.sp-tag.attend{background:rgba(255,159,28,.16); color:var(--acc)}
.sp-port.urgent{border-color:rgba(255,159,28,.45)}
/* LA TROISIÈME ÉTIQUETTE — celle qui INFORME au lieu de presser. Les deux
   au-dessus sont en accent parce qu'elles disent « commence par là » ; celle des
   deux listes de « Relier… » dit seulement combien d'entrées attendent derrière
   le clic, et un compte en orange sur trente-sept rangées ne hiérarchise plus
   rien. Elle emprunte donc la teinte de `.sp-meta`, à qui elle tient compagnie
   sur la même ligne. */
.sp-tag.sourd{background:var(--bg2); color:var(--txt3)}
/* Une sortie SANS SUITE reste cliquable — c'est son écran de destination qui
   explique pourquoi elle ne mène nulle part (voir `coeur/relier.js`). Elle est
   estompée, jamais `disabled` : un bouton désactivé ne peut pas dire pourquoi.
   Même valeur que `.opt.vide`, à qui la règle est empruntée. */
.sp-port.sourd{opacity:.5}
.sp-port.sourd:hover{opacity:1}
/* L'ÉTAT VIDE — il NOMME la cause, il n'affiche pas une liste de zéro entrée.
   Même famille que les états vides du §14.7 : un titre qui dit ce qui se passe,
   une phrase qui dit pourquoi, et le pied de la fenêtre qui porte le remède
   quand il en existe un à un clic. */
.sp-vide{padding:18px 14px; text-align:center; background:var(--bg3);
  border:1px dashed var(--line); border-radius:var(--r-controle); margin:10px 0 12px}
.sp-vide-t{font-size:var(--t-xl); color:var(--txt2); margin-bottom:6px}
.sp-vide p{margin:0; font-size:var(--t-l); color:var(--txt3); line-height:1.5}

/* couleur par appareil */
.noeud.sel .cadre{stroke:var(--it); stroke-width:2}
.noeud:hover .cadre{stroke:var(--it)}
.port .dir{white-space:nowrap}

/* la bascule de vue vit dans la barre du haut */
.topbar .vues{flex:none}
.zone-baies{padding-top:0}

/* ---------- tiroir de contrôle ---------- */
.btn.ctrl{padding:6px 11px; display:flex; align-items:center; gap:7px}
.btn.ctrl .voyant{width:8px; height:8px; border-radius:var(--r-cercle); flex:none}
/* LES DEUX LIBELLÉS (`.lg` / `.ct`) ONT DISPARU AVEC LE COMPTE. Le bouton
   affichait « 12 erreurs », donc un libellé dont la LONGUEUR dépendait de
   l'état du studio, d'où la forme courte sous 360 px. Il affiche désormais son
   NOM — « Contrôle », huit signes, quoi qu'il arrive : une porte se nomme par
   ce qu'elle ouvre, pas par un compte, et la barre cesse par la même occasion
   de changer de largeur quand un diagnostic apparaît. La gravité reste dite,
   par le voyant, qui ne coûte pas un mot. */
.btn.ctrl.ok .voyant{background:var(--ok); box-shadow:0 0 6px var(--ok)}
.btn.ctrl.warn .voyant{background:var(--warn); box-shadow:0 0 6px var(--warn)}
.btn.ctrl.err .voyant{background:var(--err); box-shadow:0 0 7px var(--err)}
.btn.ctrl.warn{border-color:rgba(255,209,102,.5); color:var(--warn)}
.btn.ctrl.err{border-color:var(--err); color:var(--err)}
.btn.ctrl.err:hover,.btn.ctrl.warn:hover{filter:brightness(1.15)}
/* `top` mesuré et non zéro : voir B7 sur `.topbar`. Le tiroir garde sa place
   à droite et perd seulement la bande qu'il n'aurait jamais dû prendre.

   ⚠ SOUS LES DEUX BANDES DU HAUT DEPUIS #9 (second morceau), ET NON PLUS UNE
   SEULE. `.toolbar` s'est intercalée entre `.topbar` et le contenu — Auto-
   placer, annuler, refaire et `⋯` y vivent maintenant, dans son coin droit —
   et `top:var(--h-barre,57px)` seul aurait recouvert cette moitié droite
   exactement comme `bottom:0` recouvrait `.bande-bas` avant le correctif du
   premier morceau. Même famille de défaut, trouvée cette fois EN ÉCRIVANT ce
   lot plutôt qu'en le recettant : `--h-toolbar` est mesurée et publiée par
   `publierHauteurToolbar()` (app.js), le pendant exact de `--h-barre` /
   `publierHauteurBarre()`, et les deux s'additionnent ici.

   ⚠ `bottom` MESURÉ AUSSI DEPUIS #9, ET POUR LA MÊME RAISON QUE B7 : Contrôle,
   « à jour » et « Une remarque ? » vivent maintenant dans `<footer
   class="bande-bas">`, un décor PERMANENT au même titre que `.topbar` — le
   principe posé par B7 dit qu'un panneau appelé ne doit jamais battre un décor
   permanent. Avec `bottom:0` tel quel, ce tiroir (z-index 40, plein écran de
   large jusqu'à 380px) aurait recouvert le coin où « Une remarque ? » et le
   zoom se rangent désormais, à `.spacer` : posé LÀ où le tiroir s'ouvre le plus
   souvent — devant le rack, sur le Schéma —, c'est justement l'aller-retour que
   ce lot voulait rendre plus facile qui aurait disparu derrière lui. `--h-bande-bas`
   est mesurée et publiée par `publierHauteurBandeBas()` (app.js), le pendant
   exact de `--h-barre` / `publierHauteurBarre()`. */
.drawer{
  position:fixed; top:calc(var(--h-barre,57px) + var(--h-toolbar,45px)); right:0;
  bottom:var(--h-bande-bas,45px);
  width:380px; z-index:40;
  background:var(--bg2); border-left:1px solid var(--line);
  box-shadow:-16px 0 40px rgba(0,0,0,.45);
  display:flex; flex-direction:column;
}
.drawer-head{
  display:flex; align-items:center; justify-content:space-between;
  padding:12px 14px; border-bottom:1px solid var(--line); flex:none;
}
.drawer-head h2{margin:0; font-size:var(--t-l); text-transform:uppercase; letter-spacing:1px; color:var(--txt2); min-width:0}
/* Le retour au contrôle ne se coupe PAS en deux lignes : « ← » seul au-dessus de
   « Contrôle » se lit comme deux commandes. Le titre, lui, peut s'enrouler — il
   décrit, il ne s'appuie pas. */
#drawerRetour{white-space:nowrap; flex:none}
.drawer-body{flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:12px}
.drawer .card h3{display:flex; align-items:center; justify-content:space-between}
.aide-pop .rep.pas{background:none; width:9px; color:var(--txt3); border-radius:0}
.aide-pop .rep.pas::before{content:'·'; font-size:var(--t-2xl); line-height:0}
/* B10 — L'ELLIPSE MANGE LA PHRASE, PLUS JAMAIS LA SORTIE.

   La bande tient une ligne, en `nowrap` avec ellipse, et « arrêter » était le
   DERNIER mot de la phrase : c'est donc lui que la troncature emportait en
   premier. Or c'est la seule sortie du curseur de remplissage au doigt —
   l'autre est Échap, qui n'existe pas sur un téléphone. Le geste s'y trouvait
   armé sans recours, exactement comme le `✕` d'un point avant le lot tactile.

   D'où deux boîtes plutôt qu'une : le texte prend ce qui reste (`min-width:0`,
   c'est lui qui se tronque) et le lien de sortie est `flex:none` (il ne se
   tronque jamais). Aucun mot n'est retiré, aucune hauteur n'est ajoutée — c'est
   l'ORDRE de sacrifice qui change, et il devient celui qu'on veut. */
.hint{display:flex; align-items:center; gap:8px; white-space:nowrap; overflow:hidden}
.hint .hint-t{min-width:0; overflow:hidden; text-overflow:ellipsis}
.hint .lien-arret{flex:none}

/* ---------- détail : popover ancré ---------- */
/* B7, seconde moitié — 41 et non 35 : à 35 le popover de détail passait DERRIÈRE
   le tiroir, donc invisible pour toutes les colonnes de droite, celles-là mêmes
   qu'on inspecte le tiroir ouvert. L'ordre documenté du projet reste croissant
   et sans trou de sens : 1 · 40 (tiroir) · 41 (détail) · 42 (barre) · 45 (aide)
   · 46 (menu) · 50 (modale) · 60 (infobulle). */
.detail-pop{
  position:fixed; z-index:41; width:360px; max-height:62vh; overflow-y:auto; overflow-x:hidden;
  filter:drop-shadow(0 14px 34px rgba(0,0,0,.6));
}
.detail-pop .card{background:var(--bg2); border-color:var(--txt3)}

/* filtres de diagnostics */
.filtres{display:flex; gap:4px; margin-bottom:9px; flex-wrap:wrap}
.chip{
  background:var(--bg2); border:1px solid var(--line); color:var(--txt3);
  border-radius:var(--r-gelule); padding:3px 10px; font:var(--t-s) var(--mono); cursor:pointer;
}
.chip:hover{color:var(--txt2); border-color:var(--txt3)}
.chip.on{background:var(--line); color:var(--txt); border-color:var(--txt3)}
.chip.err.on{border-color:var(--err); color:var(--err)}
.chip.warn.on{border-color:var(--warn); color:var(--warn)}
.chip.info.on{border-color:var(--acc2); color:var(--acc2)}
.chip.vide{opacity:.35; cursor:default}

/* menu de débordement */
.menu-pop{
  /* `max-width` ET `min-width` : le verdict d'un titre de bloc est une PHRASE,
     et sans plafond il étirerait le menu à sa longueur — 353 px pour deux mots
     de plus, mesuré. Borné, il retombe à la ligne sous son titre, ce que
     `.menu-titre` sait faire (`flex-wrap`). Le second terme du `min()` garde le
     menu dans une fenêtre étroite, où 320 px ne tiennent pas. */
  /* LE DÉFILEMENT EST GÉNÉRAL, PLUS SEULEMENT MOBILE. La règle vivait dans le
     bloc ≤ 820 px avec un `max-height:calc(100vh - 70px)` estimé à la louche ;
     or le menu déborde dès que son contenu dépasse la fenêtre, ce qui arrive à
     l'échelle 1,3 sur une fenêtre de 780 px de haut — et sur du desktop, où il
     n'y avait alors aucune barre de défilement, la dernière entrée était
     simplement invisible. Le plafond exact est posé en ligne par le JS, qui est
     le seul à connaître le `top` du menu ; `overflow-y` ici est ce qui rend ce
     plafond utile. */
  position:fixed; z-index:46; min-width:210px; max-width:min(320px, calc(100vw - 12px)); padding:5px;
  overflow-y:auto; overscroll-behavior:contain;
  background:var(--bg2); border:1px solid var(--line); border-radius:var(--r-carte);
  box-shadow:0 14px 34px rgba(0,0,0,.55); display:flex; flex-direction:column; gap:2px;
}
.menu-item{
  background:none; border:0; color:var(--txt2); text-align:left;
  font-size:var(--t-l); padding:7px 10px; border-radius:var(--r-controle); cursor:pointer;
}
.menu-item:hover{background:var(--bg3); color:var(--txt)}
/* Les trois pages juridiques sont de VRAIS liens dans le menu, et non des
   boutons qui appelleraient `window.open` : le clic du milieu, le clic droit et
   « ouvrir dans un onglet » marchent tout seuls, ce qu'aucun `onclick` ne
   rattrape. Il ne leur manque que la forme d'un item de menu. */
a.menu-item{display:block; text-decoration:none}
/* la doublure d'un bouton qui avertit avertit aussi — mêmes teintes, autre
   forme : dans un menu il n'y a pas de bordure à colorer, c'est le mot qui
   porte. Voir `.btn.avertit`, et `entreesRepliees` qui recopie la classe. */
.menu-item.avertit{color:var(--warn)}
.menu-item.avertit:hover{background:var(--bg3); color:var(--warn)}
.menu-item.danger{color:var(--err)}
.menu-item.danger:hover{background:var(--err); color:#fff}
/* Une entrée « danger » ÉTEINTE (#36) ne doit pas prendre le survol plein rouge
   de sa voisine vivante — ce serait dire « clique-moi » à un geste qui refuse.
   Spécificité à quatre classes, exprès : sans elle `.danger:hover`, déclarée
   juste au-dessus, l'emporterait à égalité de règle générale `.menu-item.sourd`. */
.menu-item.danger.sourd:hover{background:none; color:var(--err)}
/* le menu se lit en quatre blocs : les commandes repliées de la barre, les
   quatre exports — les seules entrées qui n'écrivent rien du tout —, les
   raccourcis de rangement, les deux gestes destructeurs. Le trait sépare, il
   n'ajoute aucun contrôle. */
.menu-sep{height:1px; margin:4px 6px; background:var(--line)}

/* ---------- le titre d'un bloc, et son VERDICT ----------

   Trois blocs sur quatre écrivent dans le studio, et rien ne le disait avant
   d'avoir cliqué. Le titre porte donc DEUX choses : ce que le bloc est, et ce
   qu'il abîme — la seconde en toutes lettres, à demeure, jamais au survol. Une
   infobulle aurait répondu à qui la cherche ; la question se pose justement à
   qui ne sait pas encore quoi chercher.

   `em` n'est pas de l'emphase typographique ici, c'est le VERDICT : d'où le
   `font-style:normal`. On garde la balise parce qu'elle porte le sens dans le
   balisage — l'inspecteur et le lecteur d'écran y lisent une distinction, ce
   qu'un second `span` ne dirait pas.

   Le trait de frontière est le `border-top` de ce titre, et non un `.menu-sep`
   posé à côté : deux éléments pour une seule frontière se décalent au premier
   ajustement de marge. `.menu-sep` survit pour le bloc SANS titre, celui des
   commandes repliées de la barre — elles n'ont pas de verdict commun, ce sont
   les boutons du haut, chacun avec le sien. */
.menu-titre{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 7px;
  margin:9px 6px 4px; padding-top:8px; border-top:1px solid var(--line);
  font-size:var(--t-xs); font-family:var(--mono);
  text-transform:uppercase; letter-spacing:.05em; color:var(--txt3);
}
.menu-pop > .menu-titre:first-child{margin-top:2px; padding-top:0; border-top:0}
.menu-titre em{
  /* `min-width:0` — sans lui, l'élément de flex refuse de descendre sous sa
     largeur de contenu : il ne se coupe pas, il DÉBORDE, et la phrase sort du
     menu par la droite au lieu de passer à la ligne. */
  min-width:0;
  font-style:normal; text-transform:none; letter-spacing:0;
  font-family:inherit; color:var(--txt4);
}
/* le bloc qui ÉCRIT porte la couleur d'avertissement sur son seul verdict : le
   titre reste gris, sans quoi trois blocs sur quatre crieraient et aucun ne
   serait lu. Ce n'est pas `--err` non plus — le rouge est réservé aux deux
   entrées destructrices elles-mêmes, qui le portent déjà. */
.menu-titre.ecrit em{color:var(--warn)}

/* ---------- aperçus d'export ----------
   L'aperçu montre le CONTENU d'une planche, pas sa mise en page : la géométrie
   en millimètres vit dans `impression.css`, sous `@media print`, et la recopier
   ici pour l'écran ferait deux jeux de cotes qui divergeraient au premier
   réglage. La mise en page papier se juge dans la boîte d'impression. */
.apercu{max-height:44vh; overflow:auto; margin-top:12px; border:1px solid var(--line); border-radius:var(--r-controle)}
.apercu table{width:100%; border-collapse:collapse; font-size:var(--t-l)}
.apercu th,.apercu td{text-align:left; padding:4px 9px; vertical-align:top;
  border-bottom:1px solid rgba(255,255,255,.05)}
/* l'en-tête reste lisible après vingt lignes de défilement : sans lui, on ne
   sait plus si la colonne qu'on lit est la rangée A ou la rangée B */
.apercu th{position:sticky; top:0; z-index:1; background:var(--bg3); color:var(--txt3);
  font:var(--t-s)/1.4 var(--mono); text-transform:uppercase; letter-spacing:.06em}
.apercu td.mono{font-family:var(--mono); color:var(--txt2); white-space:nowrap}
.apercu .grp{color:var(--txt3); font-size:var(--t-m)}
.apercu tr.sep td{background:var(--bg3); color:var(--acc2); font:var(--t-s) var(--mono);
  text-transform:uppercase; letter-spacing:.06em}
/* une limite chiffrée n'est pas une note de bas de page : elle prend la couleur
   de l'avertissement, sans quoi elle se lit comme le reste et ne se lit pas */
.note.avert{border-left-color:var(--warn); color:var(--txt2)}
.note.avert b{color:var(--warn)}
/* Une confirmation, dans la fenêtre même où le geste a été fait. */
.note.ok{border-left-color:var(--ok); color:var(--txt2)}
.note.ok b{color:var(--ok)}

/* CE QUE LE CLIC VA FAIRE — la liste numérotée des modales d'action.

   Elle existe parce qu'une fenêtre de confirmation faite de `.kv` ne dit que
   des NOMBRES, et qu'un nombre répond à « combien » sans jamais répondre à
   « quoi ». Les `.kv` restent, mais après : ils deviennent le bilan chiffré
   d'un récit, au lieu d'en tenir lieu.

   Ordonnée (`<ol>`), et le numéro vient du compteur CSS plutôt que du marqueur
   natif : il faut le même pastillage que les repères du reste de l'appli, et un
   `list-style` ne se peint pas. `em` porte le LIEU (baie, colonne, mode) —
   c'est le mot qu'on cherche des yeux, il prend l'accent, comme partout. */
.deroule{margin:0 0 12px; padding:0; list-style:none; counter-reset:deroule}
.deroule li{position:relative; padding:0 0 0 27px; margin-bottom:10px;
  font-size:var(--t-l); color:var(--txt2); line-height:1.5}
.deroule li:last-child{margin-bottom:0}
.deroule li::before{
  counter-increment:deroule; content:counter(deroule);
  position:absolute; left:0; top:1px; width:18px; height:18px; box-sizing:border-box;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line); border-radius:50%; background:var(--bg3);
  font:var(--t-s) var(--mono); color:var(--txt3)}
.deroule b{color:var(--txt)}
.deroule em{font-style:normal; color:var(--acc)}
/* le pas qui ne fait rien — « rien de ce que tu as posé ne bouge » : il compte
   dans le récit, il ne doit pas peser comme une écriture */
.deroule li.calme{color:var(--txt3)}
.deroule li.calme b{color:var(--txt2)}
/* la définition du mode, sous la ligne qui le nomme : elle explique un mot du
   récit, elle n'en est pas un pas — d'où le retrait de couleur */
.deroule .glose{display:block; color:var(--txt3); font-size:var(--t-m)}

/* gouttière des rangées */
/* B5 et B6 — LA GOUTTIÈRE SUIT L'ÉCHELLE, PARCE QUE CE QU'ELLE NOMME LA SUIT.

   Trois cotes en dur devant trois cotes déjà mises à l'échelle, et chacune
   décrochait dans un sens différent :

   · `min-width:58px` face à la piste de grille `calc(58px * var(--fs))` de
     `.bay`. À l'échelle COMPACTE (0,9) la piste vaut 52,2 px : l'élément
     débordait de 5,8 px sur la colonne 1, et `ajusterColonnes()` — qui soustrait
     `58 * fs` de la largeur disponible — se trompait du même écart sur chaque
     largeur de colonne calculée.
   · `height:52px` face à `.point{height:calc(52px * var(--fs))}`. Les libellés
     « A / sorties » et « B / entrées » décrochaient de 8 px à l'échelle 1,3 des
     rangées qu'ils nomment — or les nommer est leur seule fonction.
   La troisième, `.rang-mode`, est plus bas : elle suit `.mode`, qui n'est PAS
   à l'échelle, et non `.point`. Deux cotes voisines, deux références
   différentes : c'est exactement le genre de détail qu'il faut écrire. */
.col.gouttiere{min-width:calc(58px * var(--fs)); padding-right:8px}
.col.gouttiere .rang{
  display:flex; flex-direction:column; justify-content:center; align-items:flex-end;
  gap:1px; height:var(--h-point); text-align:right;
}
.col.gouttiere .rang b{font:var(--t-l) var(--mono); color:var(--txt2); line-height:1}
.col.gouttiere .rang span{font-size:var(--t-xs); color:var(--txt3); text-transform:uppercase; letter-spacing:.5px}
/* Sa référence est `.mode`, pas `.point` : 15 px ici comme là, et 32 px au
   doigt (bloc `pointer:coarse`, plus bas). Elle ne porte pas `--fs` parce que
   `.mode` ne le porte pas — l'aligner sur l'échelle la ferait décrocher de ce
   qu'elle nomme, ce qui est le défaut qu'on répare. */
.col.gouttiere .rang-mode{
  height:15px; display:flex; align-items:center; justify-content:flex-end;
  font:var(--t-xs) var(--mono); color:var(--txt4); text-transform:uppercase; letter-spacing:.5px;
}

/* fiche dont tous les ports sont posés : réduite à son en-tête */


/* bande des appareils déjà posés */
.places{
  flex:none; display:flex; align-items:center; gap:5px; flex-wrap:wrap;
  padding:0 14px; max-height:0; overflow:hidden; transition:max-height .15s, padding .15s;
}
.places:not(:empty){max-height:96px; overflow-y:auto; padding:8px 14px; border-top:1px solid var(--line)}
.places-t{
  font:var(--t-xs) var(--mono); color:var(--txt3); text-transform:uppercase; letter-spacing:1px;
  margin-right:4px; flex:none;
}
.puce{
  display:flex; align-items:center; gap:6px; padding:3px 9px 3px 7px;
  background:var(--bg3); border:1px solid var(--line); border-left:3px solid var(--it);
  border-radius:var(--r-gelule); color:var(--txt3); font-size:var(--t-m); cursor:pointer;
}
.puce:hover{color:var(--txt); border-color:var(--it)}
.puce em{font:var(--t-xs) var(--mono); font-style:normal; color:var(--txt3)}
.puce:hover em{color:var(--txt3)}

/* ---------- infobulles ---------- */
.tip{
  position:fixed; z-index:60; max-width:300px; padding:8px 11px;
  background:#0a0e13; border:1px solid #38434f; border-radius:var(--r-controle);
  box-shadow:0 12px 30px rgba(0,0,0,.65);
  display:flex; flex-direction:column; gap:3px; pointer-events:none;
}
.tip span{font-size:var(--t-m); line-height:1.45; color:var(--txt3)}
.tip span:first-child{font-size:var(--t-l); font-weight:600; color:var(--txt)}
.tip[hidden]{display:none !important}

/* bouton « poser tout » : il annonce ce qu'il ferait */
.ia.pose{gap:2px}
.ia.pose em{font:var(--t-xs) var(--mono); font-style:normal; color:var(--acc)}
.ia.pose.actif{color:var(--acc)}
/* `.avertit` — le pendant d'`.inerte` à l'autre bout : un contrôle qui ferait
   PLUS que son libellé ne le laisse croire. Aucun bouton ne le porte
   aujourd'hui — `#btnAuto` en était le seul porteur avant #80, qui a
   justement retiré le comportement qui le justifiait (le repli silencieux
   vers « Recâbler tout depuis zéro » à file vide). Générique, gardé pour le
   prochain contrôle qui en aura besoin. Même teinte que `.btn.ctrl.warn`, à
   dessein — le projet a un seul jaune, et il veut dire la même chose
   partout : regarde avant de cliquer. */
.btn.avertit{border-color:rgba(255,209,102,.5); color:var(--warn)}
.btn.avertit:hover{filter:brightness(1.15)}

/* `.inerte` est générique : un contrôle qui ne ferait rien le DIT, au lieu de
   disparaître ou de mentir. Le bouton reste cliquable — il répond par un flag. */
.inerte{opacity:.35}
.ia.inerte:hover{background:none; color:var(--txt2)}

/* ---------- schéma : affordance des pastilles ---------- */
.pport circle{transition:stroke-width .12s}
.pport:hover circle{stroke-width:3}
.pport.enmain rect{fill:rgba(255,159,28,.32)}
.pport.enmain .pnom{fill:var(--txt); font-weight:600}
.pport.enmain .ppt{fill:var(--acc)}
.pport.enmain .logement{fill:#3a2a12; stroke:var(--acc); stroke-width:2}
.pport.enmain .plot{fill:var(--acc); stroke:var(--acc); stroke-width:2}
/* `.cablage` ET NON `.armee` — un geste de câblage a DEUX temps, et ces règles
   valent aux deux. `.armee` ne disait que le second, celui où un départ est
   déjà choisi : tant que le premier n'existait pas, la distinction n'en était
   pas une. Depuis qu'on peut attendre une SORTIE sans en tenir une, le style du
   geste doit s'allumer avant le premier clic, sinon les six boîtes qui viennent
   de s'ouvrir n'ont aucune pastille en évidence et l'ouverture n'est pas
   lisible. `.cible` et `.hors` gardent leur sens exact : ce qui reçoit le
   prochain clic, et ce qui ne le reçoit pas. */
#schema.cablage .pport.depart{opacity:1}
#schema.cablage .noeud:has(.pport.depart) .cadre{stroke:var(--acc); stroke-width:1.6}
.pport.inspecte rect{fill:rgba(255,159,28,.18)}
.pport.inspecte circle{stroke-width:3}
#schema.cablage .pport.hors{opacity:.22}
#schema.cablage .pport.cible rect{fill:rgba(255,159,28,.10)}
#schema.cablage .pport.cible:hover rect{fill:rgba(255,159,28,.3)}
#schema.cablage .pport.cible circle{stroke-width:2.6}
#schema.cablage .noeud .cadre{stroke:#2f3a47}
.schema-bar .aide.armee{color:var(--txt2)}
/* L'aide d'ÉTAPE — le premier geste à faire quand le studio est encore vide.
   Même bande, même place, un ton plus haut : elle n'ajoute pas un pixel de
   chrome et s'efface d'elle-même dès qu'il y a du travail à l'écran. */
.schema-bar .aide.etape{color:var(--txt2)}
.schema-bar .aide.etape b{color:var(--acc)}
.schema-bar .aide.armee b{color:var(--acc)}

/* traits de liaison dans l'aide */
.aide-pop .l{width:20px; height:0; flex:none; margin-top:8px; border-top:1.5px solid var(--txt2)}
.aide-pop .l.l-cordon{border-top:3px dotted var(--acc)}
.aide-pop .l.l-baie{border-top:1.5px solid var(--txt2)}
.aide-pop .l.l-direct{border-top:1.5px dashed var(--txt3)}
.aide-pop .l.l-intention{border-top:2px dotted var(--txt3)}

/* trois niveaux de lecture quand un appareil est sélectionné */
.lien.chaine{opacity:.55}
.lien.chaine .trait{stroke-dasharray:none}
.sim.chaine{border-left-color:var(--acc2); font-size:var(--t-l)}
.sim.chaine b{color:var(--acc)}

/* étapes du flux : le bouton d'ajout. L'intitulé de colonne, lui, a quitté le
   SVG — voir `.schema-etapes` plus haut, et le bandeau de `renderSchema`. */
.ajout{cursor:pointer}
/* `pointer-events:all` — LE MÊME DÉFAUT QUE `.depli` CI-DESSOUS, DANS LA RÈGLE
   D'À CÔTÉ, QU'UN LOT PRÉCÉDENT N'AVAIT PAS BALAYÉE. Avec `fill:none`, le
   hit-testing SVG par défaut (`visiblePainted`) ne retient que ce qui est PEINT :
   ici le pointillé d'un pixel et les glyphes du texte. Mesuré à la sonde avant
   correction, sur une grille de points répartis dans le cadre : 7 à 16 % de la
   surface du bouton répondait au survol et au clic, le reste passait au travers.
   Le centre géométrique ne « marchait » que par coïncidence — le libellé est
   centré, donc ses glyphes s'y trouvent.

   C'est le bouton du PREMIER geste offert à une colonne vide, et il fait
   270 × 39 px : la cible que le §14 demande au doigt existait déjà, elle
   n'était simplement pas atteignable. `all` rend tout l'intérieur du cadre
   sensible SANS peindre un pixel de plus — c'est ce qui rend la correction
   gratuite, et c'est pourquoi on ne remplace pas `fill:none` par un fond, même
   transparent : le `:hover` ci-dessous en pose déjà un, et lui seul doit se voir. */
.ajout rect{fill:none; pointer-events:all; stroke:#333d49; stroke-width:1; stroke-dasharray:4 4}
/* `--txt4` est LA VARIABLE DU DÉCORATIF, et ce texte-ci ne l'est pas : c'est le
   libellé du PREMIER geste proposé à un studio vide. Il était à 3,44:1 sur le
   fond, sous le seuil AA de 4,5:1 — un premier geste qu'on ne lit pas n'est pas
   un premier geste. `--txt3` le passe à 5,3:1. Même correction sur
   `.pport.libre .pnom`, qui porte le nom d'un port. */
.ajout text{fill:var(--txt3); font-size:11px}
.ajout:hover rect{stroke:var(--acc); stroke-dasharray:none; fill:rgba(255,159,28,.07)}
.ajout:hover text{fill:var(--acc)}
#schema.cablage .ajout{opacity:.3}

/* LA LIGNE DE REPLI — « + 34 ports libres ». Elle occupe une rangée de port et
   emprunte donc son gabarit, mais pas son curseur : elle ne se câble pas, elle
   ouvre. Le pointillé est, dans toute cette feuille, le trait de ce qui attend. */
.depli{cursor:pointer}
/* `pointer-events:all` ET C'EST LE POINT LE PLUS COÛTEUX DE CETTE LIGNE.
   Avec `fill:none`, le hit-testing SVG (`visiblePainted` par défaut) ne touche
   QUE le trait pointillé d'un pixel et les glyphes du texte : cliquer au milieu
   de « + 5 ports libres » traversait le cadre et SÉLECTIONNAIT L'APPAREIL —
   le tiroir s'ouvrait, la boîte restait repliée. Mesuré au clic réel avant
   correction. C'est le même défaut que celui du lot, un cran plus bas : le
   dépli est le seul geste qui rende un port libre atteignable, et sa cible
   faisait 9 px de haut en glyphes de mono là où le §14 en demande 44 au doigt.
   `all` rend toute la surface cliquable sans peindre un seul pixel de plus. */
.depli rect{fill:none; pointer-events:all; stroke:#333d49; stroke-width:1; stroke-dasharray:3 3}
.depli text{fill:var(--txt3); font-size:9px; font-family:var(--mono)}
.depli:hover rect{stroke:var(--acc); stroke-dasharray:none; fill:rgba(255,159,28,.07)}
.depli:hover text{fill:var(--acc)}
.depli.ouvert rect{stroke:transparent}
.depli.ouvert text{fill:var(--txt4)}
/* pendant un câblage, déplier n'est pas le geste en cours — et surtout, une
   boîte que le geste vient d'ouvrir se rouvrirait au rendu suivant : le clic
   serait une commande sans effet. Le texte de la ligne perd son signe en même
   temps (voir `renderSchema`), pour ne pas offrir par écrit ce que le pointeur
   refuse. */
#schema.cablage .depli{opacity:.22; pointer-events:none}
.chip-note{font:var(--t-s) var(--mono); color:var(--txt3); align-self:center}

/* `#vueSchema .schema-bar` A DISPARU AVEC #9 (second morceau) : la barre a
   quitté ce conteneur pour `.toolbar`, voir `studio.html`. */
#vueSchema{display:flex; flex-direction:column; overflow:hidden; padding:14px 16px 0}
#schema{flex:1; overflow:auto; min-height:0; padding-bottom:14px}
.schema-vide{text-align:center; padding:44px 16px; color:var(--txt2)}
.schema-vide h2{font-size:var(--t-xl); color:var(--txt); margin:0 0 10px}
.schema-vide p{max-width:420px; margin:0 auto 18px; color:var(--txt3)}
.schema-vide .btn{min-height:44px}

/* L'AJOUT DE MATÉRIEL s'efface quand l'attention est ailleurs — et LUI SEUL.
   Ces deux règles visaient `.etape`, le groupe qui enveloppait dans le SVG le
   bouton `+ appareil` ET l'intitulé de colonne : dès qu'un nœud ou une liaison
   était sélectionné, l'intitulé s'éteignait à 0,22 avec le bouton, c'est-à-dire
   exactement le repère que le §14.3 point 4 veut rendre permanent. Le sélecteur
   voulait viser `.ajout`. L'intitulé étant sorti du SVG, le défaut ne peut plus
   se reformer par ici. */
#schema.cablage .ajout,
#schema.focus .ajout{opacity:.22; pointer-events:none}

/* SURVOLER MET EN ÉVIDENCE LA CHAÎNE — raccourci de bureau (§14.4, divergence 1).
   Les mêmes trois niveaux de lecture que la sélection, obtenus par des classes
   posées à la main : pas de rendu, donc pas de scintillement sous le curseur.
   Aucune règle ici ne s'applique au doigt — `brancherSurvolChaine` ne branche
   rien sous `(pointer:coarse)`, et la sélection y fait tout. */
#schema.survol .noeud.hors-survol{opacity:.16}
#schema.survol .lien.hors-survol{opacity:.05}
#schema.survol .lien.chaine-survol{opacity:1}
#schema.survol .lien.chaine-survol .trait,
.survol-dessus .trait-survol{fill:none; opacity:1; stroke-width:2.4; filter:drop-shadow(0 0 5px var(--acc))}
.survol-dessus .trait-survol-contour{
  fill:none; opacity:.96; stroke:var(--bg); stroke-width:7;
  stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:none;
}
.survol-dessus .trait-survol-cible{stroke-width:3.4}
.pport.bout-survol .logement{stroke:var(--acc); stroke-width:2.4}
.pport.bout-survol .plot{stroke-width:2.6; filter:drop-shadow(0 0 4px var(--acc))}
/* Les zones de clic restent sur les liens originaux, jamais sur cette peinture. */
.survol-dessus,.survol-dessus *{pointer-events:none}
#schema.survol .noeud.survole .cadre{stroke:var(--it)}

/* ---------- annulation ---------- */
/* la règle existante ne couvrait que .ia et .kill : sans elle, e.target.dataset
   viserait le <path> des icônes des boutons de la barre */
.btn svg{pointer-events:none; display:block}
/* pas d'opacity : .btn.discret est en --txt2, l'estomper le ferait passer sous
   --txt4, la variable créée exprès pour le décoratif */
.btn.discret:disabled{color:var(--txt4); cursor:default}
.btn.discret:disabled:hover{color:var(--txt4); border-color:var(--line)}

/* saut vers une cible : un halo pulsé, deux battements, puis plus rien.
   Ni contour ni fond — un contour se lirait « sélectionné ». */
@keyframes saut-halo{
  0%{box-shadow:0 0 0 3px rgba(255,159,28,.45)}
  25%{box-shadow:0 0 0 3px rgba(255,159,28,0)}
  50%{box-shadow:0 0 0 3px rgba(255,159,28,.45)}
  100%{box-shadow:0 0 0 3px rgba(255,159,28,0)}
}
@keyframes saut-ombre{
  0%{filter:drop-shadow(0 0 5px rgba(255,159,28,.75))}
  25%{filter:drop-shadow(0 0 0 rgba(255,159,28,0))}
  50%{filter:drop-shadow(0 0 5px rgba(255,159,28,.75))}
  100%{filter:drop-shadow(0 0 0 rgba(255,159,28,0))}
}
.saut{animation:saut-halo 1.1s ease-out}
g.saut{animation:saut-ombre 1.1s ease-out}

/* ---------- la colonne comme objet manipulable ---------- */
/* `.num` existait déjà, occupait déjà une rangée de la sous-grille `.col` et
   n'était QUE décoratif : il devient la poignée de la colonne — on l'attrape
   par son numéro, comme une ligne de tableur par son en-tête. C'est le seul
   changement de gabarit de la baie, absorbé par la sous-grille sans toucher
   ajusterColonnes().
   Tous les sélecteurs portent `[data-colonne]` : la gouttière a elle aussi un
   `.num`, mais elle n'en porte pas, et elle ne doit jamais recevoir ces états. */
.num[data-colonne]{
  height:14px; line-height:13px; border-radius:var(--r-pastille); cursor:grab;
  border:1px solid transparent;
  transition:background .12s, color .12s, border-color .12s;
}
.col.gouttiere .num{height:14px}   /* la gouttière garde l'alignement des rangées */
.num[data-colonne]:hover{background:var(--bg3); color:var(--txt); border-color:var(--line)}

/* CE QUI VA BOUGER, MONTRÉ AVANT QU'ON BOUGE. Le numéro s'éclairait seul au
   survol : on voyait une petite pastille s'allumer, pas qu'on tenait la colonne
   ENTIÈRE — les deux points, le mode, tout. Julien n'avait pas vu qu'une
   colonne se déplaçait.

   Le vocabulaire est déjà là et n'est pas réinventé : `.col.cible` entoure la
   colonne en 2 px pleins pendant le glisser. Le survol prend le MÊME contour en
   1 px, donc la même couleur d'accent que tous les autres survols de l'appli
   (un point, un mode) et la même forme que la cible, en plus léger. L'épaisseur
   fait la différence entre « voici l'objet » et « voici où ça tombe ».

   `:has()` porte la règle, faute de quoi il faudrait poser une classe en
   JavaScript à chaque `mouseenter` — sur une baie de 24 colonnes, 48 écouteurs
   pour un effet que le CSS sait faire. Un navigateur qui ne connaîtrait pas
   `:has()` laisse simplement tomber la règle et retrouve l'ancien survol.

   Seul le NUMÉRO reste la poignée : le contour n'apparaît qu'en le survolant,
   donc il ne promet jamais qu'on peut attraper la colonne par un de ses points —
   glisser un point, lui, déplace ce seul port. */
.col:has(> .num[data-colonne]:hover){
  /* POINTILLÉ, comme tous les contours d'attente de l'appli — le point vide, le
     curseur de remplissage, le bouton « + appareil » du schéma. Le trait plein
     est réservé à ce qui est ARRÊTÉ : la cible d'un dépôt, une sélection. Ici
     rien n'est encore décidé, on survole. */
  outline:1px dashed var(--acc); outline-offset:-1px; border-radius:var(--r-controle);
}
/* pendant le geste, la cible commande : son contour de 2 px ne doit pas être
   contredit par celui du survol sur la colonne qu'on a quittée */
.col.saisie:has(> .num[data-colonne]:hover),
.col.cible:has(> .num[data-colonne]:hover){ outline-width:2px; outline-style:solid }
.num[data-colonne]:focus-visible{outline:2px solid var(--acc); outline-offset:1px}
.col[data-selection] .num[data-colonne]{background:var(--acc); color:#0e1116; border-color:var(--acc)}

/* le geste, sur le patron de .point.dragging / .point.drop */
.col.saisie{opacity:.35}
.col.cible{outline:2px solid var(--acc); outline-offset:-1px; border-radius:var(--r-controle); background:rgba(255,159,28,.14)}
/* #140 : trait visible 4px, capture géométrique 22px en coordonnées client.
   Absolu et non interactif : ni piste de grille, ni nouvelle cible de clic. */
.bay{position:relative}
.repere-insertion-colonne{position:absolute;width:4px;transform:translateX(-50%);background:var(--acc);z-index:4;pointer-events:none}
.repere-insertion-colonne.refus{background:var(--err)}
/* Pendant un dépôt, le hover de la poignée n'ajoute pas un second repère. */
.bay.depot-colonne .col:has(> .num[data-colonne]:hover){outline:none}
.bay.depot-colonne .col.cible{outline:2px solid var(--acc)}
.bay.depot-colonne .col.cible.refus{outline-color:var(--err)}
/* cible occupée : la permutation est annoncée AVANT le relâchement */
.col.cible.echange .num[data-colonne]{color:transparent; position:relative}
.col.cible.echange .num[data-colonne]::after{
  content:'⇄'; position:absolute; inset:0; color:var(--acc); text-align:center; line-height:13px;
}
/* cible incompatible : la raison exacte est donnée AU DÉPÔT, dans la modale —
   une infobulle à 320 ms de délai arriverait trop tard */
.col.cible.refus{outline-color:var(--err)}
.col.cible.refus .num[data-colonne]{color:var(--err)}

/* CE QUI VA DÉCALER, ET DANS QUEL SENS. Le déplacement de colonne intercale
   désormais au lieu de permuter : relâcher sur la 8 fait bouger 8, 9, 10… et
   l'écran ne le disait pas. Une flèche DANS le numéro, pas à côté : la colonne
   fait 58 px de large et un chevron posé dans la gouttière tomberait entre deux
   colonnes, donc sur aucune des deux. Elle remplace le chiffre — on sait déjà où
   l'on est, c'est le mouvement qu'on ignore.

   Pas d'`outline` sur ces colonnes-là : le contour plein est le vocabulaire de
   la CIBLE, et vingt colonnes contournées noieraient la seule qui compte. Un
   fond très léger — le même que `.col.tranche` — suffit à dessiner l'étendue.

   ⚠ Ces règles écrasent le `::after` que le bloc `pointer:coarse` utilise comme
   zone tactile de la poignée (§14.7, vague 2), par simple spécificité. C'ÉTAIT
   SANS CONSÉQUENCE, ET ÇA NE L'EST PLUS TOUT À FAIT : le commentaire disait
   « `pousse-*` n'existe QUE pendant un glisser natif HTML5, qu'aucun contact
   tactile n'émet », et le lot de l'appui long a rendu cette phrase fausse — la
   même prévisualisation est maintenant peinte au doigt, par
   `previsualiserDecalage` appelée depuis le geste tactile.

   Ce qui se perd exactement, et pourquoi on l'accepte : la zone tactile étendue
   de la poignée disparaît sur les colonnes qui portent un chevron. Or ces
   colonnes-là ne sont peintes que PENDANT une saisie, moment où plus rien ne se
   vise par la poignée — le doigt tient déjà un objet, et la cible du dépôt est
   la colonne entière, pas son numéro. La zone revient intacte au relâchement,
   quand elle redevient utile. `.col.cible.echange` fait la même chose depuis le
   lot 2, pour la même raison. */
.col.pousse-d, .col.pousse-g{ background:rgba(255,255,255,.045) }
.col.pousse-d .num[data-colonne],
.col.pousse-g .num[data-colonne]{ color:transparent; position:relative; border-color:var(--acc2) }
.col.pousse-d .num[data-colonne]::after,
.col.pousse-g .num[data-colonne]::after{
  position:absolute; inset:0; color:var(--acc2); text-align:center; line-height:13px;
}
.col.pousse-d .num[data-colonne]::after{ content:'›' }
.col.pousse-g .num[data-colonne]::after{ content:'‹' }

/* ---------- L'OBJET SOULEVÉ AU DOIGT ----------
   LA SEULE CLASSE NEUVE DU LOT DE L'APPUI LONG, ET ELLE NE DIT AUCUN ÉTAT.
   Les états ont déjà leur vocabulaire — `.dragging`, `.saisie`, `.cible`,
   `.drop`, `.vise`, les chevrons — et le geste tactile les réutilise tels
   quels. Ce qu'il n'a pas, c'est l'IMAGE DE GLISSER : pendant un glisser natif
   le navigateur dessine lui-même une copie de l'élément et la promène sous le
   curseur ; aucun contact tactile n'en produit. Sans elle, l'objet « soulevé »
   resterait sagement à sa place sous un doigt qui le cache — le geste n'aurait
   littéralement pas de sujet. Ceci EST cette copie, à la main.

   `position:fixed` et `transform` : la copie vit dans le référentiel de la
   fenêtre, comme les coordonnées d'un `touchmove`, et se déplace par une
   transformation composée — donc sans déclencher de mise en page à chaque
   image. Ses cotes sont posées en ligne par `souleverAuDoigt` (largeur, hauteur
   et `--h-point`), qui les relève sur l'original : hors de `.bay`, les pistes
   de grille et la variable de hauteur n'existent plus.

   `pointer-events:none` est FONCTIONNEL, pas cosmétique : la cible du dépôt est
   cherchée par `elementFromPoint` sous le doigt, et une copie opaque se
   désignerait elle-même à chaque image.

   z-index 47 : au-dessus du tiroir (40), des popovers (41–46), sous la modale
   (50) et l'infobulle (60) — aucune des deux ne peut coexister avec une saisie.

   1,04 et une ombre portée, pas davantage : « soulevé » doit se lire d'un coup
   d'œil sans que l'objet cesse de couvrir sa destination. À 1,15, le fantôme
   d'une colonne masquait ses deux voisines, c'est-à-dire l'endroit même qu'on
   vise. */
.fantome-doigt{
  position:fixed; left:0; top:0; z-index:47; pointer-events:none;
  opacity:.92; border-radius:var(--r-controle);
  box-shadow:0 10px 26px rgba(0,0,0,.55);
  transform-origin:50% 50%;
  will-change:transform;
}
.fantome-doigt > *{width:100%; height:100%; opacity:1}
/* le clone d'une colonne emporte son numéro : il ne doit pas y paraître survolé */
.fantome-doigt .num[data-colonne]{cursor:inherit}

/* ---------- groupes de colonnes ----------
   Six teintes NOMMÉES, jamais un hexadécimal libre : un hexa entrerait en
   collision avec les 18 couleurs saturées de PALETTE et casserait la lecture de
   la baie. Elles sont #232a34 décalé en teinte — désaturées et sombres, donc
   structurellement incapables d'imiter une couleur d'appareil.
   La teinte NE CODE RIEN : elle sépare. Le code couleur du projet reste celui
   de l'appareil, une couleur par appareil, et l'inventaire en tient lieu de
   légende. Aucun sélecteur n'est offert : la teinte vient du RANG. */
:root{ --g-ardoise:#232a34; --g-prune:#2b2330; --g-mousse:#222e28;
       --g-sable:#302c22;   --g-ocean:#1f2b33; --g-brique:#33262a; }

/* deux rangées, la première `auto` : SANS item elle mesure 0 et `gap:0`
   n'ajoute rien — zéro groupe = zéro pixel, l'espace permanent reste aux
   outils de travail */
.bay{ grid-template-rows:auto 1fr; }
.col{ grid-row:2 }   /* explicite : sinon l'auto-placement remonte en rangée 1 */

/* P2 — `text-overflow:ellipsis` ÉTAIT INERTE, ET LE PAPIER LE SAVAIT DÉJÀ.
   La propriété ne s'applique pas à un conteneur `flex` : elle était posée sur
   le bandeau lui-même, donc sans effet, et un nom de groupe trop long était
   coupé NET, sans les trois points qui disent qu'il manque quelque chose.
   `impression.css` enveloppe correctement le sien (`.etq-groupe > span`) : les
   deux surfaces avaient divergé sur le même nom, à quelques centimètres l'une
   de l'autre. Le rendu émet désormais l'enveloppe ici aussi, et c'est elle qui
   porte la coupure. */
/* P3 — LE BANDEAU ÉTAIT INVISIBLE, ET CE N'ÉTAIT PAS SON TEXTE.

   Mesuré, pas supposé. Le texte allait très bien : `--txt2` (#a7b4c2) sur
   `--g-ardoise` (#232a34) donne 6,85:1, au-dessus du 4,5:1 exigé. Ce qui ne
   se voyait pas, c'est LA TUILE — et pour une raison qu'on ne trouve qu'en
   mettant les deux hexadécimaux côte à côte :

       .baie  background: linear-gradient(180deg, #232a34, #1a2027)
       :root  --g-ardoise: #232a34

   Le bandeau vit dans la RANGÉE 1 de `.bay`, c'est-à-dire tout en haut de la
   carte, là où le dégradé vaut encore #232a34. La première teinte de groupe
   est donc, au pixel près, la couleur du fond derrière elle : rapport 1,00:1.
   Ocean et brique donnent 1,00 aussi, prune 1,05, mousse 1,03, sable 1,04. Six
   teintes, aucune au-dessus de 1,05 — le bandeau n'était pas peu lisible, il
   n'était pas là. Julien ne connaissait pas la fonctionnalité : normal.

   ⚠ LES SIX TEINTES NE SONT PAS TOUCHÉES. Elles portent la décision « la
   teinte ne code rien, elle sépare » (lot 3), elles sont calibrées pour ne
   jamais imiter une couleur d'appareil, et les changer déplacerait le problème
   dans la palette au lieu de le régler dans la composition. Ce qui change, ce
   sont les TROIS COUCHES qu'on peut ajouter sans y toucher :

     · un VOILE blanc de 10 % au-dessus de la teinte — la tuile passe de 1,00 à
       1,36:1 contre le fond, et garde sa teinte, qui est tout son travail ;
     · une BORDURE `--txt3` — 4,21:1 contre le fond de baie, au-dessus du 3:1
       que la WCAG 1.4.11 demande à la limite d'un composant. C'est elle qui
       fait le gros du travail : un voile seul plafonne à 1,6 avant de délaver
       la teinte, une bordure d'un pixel dessine la boîte d'un coup ;
     · le TEXTE monte de `--txt2` à `--txt` — 8,98:1 sur la tuile voilée, contre
       6,85 avant. Il n'en avait pas besoin ; mais un nom de groupe est un objet
       nommé par Julien, pas de l'information secondaire, et il était le seul
       texte de l'appli à porter `--txt2` sur une surface teintée.

   LA HAUTEUR, ENFIN, ET C'EST L'AUTRE MOITIÉ. 16 px EN DUR pendant que le
   texte porte `--t-s`, qui vaut `10px × --fs` : au cran « Grande » (--fs 1,3,
   la préférence de Julien), 13 px de texte dans 16 px de boîte, sans place pour
   les jambages. Le bloc `pointer:coarse` donnait 32 px au doigt et rien au grand
   écran — le desktop était le seul à n'avoir jamais été mesuré. La hauteur suit
   donc l'échelle comme tout ce qui porte du texte : 18 px au cran normal,
   23,4 px à « Grande ». Six pixels de plus par baie qui a des groupes. */
.bandeau-groupe{
  grid-row:1; height:calc(18px * var(--fs)); margin:0 1px 3px; border-radius:var(--r-pastille);
  background:linear-gradient(rgba(255,255,255,.10), rgba(255,255,255,.10)), var(--g,#1a2027);
  border:1px solid var(--txt3);
  overflow:hidden; min-width:0;
  display:flex; align-items:center; padding:0 6px;
  font:var(--t-s) var(--mono); color:var(--txt); letter-spacing:.4px;
  cursor:pointer;
}
.bandeau-groupe > span{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* le nom reste ALIGNÉ À GAUCHE, à une colonne comme à vingt-quatre : à l'écran
   comme sur l'étiquette, l'œil cherche où le groupe COMMENCE. Tronqué à droite,
   il donne toujours ses premières lettres ; centré, il ne dirait plus rien. */
/* la case VIDE ne porte ni voile ni bordure : elle n'est pas un objet, c'est le
   trou entre deux groupes. Lui donner la boîte du bandeau dessinerait vingt-deux
   groupes fantômes sur une baie qui en a deux. */
.bandeau-groupe.vide{ background:none; border-color:transparent; cursor:default }
/* le seul contour de ce chantier : gris neutre, sous le pointeur seulement.
   Il ne survit pas au déplacement de la souris, donc ne peut pas se lire comme
   « sélectionné » — l'orange reste réservé au curseur et à la sélection. */
/* le survol ne peut plus se distinguer par la COULEUR du texte, qui est
   maintenant `--txt` au repos : il se distingue par le contour, qui passe de la
   bordure grise au blanc cassé. Toujours pas d'orange — il reste au curseur et
   à la sélection. */
.bandeau-groupe:hover{ border-color:var(--txt); outline:1px solid var(--line) }
.bandeau-groupe.vide[data-col]{ cursor:pointer }
.bandeau-groupe.vide[data-col]:hover{ background:rgba(255,255,255,.04) }

/* la tranche en cours de sélection : un fond très léger sur toute la hauteur,
   et un liseré sur les deux colonnes de BORD seulement, qui matérialise
   l'intervalle sans imiter le curseur de remplissage */
.col.tranche{ background:rgba(255,255,255,.045) }
.col.tranche.debut .num[data-colonne],
.col.tranche.fin .num[data-colonne]{ box-shadow:inset 0 -2px 0 var(--acc2) }

/* le popover de groupe reprend le patron de `.fgrid label`, en une colonne :
   16 px de bandeau ne portent ni bornes, ni déplacement, ni suppression —
   l'édition vit donc en popover ancré, jamais en place, jamais en modale */
.champ-groupe{display:flex; flex-direction:column; gap:4px; margin-bottom:8px}
.champ-groupe span{font:var(--t-s) var(--mono); color:var(--txt3); text-transform:uppercase; letter-spacing:.5px}
.champ-groupe input{width:100%}
#gRefus{color:var(--warn)}

/* ---------- la connectique refusée, pendant le geste ----------
   Le pendant visuel de `peindreCompatibilite`. Trois signaux, du plus large au
   plus fin — la baie entière hachurée, l'étiquette qui nomme la fiche refusée,
   le point survolé qui vire franchement au rouge — parce que le refus doit se
   lire de la périphérie de l'œil, qui suit le curseur, et non d'une bande de
   texte en bas d'écran que le geste ne regarde jamais.
   Aucun orange ici : l'invite `.bay.arming` et le refus sont exclusifs. */
.refus-tag{display:none}
.baie.refus{border-color:rgba(239,71,111,.5)}
.baie.refus .refus-tag{
  display:inline-block; font:var(--t-s) var(--mono); color:var(--err); white-space:nowrap;
  border:1px solid rgba(239,71,111,.5); border-radius:var(--r-pastille); padding:2px 5px;
  background:rgba(239,71,111,.10);
}
/* la hachure porte le refus sur toute la façade d'un coup, sans toucher aux
   52 px de chaque point : c'est la BAIE qui refuse, pas tel point */
.baie.refus .bay{
  background-image:repeating-linear-gradient(45deg,
    rgba(239,71,111,.075) 0 7px, transparent 7px 14px);
}
.baie.refus .point{
  border-color:rgba(239,71,111,.45); border-style:dashed; cursor:not-allowed; opacity:.5;
}
.baie.refus .point.filled{border-left-color:rgba(239,71,111,.45)}
/* le point sous le curseur : plein rouge, et le ⃠ dit ce que le bandeau explique */
.baie.refus .point.vise,
.baie.refus .point:hover{
  border-color:var(--err); background:rgba(239,71,111,.16); opacity:1;
}
/* LE SEUL `font-size` HORS DE L'ÉCHELLE `--t-*` dans tout le CSS d'écran, et il
   y est laissé exprès. Ce ⃠ n'est pas du TEXTE : c'est un dessin fait avec un
   glyphe, qui doit remplir un point de baie de 52 px de haut. Sa cote répond à
   la taille de la CASE qu'il barre, pas à une hiérarchie de lecture — le
   brancher sur `--t-2xl` le ferait flotter au milieu du point, et le premier qui
   changerait l'échelon pour un titre rétrécirait le signal de refus sans jamais
   savoir qu'il y touchait. Un rôle qui n'est pas dans les six n'entre pas dans
   les six de force. */
.baie.refus .point.vise::after{
  content:'⃠'; position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  color:var(--err); font-size:calc(20px * var(--fs)); line-height:1; pointer-events:none;
}

/* ---------- l'assistant de conf ----------
   Trois ajouts, et pas un de plus : la jauge qui déborde, le point proposé, le
   motif d'écart. Tout le reste de l'assistant réemploie le style existant —
   `.card`, `.kv`, `.reco`, `.note`, `.modal-foot`. Les cotes de texte suivent
   `--fs` comme partout ailleurs ; les bordures, les rayons et les hachures n'y
   sont PAS soumis, c'est la règle du `:root` en tête de fichier. */

/* 1. La jauge à débordement. `.bar` est bornée à 100 % par son `overflow:hidden`
      et par un seul `<i>` : elle sait dire « 24 places sur 24 », elle ne sait
      pas dire « il en faut 39 pour 24 places ». Deux segments et une butée
      règlent ça sans cas particulier dans le code appelant — quand le déficit
      est nul, le second segment n'est pas émis et la butée tombe à 100 %.
      `overflow:visible` est indispensable : la butée déborde de 3 px en haut et
      en bas, et son chiffre vit encore au-dessus. */
.bar.budget{height:9px; display:flex; overflow:visible; position:relative; background:var(--bg2)}
.bar.budget i{border-radius:0}
.bar.budget .tient{background:var(--acc)}
.bar.budget .deborde{background:repeating-linear-gradient(45deg,var(--err),var(--err) 3px,transparent 3px,transparent 6px)}
.bar.budget .butee{position:absolute; top:-3px; bottom:-3px; width:1px; background:var(--txt)}
/* `attr(data-cap)` et non un chiffre en dur : la capacité change avec les baies
   installées, et une valeur figée dans la feuille de style aurait menti dès la
   deuxième baie. C'est le seul emploi d'`attr()` du fichier, et il est légal
   partout sur `content` depuis toujours — ce sont les autres propriétés qui ne
   l'acceptent pas. */
.bar.budget .butee::after{
  content:attr(data-cap); position:absolute; top:-13px; left:-6px;
  font:var(--t-xs) var(--mono); color:var(--txt2);
}

/* 2. Le point PROPOSÉ — l'état neuf, ni vide ni posé : suggéré. MÊME GÉOMÉTRIE
      que `.point.filled` (1 px de bordure, 3 px de liseré), sans quoi la baie
      sauterait d'un pixel au moment d'appliquer et l'œil lirait un déplacement
      là où rien n'a bougé. Le liseré garde la couleur de l'appareil : la
      proposition se lit avec le même code couleur que le reste de la façade.
      La prévisualisation n'écrit RIEN — ces classes sont posées et retirées,
      exactement comme `.point.drop` du glisser. */
.point.propose{
  border:1px dashed var(--acc2); border-left:3px solid var(--sig);
  background:rgba(76,201,240,.07);
}
.point.propose .pd,.point.propose .pn{opacity:.9}
/* Le marqueur `▸` d'un point proposé — SEUL usage de `.tag` depuis #40, et
   cette règle porte donc tout ce qu'il lui faut. Rien de plus : ni contour, ni
   fond, ni rayon. Un repère de prévisualisation se lit, il ne se clique pas. */
.point.propose .tag{
  position:absolute; top:2px; right:3px;
  font:var(--t-xs) var(--mono); color:var(--acc2);
}
/* Le bandeau de mode d'une colonne touchée dit le mode PROPOSÉ, et il doit se
   distinguer du mode réel aussi discrètement que le point : même tireté, même
   accent. Sans cette ligne, la colonne annoncerait comme acquis un mode que
   « Annuler » remet à sa valeur d'avant — c'est le seul endroit de la
   prévisualisation où l'absence de marque serait un mensonge. Pas de liseré
   `--sig` ici : un bandeau n'appartient à aucun appareil. */
.mode.propose{border-color:var(--acc2); border-style:dashed; color:var(--acc2)}

/* 3. Le motif d'écart — une ligne repliable qui porte SA phrase, celle que le
      cœur rend déjà. Repliée par défaut : sur le studio réel les écartés se
      comptent en dizaines, et les déplier tous ferait de la modale un mur que
      personne ne lit. Le repli natif de `<details>` suffit et ne demande AUCUN
      état à mémoriser — donc aucun champ nouveau dans les JSON. */
.motif{border-bottom:1px solid rgba(255,255,255,.05)}
.motif:last-child{border:0}
.motif summary{
  display:flex; gap:8px; align-items:baseline; padding:6px 0; cursor:pointer;
  font-size:var(--t-l); list-style:none;
}
.motif summary::-webkit-details-marker{display:none}
.motif summary::before{content:'▸'; color:var(--txt4); font-size:var(--t-s); transition:transform .12s}
.motif[open] summary::before{transform:rotate(90deg)}
.motif b{color:var(--txt); font-variant-numeric:tabular-nums; min-width:22px; text-align:right}
.motif .quoi{color:var(--txt2); flex:1}
.motif .liste{padding:2px 0 8px 18px; font:var(--t-m) var(--mono); color:var(--txt3); line-height:1.7}

/* ==========================================================================
   LA BARRE COMPACTÉE — UNE CLASSE, ET NON UNE LARGEUR

   DEUX CONTRAINTES ÉTAIENT CONFONDUES EN UN SEUL SEUIL, ET C'EST L'ERREUR
   D'ORIGINE. La `@media (max-width:820px)` d'en dessous répondait à la fois à
   « la fenêtre est trop étroite pour la BARRE » et à « l'écran est trop court
   pour l'EMPILEMENT baie + inventaire ». Ce sont deux questions différentes,
   sur deux axes différents, et elles n'ont pas la même réponse :

   · la barre est une contrainte HORIZONTALE. Elle réclamait 1153 px à
     l'échelle « Compacte » et 1393 px à « Très grande » (mesuré,
     `width:max-content`) AVANT #9. Elle cessait donc de tenir bien avant
     820 px — vers 1300 px à l'échelle 1,3 de Julien. Entre 821 et ~1300,
     l'appli affichait la barre de bureau dans une place trop petite : la
     marque s'enroulait sur trois lignes, la barre montait à 111 px, et sous
     1150 px la page débordait de 173 px.

     ⚠ CE CHIFFRE A CHANGÉ DU TOUT AU TOUT AVEC #9, ET IL FALLAIT LE REMESURER,
     PAS LE RECOPIER. Contrôle, « à jour » et le marqueur de nouveautés sont
     sortis de cette barre : mesuré EN LOCAL (donc sans le sélecteur de
     configuration ni le bouton « qui est connecté » — le même angle mort que
     le reste de ce bloc documente déjà), la barre déployée ne réclame plus que
     304 à 394 px selon l'échelle, contre 1153 à 1393 avant. Le seuil de repli
     ne se déclenchera donc plus qu'à des largeurs de fenêtre extrêmes — un
     partage d'écran très serré —, jamais sur un usage ordinaire. Le mécanisme
     de mesure reste juste, il travaille seulement beaucoup moins souvent.
   · l'empilement est une contrainte de HAUTEUR et d'interaction (le splitter
     ne s'attrape pas au doigt). Elle n'a aucune raison de se déclencher parce
     qu'une fenêtre est étroite, et elle reste dans la `@media` d'en dessous.

   POURQUOI UNE CLASSE ET PAS UNE SECONDE `@media`. Le seuil de la barre dépend
   de `--fs`, et la mesure locale d'après #9 (304 · 327 · 362 · 394 px pour
   0,9 · 1 · 1,15 · 1,3, sans le sélecteur de configuration ni le compte) reste
   une droite croissante, comme avant le lot — seule l'ordonnée a changé, pas la
   forme de la relation. Une `@media` ne sait pas lire `--fs`, donc un seuil
   fixe resterait faux à trois crans sur quatre. Écartés aussi : quatre `@media`
   (une par cran) dupliqueraient ce bloc quatre fois, et `@container style()`
   n'est pas assez répandu pour une appli qu'on ouvre dans le navigateur qu'on a.

   `majBarreEtroite()` MESURE donc ce que la barre réclame — largeur naturelle
   contre largeur de fenêtre — et pose cette classe. Le nombre n'est écrit nulle
   part : il suit `--fs`, la longueur du libellé de `Contrôle` (« Contrôle » vs
   « 12 erreurs »), et l'arrivée du bouton « qui est connecté ».

   `:where(.etroite)` ET NON `.etroite` : la pseudo-classe est de spécificité
   NULLE, donc `.topbar:where(.etroite) .btn` pèse exactement autant que
   `.topbar .btn`. C'est ce qui laisse les deux `@media` d'en dessous — 820 px
   et 359 px — reprendre la main sur ces mêmes propriétés par simple ordre
   d'écriture, comme elles le faisaient quand ces règles vivaient chez elles.
   Sans le `:where()`, la classe gagnerait partout et le remplissage de bouton
   réglé à 320 px serait silencieusement annulé.
   ========================================================================== */

/* La marque garde ce qui IDENTIFIE (le point et « Patchbay ») et perd ce qui
   décrit — le sous-titre et la mention « Bêta » ne disent rien qu'on ait besoin
   de relire, et coûtent à eux deux une centaine de pixels sur la seule ligne où
   l'espace est disputé. Le corps descend de 16 à 14 px de base : douze pixels
   de plus rendus à une ligne qui les compte. */
.topbar:where(.etroite) .brand h1{font-size:var(--t-xl)}
.topbar:where(.etroite) .brand h1 em,
.topbar:where(.etroite) .brand .mention{display:none}

/* Le corps des boutons de la barre descend d'un point, et leur gouttière
   interne de 11 à 9 px. La HAUTEUR de cible ne bouge pas — elle vient du
   `min-height:40px` du bloc `pointer:coarse` plus bas, jamais du remplissage.
   C'est la seule compression appliquée aux commandes : on rogne la mise en
   forme, jamais un mot. */
/* ⚠ `.toolbar:where(.etroite)` REJOINT LA RÈGLE ICI (#9, second morceau) : la
   classe est MIROITÉE sur les deux bandes par `majBarreEtroite()` — un seul
   signal, « le chrome fixe est à l'étroit », qui replie les commandes des
   DEUX bandes ensemble plutôt que de laisser chacune décider seule si l'autre
   est gênée. */
.topbar:where(.etroite) .btn,
.toolbar:where(.etroite) .btn{font-size:var(--t-m); padding:6px 9px}
/* ⚠ SAUF LES PICTOGRAMMES, ET C'EST LA MÊME RÈGLE QU'AU-DESSUS D'UN CRAN : on
   rogne la mise en forme, jamais un mot — et une icône n'est pas un mot non
   plus, c'est la chose qu'il faut voir. Sans cette ligne le défaut du lot se
   ROUVRAIT là : `.topbar:where(.etroite) .btn` pèse (0,2,0), exactement comme
   `.btn.discret`, et il est écrit plus bas — il gagnait donc par simple ordre,
   et ramenait les glyphes à 11 px. Or c'est l'état où il ne reste QUE ça : la
   barre repliée masque `?`, `⚙` et les deux flèches, et `⋯` devient la seule
   porte vers les cinq commandes repliées. Coût mesuré au pire cran (1,3) :
   la barre repliée passe de 618 à 637 px, soit 19 px sur les 820 du seuil. */
.topbar:where(.etroite) .btn.discret,
.toolbar:where(.etroite) .btn.discret{font-size:calc(16px * var(--fs))}

/* #139 : « + appareil » reste visible avec `⋯`, même quand les autres actions
   sont repliées. Les raccourcis des étapes peuplées restent sur le schéma ;
   « + baie de brassage » reste sous les baies. Les commandes repliées portent un
   vrai libellé texte dans le menu `⋯`, construit par `renderMenu()` : c'est là
   que les glyphes muets (`?`, `⚙`, les deux flèches) cessent de dépendre d'une
   infobulle qui n'existe pas au doigt. Masquées en CSS et NON par un `hidden`
   en JS : leurs gestionnaires restent branchés, et les entrées de menu ne font
   que déclencher leur clic — aucune logique n'est dupliquée. `entreesRepliees()`
   lit LA MÊME classe que ces cinq lignes : le menu ne peut donc ni replier un
   bouton encore visible, ni l'oublier.

   ⚠ DEUX BANDES DEPUIS #9 (second morceau), ET NON PLUS UNE : `#btnAide` et
   `#btnPrefs` vivent dans `.topbar`, `#btnAnnuler` et `#btnRefaire` dans
   `.toolbar` — chacun replié sous la classe de SA propre bande, toutes deux
   posées ensemble par `majBarreEtroite()`. `⋯`, lui, reste visible dans les
   deux cas : c'est la seule porte vers ce qui vient de disparaître.
   ⚠ `#btnAuto` A QUITTÉ CETTE LISTE AVEC #80 : il ne vit plus dans `.toolbar`
   du tout, il est posé dans l'en-tête de la carte « À placer » du tiroir, qui
   ne se replie jamais — rien à masquer, rien à répliquer dans `⋯`. */
.topbar:where(.etroite) #btnAide,
.topbar:where(.etroite) #btnPrefs{display:none}
.toolbar:where(.etroite) #btnAnnuler,
.toolbar:where(.etroite) #btnRefaire{display:none}

/* LE FILET, ET IL N'EST PAS LE PLAN.

   CE BLOC A PORTÉ, AVANT #9, LA PREUVE QUE LA BARRE REPLIÉE NE TENAIT PAS
   TOUJOURS SUR UNE LIGNE — mesuré sur le compte réel en production, entre 717
   et 871 px selon l'échelle, contre 820 px de seuil : à 1,3 elle débordait déjà
   avant même ce lot-là. `flex-wrap:wrap` existait pour rattraper CE cas précis.

   ⚠ CONTRÔLE ET « À JOUR » VIENNENT DE QUITTER CETTE BARRE (#9), ET LE FILET
   N'A PLUS LA MÊME RAISON D'EXISTER. Mesuré EN LOCAL (donc sans le sélecteur de
   configuration ni le bouton « qui est connecté », le même angle mort que plus
   haut dans ce bloc), la barre repliée ne réclame plus que 180 à 227 px selon
   l'échelle — à comparer aux 717-871 d'avant. Même en ajoutant largement pour
   les deux inconnues locales, elle reste loin sous les 820 px du seuil : ce
   filet ne devrait plus se déclencher sur un usage ordinaire. Il reste posé
   parce qu'il ne coûte rien tant qu'il ne sert pas, et qu'une fenêtre partagée
   assez étroite peut toujours, en théorie, le solliciter — mais la preuve
   chiffrée qui le justifiait ne tient plus telle quelle après ce lot, et elle
   attend d'être refaite sur le compte réel si le besoin s'en fait sentir. */
.topbar:where(.etroite){flex-wrap:wrap; row-gap:6px}

/* ==========================================================================
   SOUS 820 px — ET IL N'Y A QU'UNE CAUSE À TRAITER

   Ce bloc est la PREMIÈRE `@media` de cette feuille : l'application n'avait
   jamais été essayée sous 1000 px. Ce n'est donc pas une régression, c'est un
   axe qui n'était pas ouvert.

   La cause, mesurée à 375 × 812 : `.topbar` est en `flex-wrap:nowrap` et ses
   trois blocs exigent 1065 px (brand 207 + vues 243 + actions 578 + 48 de
   gouttières). Le document ne peut pas se replier, le viewport de MISE EN PAGE
   passe donc à 1065 pendant que le viewport visuel reste 375, et la page
   entière glisse latéralement — en-tête et contenu ensemble. De là découlent
   presque tous les autres symptômes : sept boutons sur neuf hors écran, le
   tiroir (`position:fixed; right:0`) posé à x = 685 dans un champ de 375, et
   chaque `100%` / `vw` / `position:fixed` calculé contre une largeur qui
   n'existe pas.

   D'où la règle de ce bloc : on répare LA CAUSE, on ne rustine pas les
   victimes. En particulier `.modal-box` n'est PAS touchée — son
   `width:min(940px,100%)` est déjà juste, elle ne mesurait 940 px que parce
   que son `100%` valait 1065. Elle se répare toute seule ici.

   ⚠ CE BLOC A ÉTÉ ALLÉGÉ, ET IL FAUT SAVOIR DE QUOI. Tout ce qui REPLIAIT la
   barre — sous-titre et tag escamotés, corps des boutons, les cinq commandes
   passées dans `⋯` — vivait ici, et n'était donc vrai que sous 820 px. C'était
   la cause de la bande cassée 821–1250 px : la même barre de bureau dans une
   place trop petite pour elle. Ces règles sont remontées dans le bloc
   `.topbar:where(.etroite)` juste au-dessus, où une MESURE les déclenche.
   Ce qui reste ici est ce qui relève vraiment du petit écran : la grille à deux
   rangées de la barre (une ligne ne suffit plus sous 820 px, quel que soit le
   repli), l'empilement remplacé par des onglets, et le tiroir.
   ========================================================================== */
@media (max-width:820px){

  /* 1. DEUX RANGÉES DÉCLARÉES, ET NON DEUX RANGÉES ESPÉRÉES.

     Premier jet, avant #9 : `flex-wrap:wrap` sur `.topbar` et sur
     `.topbar-actions`. Ça ne débordait plus — mais ça se repliait où le calcul
     tombait, et le calcul tombait mal. Un repli automatique n'a aucun moyen de
     savoir laquelle des deux rangées porte les commandes.

     D'où la grille : deux colonnes, deux rangées, chaque élément à une place
     NOMMÉE. Rangée 1, la marque et `⋯` ; rangée 2, les deux vues et rien
     d'autre.

     DEUX COLONNES DEPUIS #9, QUATRE AVANT : Contrôle et le retour d'écriture
     ont déménagé dans `.bande-bas`, et `⋯` reste la SEULE action encore visible
     à ce cran — voir le commentaire de `.topbar:where(.etroite)` plus haut. Il
     n'y a donc plus qu'une colonne `auto` pour la marque et une pour `⋯`, ELLE
     AUSSI `auto` : sans commande de longueur variable à droite d'elle, rien
     n'a plus besoin d'être élastique sur cette rangée.

     `display:contents` sur `.topbar-gauche` et `.topbar-droite` : leurs
     enfants deviennent des éléments de la grille de `.topbar` sans qu'un seul
     nœud bouge dans `studio.html` — le même geste qu'`.topbar-actions`
     faisait seul avant #9 (second morceau), maintenant réparti sur les deux
     groupes qui l'ont remplacée. */
  .topbar{
    display:grid;
    grid-template-columns:minmax(0,1fr) auto;
    column-gap:8px; row-gap:6px; padding:8px; align-items:center;
  }
  .topbar-gauche{display:contents}
  .topbar-droite{display:contents}

  /* LA PLACE DE LA MARQUE DANS LA GRILLE, ET RIEN D'AUTRE. Ce qu'elle perd — le
     sous-titre, le tag, deux points de corps, l'ellipse de sécurité — est
     maintenant posé par `.topbar:where(.etroite)`, plus haut, et vaut donc bien
     avant 820 px. Sous 820 px la classe est TOUJOURS posée (`majBarreEtroite()`
     la force en mode compact, où la grille en dépend), ces règles-là sont donc
     acquises ici sans être répétées. La gouttière de 10 à 8 px, elle, est
     propre à la rangée serrée du téléphone. */
  .topbar .brand{grid-area:1/1; gap:8px}

  /* LE SÉLECTEUR DE CONFIGURATION REJOINT LA MARQUE EN RANGÉE 1 (#9, second
     morceau) — dans LA MÊME colonne que tenait `#btnPlus` avant que `⋯` ne
     déménage dans `.toolbar`. C'est un GAIN, pas une compression de plus :
     l'espace que `⋯` réclamait est rendu tel quel, et ce qui l'occupe
     maintenant est le fil d'Ariane que la barre de bureau porte déjà — la
     configuration ouverte rejoint la marque au lieu de rester couplée au
     compte (rangée 3, avant ce lot), qu'elle n'annonce pas.
     Le séparateur (`/`) ne survit pas au passage : les deux maillons vivent
     dans deux colonnes distinctes, un caractère entre elles ne collerait à
     rien. `#studio` porte seul le fil, comme le fait déjà `.studio::before`
     ci-dessous en effaçant son propre préfixe. */
  .crumb-sep{display:none}
  .studio{grid-area:1/2; justify-self:end; min-width:0; max-width:100%}
  /* le préfixe part : sur cette rangée-là, le nom de l'entité vaut mieux que
     l'étiquette qui l'annonce, et le popover porte déjà le mot en titre.
     `content:none` l'emporte sur `attr(data-mot)` comme il l'emportait sur le
     mot en dur : rien à changer ici le jour d'un autre renommage. */
  .studio::before{content:none}

  /* LA RANGÉE 2 EST AUX ONGLETS DE VUE, ET À RIEN D'AUTRE. Elle a porté le
     retour d'écriture le temps d'un essai, avant que #9 ne le déplace : les
     96 px qu'il prenait sortaient de « Schéma du signal », qui s'affichait
     « Schéma du si… ». Une troncature est le pire des deux maux — elle ne dit
     pas au lecteur ce qu'il a perdu.

     Pleine largeur (les deux colonnes) et parts égales : c'est une bascule à
     deux positions, et deux positions de tailles différentes se lisent comme
     une hiérarchie qui n'existe pas. Reste une compression de mise en forme —
     gouttière interne et interlettrage décoratif — qui donne 24 px de marge à
     320 px de large, là où le couple « Baies + pastille » / « Schéma du
     signal » n'en avait aucune. */
  .topbar .vues{grid-area:2/1/3/3; min-width:0}
  .topbar .vues .vue{flex:1; text-align:center; min-width:0; padding:4px 8px;
    letter-spacing:normal; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}

  /* `#btnAide` ET `#btnPrefs` SONT MASQUÉS PAR `.topbar:where(.etroite)`,
     plus haut, TOUJOURS posée sous 820 px : rien à leur donner comme place
     ici, comme c'était déjà le cas avant ce lot. `#btnAnnuler` et
     `#btnRefaire` ne vivent plus dans cette grille du tout — ils sont dans
     `.toolbar`, une bande entièrement séparée qui se replie de son côté (voir
     `.toolbar:where(.etroite)`, plus haut) et n'a pas besoin de grille : elle
     reste un flex, `⋯` poussé à droite par `margin-left:auto` sur
     `.toolbar-droite`, que sa moitié gauche soit vide (Baies) ou pleine
     (Schéma, qui s'enroule déjà toute seule).

     LE RETOUR D'ÉCRITURE ET SON `.save-state.repos` D'UN SEUL PIXEL, C'ÉTAIT
     ICI AVANT #9. Contrôle et « à jour » vivent maintenant dans `.bande-bas`,
     hors de cette grille, hors de ce fichier de règles — voir plus bas dans la
     feuille. Ce déménagement referme du même geste le §22 : `.save-state`
     n'occupe plus la même ligne que la marque, donc le mot « Patchbay » n'a
     plus jamais eu besoin de s'effacer pour lui faire de la place, et la règle
     `:has()` qui le faisait a disparu avec elle. */

  /* RANGÉE 3 : QUI JE SUIS, SEUL — et AUCUNE rangée en local, où `.moi` porte
     `hidden` et n'est pas un élément de grille. Le studio a rejoint la marque
     en rangée 1 (ci-dessus) : ce compte a TROIS RANGS au total, comme avant ce
     lot — marque + config, puis les vues, puis le compte —, jamais quatre.
     `justify-self:end` seul, sans second élément à sa gauche : la colonne 1
     de cette rangée reste vide plutôt que de forcer une largeur qu'aucun
     contenu ne réclame. */
  .moi{grid-area:3/2; justify-self:end; min-width:0; max-width:100%}

  /* Le seul élément du dépôt dont la largeur était réellement figée trop
     grande. `100vw` et non `100%` : il est en `position:fixed`, donc hors du
     flux et sans conteneur à mesurer. */
  .drawer{width:min(380px,100vw)}

  /* LA LIGNE DE LA BIBLIOTHÈQUE PASSE À DEUX RANGS, ET C'EST LE NOM QU'ON SAUVE.

     `.mrow` est une seule ligne de flex : le nom (`.mmeta`, élastique) contre
     les actions (`.acts`, `flex:none`). À 375 px les actions prennent presque
     tout, et le nom — le SEUL contenu qui dise ce qu'on regarde — tombait à
     62 px, c'est-à-dire à « Ma… ». Mesuré AVANT ce lot : déjà tronqué. Le
     vocabulaire l'a aggravé de 20 px (« GÉNÉRIQUE » contre « GÉNÉRALE »,
     « + prendre » contre « + studio »), et la pire ligne — celle qui porte la
     pastille `à moi`, donc trois icônes de plus — arrivait à ZÉRO pixel : une
     fiche sans nom.

     On ne rogne donc ni le mot ni les actions, on leur donne deux rangs. Le nom
     récupère la largeur entière et cesse d'être tronqué du tout — c'est mieux
     que l'état d'avant, pas seulement une compensation. Aucun effet au-dessus de
     820 px, où la ligne unique tient largement. */
  .mrow{flex-wrap:wrap}
  .mrow .mmeta{flex:1 0 100%}
  .mrow .acts{margin-left:auto}

  /* LA RECHERCHE PASSE À LA LIGNE, elle ne rétrécit pas jusqu'à l'illisible.
     Mesuré à 375 px et à l'échelle 1,3 de Julien : « INVENTAIRE » réclame
     101 px, la case et le bouton 202, et le champ retombait à 111 px — assez
     pour taper, pas pour lire ce qu'on a tapé — en poussant la rangée à 448 px,
     c'est-à-dire en rouvrant le débordement horizontal que le §14 étape 0 avait
     fermé. Une deuxième ligne, et elle ne coûte rien ailleurs : sur l'onglet
     « Baie », `.bandeau.reduit` masque le bandeau entier, et au-dessus de 820 px
     la rangée tient largement. */
  .panel-inv .panel-head{flex-wrap:wrap}
  .panel-inv .rech-inv{order:3; flex:1 1 100%; max-width:none; width:auto}

  /* 2. BAIE **OU** INVENTAIRE — voir le commentaire d'`index.html`. Le splitter
     disparaît avec l'empilement qu'il servait à régler. */
  .splitter{display:none}
  .onglets-mob{flex:none; align-self:flex-start; margin:8px 12px 0}
  .onglet{
    background:none; border:0; color:var(--txt3); padding:4px 14px; cursor:pointer;
    font-size:var(--t-m); font-weight:600;
    letter-spacing:.4px; border-radius:var(--r-controle);
  }
  .onglet.on{color:#161a1f; background:var(--acc)}

  /* `!important` assumé : `appliquerHauteurBandeau()` et `contraindreBandeau()`
     écrivent une hauteur EN LIGNE, et `setup.ui.hauteurBandeau` est une
     préférence en pixels héritée d'un écran de bureau (1638 px mesurés) qui n'a
     aucun sens ici. Le JS s'abstient déjà de l'écrire en mode compact ; cette
     ligne garantit qu'un résidu posé avant la bascule ne survive pas. */
  .bandeau{flex:1 1 auto; height:auto !important; min-height:0}

  .baies-scroll{padding:10px 12px}

  /* Le menu `⋯` porte cinq entrées de plus ici, et chacune est une cible de
     40 px : avec trois baies il dépasserait le bas de l'écran, et les deux
     gestes destructeurs — qui vivent en bas par décision du §3 — deviendraient
     les seuls inatteignables. Il défile plutôt que de déborder. Le popover est
     en `position:fixed`, d'où `vh` et non `%`. */

  /* SUR L'ONGLET « BAIE », LE BANDEAU NE GARDE QUE SA BANDE D'AIDE.
     `.hint` est la seule surface qui dise ce qu'on tient en main, et elle
     vivait dans le panneau d'inventaire, c'est-à-dire dans l'onglet d'où l'on
     PART. Elle reste donc là, seule, sous la baie. Coût au repos : zéro pixel —
     `.hint` est en `max-height:0` tant qu'elle est vide, et rien d'autre du
     bandeau n'est affiché. Voir `appliquerDispositionCompacte`. */
  .bandeau.reduit{flex:none; height:auto !important; min-height:0}
  .bandeau.reduit .panel-head,
  .bandeau.reduit #inventaire,
  .bandeau.reduit #places{display:none}
}

/* ==========================================================================
   SOUS 360 px — LE PLANCHER RÉEL DES TÉLÉPHONES EN SERVICE

   320 px de large (iPhone SE première génération, et le mode « texte agrandi »
   de plusieurs Android qui rétrécit le viewport CSS d'autant). C'était serré
   AVANT #9, quand la rangée 1 portait encore Contrôle et le retour d'écriture
   à côté de la marque — au point de devoir MASQUER le nom « Patchbay » pour
   tenir. Les deux ont depuis déménagé dans `.bande-bas`.

   ⚠ CETTE RANGÉE PORTE DE NOUVEAU DEUX ÉLÉMENTS DEPUIS LE SECOND MORCEAU DE
   #9 — la marque ET le sélecteur de configuration, qui a pris la colonne que
   `⋯` occupait avant de déménager dans `.toolbar` (voir la grille mobile plus
   haut). `.studio` porte déjà `min-width:0` et `max-width:100%` à ce cran :
   il cède la place à la marque avant de forcer un débordement, exactement
   comme `.moi` le fait déjà en rangée 3. Rien à ajouter ici pour lui.

   ⚠ MESURÉ APRÈS LE DÉMÉNAGEMENT DE #9 (PREMIER MORCEAU), ET C'EST CE QUI
   AVAIT FAIT TOMBER LE MASQUAGE DE LA MARQUE : `.topbar .brand h1{display:
   none}` ne servait plus à rien une fois Contrôle et le retour d'écriture
   partis. Le garder aurait fait disparaître le nom de l'application sans
   raison, sur la seule foi d'un commentaire qui datait d'avant le lot —
   exactement le défaut que « mesurer avant d'affirmer » existe pour éviter.
   Vérifié À NOUVEAU avec ce second morceau (§9 du rapport) : à 320 px, marque
   et sélecteur affichés, `.topbar.scrollWidth` égale toujours sa
   `clientWidth` — la classe `.etroite` compacte déjà la marque (sous-titre et
   mention masqués) avant qu'on en arrive là.

   La hauteur de cible ne bouge toujours pas (`min-height:40px`, bloc
   `pointer:coarse`). */
@media (max-width:359px){
  .topbar{column-gap:4px}
  /* `⋯` est désormais dans `.toolbar`, et non plus dans cette barre : c'est
     donc là, et non plus ici, que vit la même compression — voir le bloc
     `.toolbar` juste en dessous. Rien ne reste à compresser dans `.topbar` à
     ce cran : `#btnAide` et `#btnPrefs` sont hors écran (`.etroite`), et
     `.studio`/`.moi` sont déjà des jetons de texte, pas des `.btn`. */
  .toolbar{column-gap:4px}
  .toolbar .btn{padding:6px; font-size:var(--t-s)}
  /* même raison qu'au repli, et même piège de spécificité : le remplissage se
     rogne, le pictogramme non. Ici `⋯` est la seule icône encore à l'écran. */
  .toolbar .btn.discret{padding:6px; font-size:calc(16px * var(--fs))}
  /* Le retour d'écriture n'est plus dans cette barre (#9), mais la règle
     rend le même service dans `.bande-bas` : au plancher réel des téléphones,
     un message plus long qu'« à jour » se lit encore sans forcer un second rang. */
  .save-state{font-size:var(--t-xs)}
}

/* Le pendant explicite : au-dessus de 820 px les onglets n'existent pas, quel
   que soit l'état laissé par le JS. Le desktop doit retrouver EXACTEMENT son
   empilement baie + splitter + inventaire. */
@media (min-width:821px){
  .onglets-mob{display:none !important}
}

/* ==========================================================================
   AU DOIGT — `pointer:coarse`, ET NON une largeur

   C'est la MODALITÉ D'ENTRÉE qui décide de la taille d'une cible, pas la place
   disponible : une tablette de 1024 px se touche, une fenêtre de navigateur
   rétrécie à 700 px se clique. Mesuré sur le studio réel : 124 cibles sous les
   44 px du seuil Apple (Material dit 48), dont 42 `.ia` de 23 × 21 px collées
   à 1 px l'une de l'autre — quatre commandes dont l'une retire un appareil.

   `.point` (66 × 68 px) n'apparaît nulle part ici : c'est la seule surface de
   l'appli qui ait été dessinée comme une CIBLE et non comme du texte, et elle
   est déjà bonne. L'agrandir ne servirait qu'à faire déborder la baie.
   ========================================================================== */
@media (pointer:coarse){

  /* Poser / éditer / dupliquer / retirer. L'écart compte autant que la taille :
     à 1 px de gouttière, « dupliquer » et « retirer » sont la même cible. */
  .ia{min-width:36px; min-height:36px; padding:6px; justify-content:center}
  .dev-head .acts{gap:6px}
  .baie-head .acts{gap:6px}

  /* Quatre `.ia` de 36 px tiennent mal dans une colonne d'inventaire de 212 px :
     le nom de l'appareil y tomberait à une quarantaine de pixels. On élargit la
     colonne plutôt que de rétrécir les cibles — sur un téléphone `columns` n'en
     rend de toute façon qu'une seule, pleine largeur. */
  .grille{columns:260px}

  .mode{height:32px}
  /* B6 — le libellé « mode » de la gouttière suit la case qu'il nomme, ici
     comme à 15 px : sans cette ligne il décrochait de 17 px au doigt, soit
     plus que sa propre hauteur. */
  .col.gouttiere .rang-mode{height:32px}
  .port{min-height:40px; padding:6px 8px}
  /* le pli d'une carte est le geste qui rend un port posé consultable : au
     doigt il vaut une rangée de port, pas une ligne de texte de 9 px — c'est
     la leçon déjà tirée sur `.depli rect` côté schéma. */
  .dev .depli{min-height:40px}
  .btn{min-height:40px}
  .btn.discret{min-width:40px}
  .vue,.onglet{min-height:40px}
  /* Le menu `⋯` devient LE plan de commande du mobile (cinq entrées repliées y
     arrivent) : ses lignes sont des cibles à part entière, plus des lignes de
     liste. */
  .menu-item{min-height:40px}
  /* `flex:none` : `.check` est un conteneur flex, la case y était rabotée
     à 17 px de large pour 20 de haut — une cible qui n'est carrée que sur le
     papier. */
  .check input[type=checkbox]{width:20px; height:20px; flex:none}

  /* LE PLANCHER DE 16 px, ET POURQUOI IL S'ÉCRIT `max()`. Sous 16 px, iOS
     zoome tout seul à la mise au point d'un champ — et ne dézoome pas en
     sortant : on se retrouve dans le débordement horizontal qu'on vient
     justement de tuer. Les cotes du projet sont toutes en
     `calc(Npx * var(--fs))` (voir le bandeau de `--fs` en tête de fichier) ;
     écrire `16px` sec casserait l'échelle vers le haut, où 12 × 1,3 = 15,6 px
     doit pouvoir continuer de grandir. `max()` pose un plancher sans plafonner.
     Les sélecteurs sont recopiés à l'identique de leurs règles d'origine : un
     `input` nu (0,0,1) perdrait contre `input[type=text]` (0,1,1). */
  select,textarea,
  input[type=text],input[type=email],input[type=number],input[type=search],
  .pe-r input[type=text],.pe-r select{
    font-size:max(16px, var(--t-l));
  }

  /* LIBÉRER UN POINT CESSE DE DÉPENDRE D'UN SURVOL ET D'UN DOUBLE-TAP.
     Le `✕` était en `opacity:0` hors `.point:hover` — invisible au doigt —, et
     l'autre chemin, le double-clic, est intercepté par le zoom du navigateur :
     le geste était fermé DEUX FOIS. Il s'affiche donc en permanence ici, et
     grandit à 24 px : à 14 px il était visible sans être atteignable, ce qui
     est la même impasse avec un pixel de plus.
     Le `ondblclick` reste branché pour la souris — on ne retire pas un chemin
     qui marche, on en ouvre un second. */
  .point .kill{opacity:1; width:24px; height:24px; bottom:2px; right:2px}
  .point .kill svg{margin:auto}

  /* ET DONC LE POINT GRANDIT DE DIX PIXELS — la seule exception au bandeau de
     tête de ce bloc, qui écrit que `.point` est déjà une bonne cible et n'a pas
     à grossir. Ce n'est justement PAS une histoire de cible : c'est que le `✕`
     est ici PERMANENT et large de 24 px, là où il n'en fait que 14 et ne paraît
     qu'au survol à la souris. Depuis que le nom de port s'enroule sur deux
     lignes, ces 24 px se posaient sur la seconde — « Ctrl Room R » sans son
     « R » sur un téléphone, vérifié en séance. Trois issues, deux écartées :
     rétrécir le `✕` défait le geste que le §14.5 étape 2 venait d'ouvrir
     (« à 14 px il était visible sans être atteignable ») ; étendre sa zone
     tactile par un pseudo-élément — le procédé de la poignée de colonne, juste
     au-dessous — la ferait déborder sur le TEXTE du point, or le geste ainsi
     étendu SUPPRIME : la poignée, elle, déborde sur du vide. Reste la hauteur,
     qui ne coûte que du défilement vertical dans une vue qui en a déjà, et rien
     du tout en largeur — l'objection du bandeau de tête portait sur la largeur.
     La même inéquation qu'en haut de fichier, avec 26 px sous le bouton au lieu
     de 15 : `h ≥ 28 + 37,8 × fs`, donc 70 et non 58. */
  .bay{--h-point:calc(70px * var(--fs))}

  /* LA POIGNÉE DE COLONNE : 14 px de haut, et c'est par elle qu'on ouvre le
     popover qui porte « Déplacer la colonne ». Une cible de 14 px rend ce geste
     injouable au doigt avant même qu'il soit écrit.

     La BOÎTE ne peut pas grandir : le numéro vit dans la grille de la baie,
     l'épaissir descendrait toute la façade, et l'état sélectionné
     (`.col[data-selection] .num` — un pavé orange plein) deviendrait un bandeau
     de 40 px. C'est donc la ZONE TACTILE seule qui grandit, par un
     pseudo-élément hors du flux : la poignée reste visuellement fine.

     Le débord est ASYMÉTRIQUE, et c'est tout le sujet — il est borné par ses
     deux voisins, qu'on ne vole pas :
     — vers le BAS, 3 px : exactement la gouttière `.col{gap:3px}`. Le point A
       commence après, et pas un pixel de lui n'est recouvert.
     — vers le HAUT, 10 px : le `padding-top` de `.bay`, c'est-à-dire du vide,
       quand la baie n'a pas de groupe. Quand elle en a, le bandeau de groupe
       occupe cette rangée et le débord passerait DEVANT lui — un pseudo-élément
       positionné peint au-dessus d'un frère statique, quel que soit l'ordre du
       DOM. D'où la ligne suivante, qui n'est pas un réglage de cible mais un
       garde-fou : elle rend au bandeau de groupe la priorité sur MON débord.
     Résultat mesuré : 27 px de haut sur une baie sans groupe, 20 px sur une
     baie qui en a, contre 14 px avant, sur toute la largeur de la colonne. Ce
     n'est pas les 44 px du seuil Apple — les atteindre demanderait soit de
     décaler la façade, soit de mordre sur la rangée A, et les deux sont exclus. */
  .bandeau-groupe{position:relative; z-index:1}
  .num[data-colonne]{position:relative}
  .num[data-colonne]::after{
    content:''; position:absolute; left:-2px; right:-2px; top:-10px; bottom:-3px;
  }

  /* LES DEUX POIGNÉES DE L'APPUI LONG CESSENT D'ÊTRE DU TEXTE À SÉLECTIONNER.
     Un appui long sur du texte ordinaire déclenche la sélection d'Android et la
     loupe d'iOS — le MÊME geste que celui qu'on vient d'ouvrir, servi par le
     système avant nous. `contextmenu` est déjà refusé côté JavaScript pendant
     toute la durée du contact ; ces deux propriétés-ci ferment l'autre moitié,
     celle que `preventDefault` n'atteint pas.
     Le périmètre est exactement celui des poignées — un point de baie, un
     numéro de colonne : rien ailleurs ne devient insélectionnable, et surtout
     pas les noms d'appareils ni les libellés que l'on peut vouloir copier. */
  .point[data-point], .num[data-colonne]{
    -webkit-user-select:none; user-select:none; -webkit-touch-callout:none;
  }

  /* ── F2 — LES SIX FAMILLES CLIQUABLES QUE LA VAGUE 3 AVAIT LAISSÉES DEHORS ──

     Elles n'ont rien en commun sauf d'être des cibles, et c'est justement
     pourquoi elles avaient été oubliées : aucune ne ressemble à un bouton.
     (`.num[data-colonne]` a été traité plus haut, avec la vague 2.) */

  /* Le bandeau de groupe — 16 px, et il porte le seul geste qui ouvre l'éditeur
     de groupes. Ici la BOÎTE grandit, contrairement à la poignée de colonne :
     elle vit dans la rangée 1 de `.bay`, qui est en `auto`, donc l'agrandir
     descend la façade de 16 px sans décaler quoi que ce soit à l'intérieur —
     la gouttière et les points sont tous en rangée 2 et gardent leur
     alignement. Seize pixels de défilement en plus sur les baies qui ont des
     groupes, contre un geste jouable : le change est bon.

     P3 — LE PLANCHER RESTE 32 px EN DUR, ET NE SUIT PAS `--fs`. La règle de
     base porte maintenant `calc(18px * var(--fs))`, qui vaut 23,4 px au cran
     « Grande » : sous le seuil tactile. 32 px n'est pas une cote de texte, c'est
     la taille d'un doigt — la faire varier avec la préférence de lecture ferait
     manquer la cible au cran « Compacte ». `max()` plutôt qu'un écrasement sec :
     un futur cran d'échelle au-delà de 1,8 dépasserait 32 px, et la boîte doit
     alors suivre son texte, pas le rogner. */
  .bandeau-groupe{height:max(32px, calc(18px * var(--fs)))}

  /* La pastille n'est PLUS une cible : elle n'a donc plus à faire 32 px au
     doigt. Elle reste un peu aérée pour rester lisible à côté du mot, et rien
     de plus — l'ancienne taille avait pour seule raison qu'on la visait « au
     milieu d'une autre », ce qui était le défaut, pas la contrainte. */
  .vue em,.onglet em{padding:4px 7px; margin-left:8px;
    display:inline-flex; align-items:center; justify-content:center}

  /* Les trois familles de pastilles et d'onglets de fenêtre. `min-height`
     seul — leur largeur vient de leur texte et suffit déjà. */
  .chip{min-height:32px; display:inline-flex; align-items:center}
  .tab{min-height:40px}
  .puce{min-height:36px}

  /* Le repliable des écarts de l'assistant : un `<summary>` est une cible, même
     s'il n'en a pas l'air. Sa hauteur venait de son seul remplissage. */
  .motif summary{min-height:36px; align-items:center; padding:8px 0}

  /* LES PORTS LIBRES DU SCHÉMA CESSENT D'ATTENDRE UN SURVOL. `.pport.libre`
     éteint son nom (`--txt4`) et son plot (50 %) et ne les rallume qu'au
     `:hover` — c'est-à-dire jamais au doigt : la moitié libre du schéma était
     illisible sans qu'aucun geste puisse la révéler. Même défaut de modalité
     que les infobulles et que le `✕` d'un point, même correctif.
     Le contraste RESTE hiérarchisé — un port libre reste plus discret qu'un
     port relié (`.pport.relie .pnom{fill:var(--txt)}`), il cesse seulement
     d'être un secret : `--txt2` au lieu de `--txt4`. */
  .pport.libre .pnom{fill:var(--txt2)}
  .pport.libre .plot{opacity:.85}
}


/* ==========================================================================
   LE JOURNAL DES NOUVEAUTÉS — le marqueur et son popover (#41)

   ⚠ CE BLOC EST EN FIN DE FEUILLE POUR UNE RAISON DE CASCADE, et non par
   négligence de rangement : sa `@media` de fin ajuste la largeur du popover au
   doigt, et un ajustement ne peut se faire qu'APRÈS la règle qu'il corrige, à
   spécificité égale.

   ⚠ ET IL N'INVENTE AUCUN ONGLET. Les onglets du popover sont `.vues` / `.vue` /
   `.vue.on`, c'est-à-dire EXACTEMENT le composant de la barre de navigation :
   fond d'accent plein, texte sombre pour l'actif, et la pastille de compte qui
   s'inverse avec lui (`.vue.on em`). Une première version leur donnait un gris à
   peine plus clair que le fond : on ne voyait pas lequel était ouvert, ce qui
   est le seul travail d'un onglet. Trois jeux d'onglets dans l'application, un
   seul habillage — ce qui se règle ici ne peut donc être que la PLACE, jamais
   l'apparence.
   ========================================================================== */

/* ---------- le marqueur ----------

   IL VIT DANS LA BANDE DU BAS DEPUIS #9 — voir `<footer class="bande-bas">`
   dans `studio.html`. Il y était PROVISOIREMENT dans la barre du haut ; la
   mention a disparu avec le déménagement, pas avant : un test (`JN-25`) gelait
   cette consigne pour que personne ne l'oublie en route.

   Fond d'accent plein et texte sombre : c'est le traitement de ce qui APPELLE
   dans cette feuille (`.vue.on`, `.btn.primary`), et le marqueur est le seul
   élément de la bande dont l'existence même est un message. Il n'a pas de forme
   au repos, puisqu'au repos il n'existe pas.

   ⚠ LE PRIX À SURVEILLER, ET IL N'EST PAS NUL : l'accent sert maintenant à
   quatre choses — l'onglet de vue actif, ce marqueur, la pastille de la file, et
   les onglets du popover. Popover ouvert, trois sont à l'écran ensemble. Le
   remède n'est pas de dépareiller les onglets, c'est de regarder les autres
   emplois de l'accent. */
.marqueur{
  flex:none; background:var(--acc); color:#161a1f; border:0;
  border-radius:var(--r-pastille); padding:3px 10px;
  font:var(--t-m) var(--mono); font-weight:600; letter-spacing:.02em;
  cursor:pointer; white-space:nowrap; min-width:0; max-width:100%;
  overflow:hidden; text-overflow:ellipsis;
}
.marqueur:hover{filter:brightness(1.1)}

/* DEUX FORMES, ET LE JS N'EN CHOISIT AUCUNE : il écrit les deux, la feuille
   décide. Sous #9 la bande du bas est un FLEX qui s'enroule (`flex-wrap`), pas
   une grille mesurée comme l'était `.topbar` : la décision n'a donc plus besoin
   d'une classe posée par mesure, une largeur d'écran suffit — la même que celle
   où le reste de l'interface bascule au doigt.

   Au doigt, le marqueur tombe à son chiffre seul : il est le seul élément de la
   bande qui puisse se réduire ainsi sans rien perdre — un état comme « à jour »
   ne se dessine pas, il se lit. */
.marq-court{display:none}
@media (max-width:820px){
  .marq-long{display:none}
  .marq-court{display:inline}
}

/* ---------- le popover ----------

   Plus large que les autres popovers (320 px) parce qu'il porte des PHRASES et
   non des libellés de commande : à 320 px, « Des prises ont été ajoutées,
   retirées ou changées de sens » tombe sur quatre lignes. Le second terme du
   `min()` le garde dans une fenêtre étroite, comme `.menu-pop`.

   `padding` plus généreux et `gap:0` : `.menu-pop` est une colonne d'items
   espacés de 2 px, ce popover-ci est un DOCUMENT — ses marges sont portées par
   ses paragraphes, qui savent lesquelles annuler entre voisins. */
.menu-pop.journal-pop{
  min-width:min(280px, calc(100vw - 12px));
  max-width:min(420px, calc(100vw - 12px));
  padding:12px 14px; gap:0;
}

.jr-tete{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:10px 14px; flex-wrap:wrap; flex:none;
  padding-bottom:10px; margin-bottom:10px; border-bottom:1px solid var(--line);
}
.jr-tete b{font-size:var(--t-xl); color:var(--txt)}
/* LE POINT DE REPÈRE, EN TOUTES LETTRES — et c'est la dernière LECTURE, jamais
   la dernière visite : quelqu'un qui vient trois fois sans ouvrir ce panneau
   perdrait deux fournées. La date de chaque changement est l'AUTRE date, et elle
   ne s'écrit que sur les groupes, le jour où elle varie. */
.jr-tete span{font:var(--t-s) var(--mono); color:var(--txt4)}

/* La barre d'onglets. `flex-wrap` : à 375 px, trois onglets et leurs comptes ne
   tiennent pas sur une ligne, et une troncature ne dirait pas au lecteur ce
   qu'il a perdu. L'interlettrage décoratif de `.vue` part pour la même raison —
   il coûte quelques pixels par onglet sans rien apprendre à cette taille. */
.jr-onglets{flex:none; flex-wrap:wrap; margin-bottom:12px}
.jr-onglets .vue{padding:4px 9px; letter-spacing:normal}

.jr-corps{min-width:0}

/* L'INTERTITRE QUI GROUPE — c'est le « où » du §2, sorti de la ligne. Répété sur
   chaque ligne il devenait une colonne de redites ; en titre, il ne se dit
   qu'une fois. Sans-serif, clair, une seule capitale : rien de commun avec la
   pastille de type, et c'est le point — l'œil doit distinguer un titre de
   section d'une étiquette de ligne sans avoir à lire. */
.jr-grp{
  margin:16px 0 5px; font-size:var(--t-l); font-weight:600; color:var(--txt);
  display:flex; align-items:baseline; gap:8px;
}
.jr-corps > .jr-grp:first-child{margin-top:0}
.jr-grp em{
  font:var(--t-xs) var(--mono); font-style:normal; color:var(--acc);
  background:rgba(255,159,28,.12); padding:1px 5px; border-radius:var(--r-pastille);
}
/* Le jour, poussé au bout de son titre et JAMAIS répété sur les lignes : six
   entrées du même jour n'ont pas besoin qu'on le dise six fois. Il n'est même
   écrit du tout que s'il y a plusieurs jours à l'écran (`plusieursJours`). */
.jr-jour{margin-left:auto; font:var(--t-s) var(--mono); font-weight:400; color:var(--txt4)}

/* Une ligne : sa pastille de type, sa phrase, et rien d'autre. */
.jr-ent{
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:10px;
  align-items:start; padding:5px 0; font-size:var(--t-m); color:var(--txt2); line-height:1.55;
}
/* UNE PASTILLE, forme qu'un titre n'a jamais — et NEUTRE. Quatre types colorés
   feraient quatre catégories à mémoriser pour un bénéfice nul : personne ne
   cherche « les améliorations ». La couleur ne range pas, elle appelle, et dans
   cet écran c'est le marqueur qui appelle. */
.jr-type{
  justify-self:start; font:var(--t-xs) var(--mono); text-transform:uppercase;
  letter-spacing:.06em; padding:3px 7px; border-radius:var(--r-pastille);
  background:var(--bg3); color:var(--txt3); white-space:nowrap;
}
/* Un type illisible dans le fichier ne fabrique pas une pastille vide : il perd
   sa pastille, et la phrase glisse à sa place. */
.jr-type.vide{background:none; padding:0}

.jr-sous{margin:0 0 7px; font-size:var(--t-m); color:var(--txt3); line-height:1.55}
.jr-sous b{color:var(--txt2); font-weight:600}
.jr-fiches{display:flex; flex-wrap:wrap; gap:5px; margin:0}
.jr-ech{margin:0; font:var(--t-m) var(--mono); color:var(--txt2); line-height:1.6}
.jr-plus{color:var(--txt4)}
.jr-pied{margin:16px 0 0}
.jr-vide{margin:0; font-size:var(--t-m); color:var(--txt3); line-height:1.55}
/* La coupe se DIT et se FRANCHIT — « Voir 12 lignes de plus » est un bouton, et
   il porte donc `.btn.mini` comme celui du catalogue, dans le même `.jr-pied`.
   Il n'y a rien à styler de plus : une règle propre ici dirait qu'il est d'une
   autre espèce, alors que c'est le même geste — le journal ROUTE, et il route
   ici vers son propre passé. */

/* ---------- au doigt ----------

   PLUS DE PLACE À RÉCLAMER DANS LA GRILLE DE `.topbar` DEPUIS #9 : le marqueur
   a quitté la barre du haut pour `.bande-bas`, un FLEX qui s'enroule tout seul
   (`flex-wrap`, voir plus bas) — sans grille, il n'y a pas d'auto-placement à
   craindre, et `.moi` n'a donc plus rien à céder. */
@media (max-width:820px){
  /* Le popover prend la largeur qu'on lui laisse : à 375 px, `min-width` le
     ferait déborder s'il valait 280 px fixes — d'où le `min()` de sa règle. Ici
     on ne règle que le confort de lecture. */
  .journal-pop{padding:11px 12px}
}

/* ⚠ #88 — LES ÉLÉMENTS TECHNIQUES, JAMAIS RENDUS. Un `<input type=file>` et le
   `<a download>` créé le temps d'un `.click()` de synthèse : on les ÉMET, on ne
   les clique pas. Ils portent cette classe pour que le garde de « clic dans le
   vide » les reconnaisse — un élément qui n'est pas à l'écran ne peut pas être
   cliqué par mégarde. `display:none` n'est pas décoratif ici : c'est ce qui rend
   l'exemption légitime, et `MQ-88` le gèle. */
.hors-vue{display:none}
