/* ---------------------------------------------------------------------------
   PALETTE — l'extension du 5C, la charte QARNITAlab.

   Elle a remplacé celle de la maquette « Plan d'abord », qui était bleue. Il
   n'y a pas de bleu dans la charte : encre #16181A et minium #D8542A sont
   maîtres, tout le reste leur est subordonné. Les neutres sont légèrement
   CHAUDS, base jaune-rouge — un gris neutre ferait virer le minium au sale.

   Les noms disent le RÔLE et pas la teinte : c'est ce qui a permis de changer
   toute la palette sans renommer une seule variable.

   QUATRE VALEURS DÉVIENT DE LA CHARTE, et chacune pour la même raison : la
   charte est écrite pour du support, où le fond est toujours du papier. Une
   interface a des fonds que le papier n'a pas. Aucune n'est un changement de
   teinte : ce sont des ajustements de clarté sur la même famille.

     #D8542A en TEXTE sur fond clair — 3,86:1, sous le seuil AA. Le texte
       d'accent prend donc la brique #A33A1C (6,33:1), que la charte donne
       elle-même pour « minium sur fond clair très saturé ». Le minium reste le
       minium partout où il est un aplat et non une lettre.
     #D8542A en APLAT portant du blanc — 4,02:1. L'aplat d'action est foncé du
       minimum qui fait passer le blanc : #C4471F (4,92:1). Son état pressé est
       la brique de la charte.
     #E3C34A sur fond clair — 1,65:1, invisible même en filet. Le jaune signal
       garde sa place sur fond sombre (10,3:1) et prend un pendant sombre,
       #8A6A18 (4,84:1), sur fond clair. Même logique que l'abricot, qui est le
       pendant clair du minium sur fond sombre.
     #7A736A en troisième niveau de texte — 3,66:1 sur le sable. Il porte des
       libellés, pas des légendes : relevé à #6B655C (4,52:1).

   ET UNE CINQUIÈME, qui n'est pas un ajustement mais un choix :

     #8E2F2F, le rouge d'erreur, ne rend que 2,20:1 sur l'encre. Or tout rouge
       lisible sur fond sombre est un rouge CLAIR, donc voisin de l'abricot :
       #D96A6A n'en est qu'à 16° de teinte et 1,21:1 de contraste — les deux
       seraient indiscernables. Sur fond sombre l'erreur passe donc au CARMIN
       #E0687F, à 27° de l'abricot. La clarté ne pouvant plus séparer les deux,
       c'est la teinte qui s'en charge. Sur fond clair, le rouge sombre de la
       charte convient (7,75:1) et reste en place.

   Pour mémoire, le bleu séparait l'accent du danger de 137° de teinte. Le
   minium ne peut pas en faire autant : c'est le prix de la charte, et la
   raison pour laquelle les fonctionnels sont mesurés un par un.
   --------------------------------------------------------------------------- */

:root {
  /* Les cinq plans, du fond vers l'avant. Ils sont CHAUDS, base jaune-rouge :
     un gris neutre ferait virer le minium au sale. L'échelle est bâtie autour
     de l'encre #16181A, qui est la surface sombre de la charte. */
  --app: #101113;          /* derrière tout */
  --barre: #16181A;        /* barres, panneaux flottants, rail — l'encre */
  --scene: #1B1D1F;        /* le plan */
  --champ: #212325;        /* fond d'un champ de saisie */
  --carte: #1E2023;        /* le fond d'une carte : au-dessus du contenu, qui est en retrait (--app) */
  --overlay: #141517;      /* palette de commandes, menus */

  /* Les filets. Aucun n'a à se voir : ils délimitent, ils ne dessinent pas.
     1,3:1 contre leur fond pour les plus discrets, 1,5:1 pour ceux qui portent
     une séparation qu'on doit suivre des yeux. */
  --filet: #2C2E30;
  --filet-panneau: #34373A;
  --filet-champ: #34373A;
  --filet-actif: #F0784E;  /* 5,63:1 contre le champ — c'est un repère */
  --filet-overlay: #34373A;

  /* Surfaces d'appui : un onglet, une touche au repos, une ligne choisie.
     `--neutre` est la « surface sombre secondaire » de la charte ; les deux
     surfaces de choix sont un voile de minium, dérivé — la charte donne le
     voile clair (#F7E5DC) mais pas son pendant sombre. */
  --neutre: #2A2C2E;
  --choix: #2E1A12;
  --choix-fort: #3A2117;

  /* Trois niveaux de texte, et pas six. */
  --texte: #FBFAF8;        /* le papier de la charte */
  --texte-2: #D6D1CA;
  --texte-3: #A29A90;      /* le tertiaire de la charte : 6,4:1 sur l'encre */

  /* LA MARQUE. Deux teintes, et deux seulement : l'encre du mot et le minium
     qui n'accentue que `lab` et la branche droite du symbole. Elles ne servent
     qu'au logotype — l'interface garde sa propre palette.

     Les valeurs ne se transposent pas d'un thème à l'autre, la charte le dit et
     la mesure le confirme : sur la barre sombre, le minium #D8542A ne rend que
     4,42:1 quand le #F0784E éclairci monte à 6,36:1 ; sur le blanc, ce même
     #F0784E s'effondre à 2,80:1 là où le #D8542A tient 4,02:1. Chaque fond a
     donc son minium, et l'échanger abîmerait les deux. */
  --marque-encre: #FBFAF8;
  --marque-accent: #F0784E;

  /* ---------------------------------------------------------------------
     LES SIGNAUX

     Une seule règle les gouverne : **le minium est réservé à l'action et à la
     marque.** S'il colorait aussi des données ou des états, il perdrait son
     pouvoir de guidage — on ne saurait plus, d'un coup d'œil, où l'on peut
     agir.

     Le bleu ne disparaît pas par goût : il n'y a pas de bleu dans la charte.
     Ce qu'il faisait, le minium le fait maintenant.
     --------------------------------------------------------------------- */
  --accent: #F0784E;       /* l'abricot : sélection, valeur active, lien */
  --action: #F0784E;       /* la touche qui engage — elle porte de l'ENCRE */
  --action-texte: #16181A; /* 6,36:1 sur l'abricot ; le blanc n'y tiendrait pas */
  --alerte: #E3C34A;       /* le jaune signal : 10,3:1 sur l'encre */
  --succes: #6FA383;
  --succes-texte: #6FA383;
  --danger: #E0687F;       /* voir plus bas : pourquoi un carmin et pas un rouge */

  /* Largeur de la barre d'outils. Deux règles en dépendent : la colonne de la
     grille, et le décalage du panneau de structure qui commence après elle. */
  --rail-largeur: 3.25rem;

  /* Épaisseur des réglettes graduées du plan.

     ZÉRO par défaut : seul l'atelier en a, et le catalogue emploie les mêmes
     panneaux flottants sans en avoir. C'est `workspace.js` qui pose la vraie
     valeur au démarrage — il les dessine, il est seul à en connaître
     l'épaisseur, et deux nombres à tenir d'accord finissent par diverger. */
  --reglette: 0px;

  /* Anciens noms, gardés le temps que tout le fichier passe aux nouveaux.
     Ils POINTENT vers les nouveaux : une seule valeur par rôle, jamais deux
     qui pourraient diverger. */
  --bg: var(--app);
  --surface: var(--barre);
  --border: var(--filet);
  --text: var(--texte);
  --muted: var(--texte-3);
  --panneau: var(--barre);
  --panneau-filet: var(--filet-panneau);
  /* LA PLAQUE D'UN PICTOGRAMME — relevée de la planche, bloc « tampon ».

     Le pictogramme n'y est pas posé nu : il est sur une plaque de 58 × 47 —
     COUCHÉE, pas carrée — filet de 1,5, rayon de 4, et le dessin de 38 centré
     dedans. Les 47 se décomposent : 1,5 de filet deux fois, 3 de marge deux
     fois, 38 de dessin. C'est de là que viennent les rapports ci-dessous.

     TOUT SE CALCULE DEPUIS `--picto-encre`, la taille du DESSIN : un endroit
     déclare la sienne, la plaque suit. Les propriétés personnalisées se
     résolvent au point d'usage, si bien que ces trois calculs valent pour
     n'importe quelle taille sans être recopiés une seule fois.

     LE FILET EST À 1 PX ET NON 1,5. Dans l'arbre la plaque fait vingt pixels de
     haut ; à cette échelle un filet d'un pixel et demi s'étale sur deux colonnes
     à demi opaques et se délave. La boîte y perd un pixel de haut sur le relevé ;
     le RAPPORT, lui, est tenu — et c'est lui qu'on lit.

     Le fond et le filet sont des JETONS et non les hexadécimaux de la planche :
     la plaque suit ainsi le thème clair comme elle suit le sombre.

     `--champ` ET NON `--scene` POUR LE FOND, et c'est un écart assumé sur le
     relevé. `--scene` vaut #1B1D1F, à un cheveu du #1B2027 de la planche — mais
     la palette claire le pose ÉGAL au panneau, et la plaque y perdait son fond
     pour ne plus se lire que par son liseret. `--champ` se détache des deux
     côtés, et dans le sens de la planche : la plaque y est le plan le plus en
     avant, donc le plus clair sur un fond sombre. */
  --picto-encre: 1em;
  --picto-haut: calc(var(--picto-encre) * 44 / 38 + 2px);
  --picto-large: calc(var(--picto-haut) * 58 / 47);
  --picto-marge: calc(var(--picto-encre) * 3 / 38);
  --picto-fond: var(--champ);
  --picto-filet: 1px solid var(--filet-panneau);
  --picto-rayon: 3px;
  --panneau-texte: var(--texte-2);
  --panneau-sourd: var(--texte-3);
  --dossier: var(--neutre);
  --dossier-filet: var(--filet-champ);

  color-scheme: dark;
}

/* ---------------------------------------------------------------------------
   LE THÈME CLAIR

   `:root` porte le SOMBRE, qui est le thème de la maquette et le défaut. Le
   clair ne redéfinit que ce qui change, à deux endroits qui disent la même
   chose de deux façons — une règle `@media` et une règle d'attribut ne se
   fusionnent pas en un seul sélecteur sans outil de compilation :

     @media (prefers-color-scheme: light) — ce que veut la machine, tant que
       rien n'a été choisi. La garde `:not([data-theme="sombre"])` laisse un
       choix explicite reprendre la main.
     :root[data-theme="clair"] — le choix explicite, qui l'emporte partout.

   Les deux listes doivent rester RIGOUREUSEMENT identiques. Comme c'est le
   genre de promesse qui se rompt en silence, un contrôle les compare
   (scratchpad, `themes.php`).

   Ce ne sont pas les valeurs sombres inversées : sur du blanc, l'accent doit
   FONCER pour rester lisible, et les deux niveaux de texte sourds se calent sur
   d'autres seuils.
   --------------------------------------------------------------------------- */

@media (prefers-color-scheme: light) {
  :root:not([data-theme="sombre"]) {
    --app: #F2F0EC;
    --barre: #FBFAF8;
    --scene: #FBFAF8;
    --champ: #FFFFFF;
    --carte: #FFFFFF;
    --overlay: #FBFAF8;
    --filet: #DED8CF;
    --filet-panneau: #DED8CF;
    --filet-champ: #C4BCB1;
    --filet-actif: #D8542A;
    --filet-overlay: #DED8CF;
    --neutre: #E8E3DB;
    --choix: #F7E5DC;
    --choix-fort: #F2CDBE;
    --texte: #16181A;
    --texte-2: #4A453E;
    --texte-3: #6B655C;
    --marque-encre: #16181A;
    --marque-accent: #D8542A;
    --accent: #A33A1C;
    --action: #C4471F;
    --action-texte: #FFFFFF;
    --alerte: #8A6A18;
    --succes: #4A7A5C;
    --succes-texte: #4A7A5C;
    --danger: #8E2F2F;
    color-scheme: light;
  }
}

:root[data-theme="clair"] {
  --app: #F2F0EC;
  --barre: #FBFAF8;
  --scene: #FBFAF8;
  --champ: #FFFFFF;
  --carte: #FFFFFF;
  --overlay: #FBFAF8;
  --filet: #DED8CF;
  --filet-panneau: #DED8CF;
  --filet-champ: #C4BCB1;
  --filet-actif: #D8542A;
  --filet-overlay: #DED8CF;
  --neutre: #E8E3DB;
  --choix: #F7E5DC;
  --choix-fort: #F2CDBE;
  --texte: #16181A;
  --texte-2: #4A453E;
  --texte-3: #6B655C;
  --marque-encre: #16181A;
  --marque-accent: #D8542A;
  --accent: #A33A1C;
  --action: #C4471F;
  --action-texte: #FFFFFF;
  --alerte: #8A6A18;
  --succes: #4A7A5C;
  --succes-texte: #4A7A5C;
  --danger: #8E2F2F;
  color-scheme: light;
}

/* Quelques teintes restées en dur ne valent que sur fond sombre : elles sont
   reprises ici, aux deux mêmes endroits. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="sombre"]) button:hover { background: #E2DCD3; }
  :root:not([data-theme="sombre"]) .tnode.picked { background: #FAF0CE; }
  :root:not([data-theme="sombre"]) .tnode.picked:hover { background: #F5E7B8; }
  :root:not([data-theme="sombre"]) .carte.picked { background: #FAF0CE; }
  :root:not([data-theme="sombre"]) .carte.picked.cote-nue { background: var(--neutre); }
  /* L'œil au trait ardoise : sur du papier, le trait clair du thème sombre ne
     laisserait rien voir. */
  :root:not([data-theme="sombre"]) .teye {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236B655C' stroke-width='1.35' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.3 8S3.7 4 8 4s6.7 4 6.7 4-2.4 4-6.7 4S1.3 8 1.3 8z'/%3E%3Ccircle cx='8' cy='8' r='1.9'/%3E%3C/svg%3E");
  }
  :root:not([data-theme="sombre"]) .teye.off {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238E2F2F' stroke-width='1.35' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.7 5.3C1.8 6.3 1.3 8 1.3 8s2.4 4 6.7 4c1 0 1.9-.2 2.7-.6'/%3E%3Cpath d='M12.9 10.2c1.2-1.1 1.8-2.2 1.8-2.2S12.3 4 8 4c-.6 0-1.2.1-1.8.2'/%3E%3Cpath d='M2.2 2.2l11.6 11.6'/%3E%3C/svg%3E");
  }
  :root:not([data-theme="sombre"]) .teye:hover { background-color: rgba(31, 41, 51, 0.08); }
  :root:not([data-theme="sombre"]) button.danger:hover { background: #FDECEC; border-color: #E9B4B4; }
  :root:not([data-theme="sombre"]) button.danger:active { background: #F8DADA; }
  :root:not([data-theme="sombre"]) .cat-error { background: #FDECEC; border-color: #E9B4B4; }
  :root:not([data-theme="sombre"]) button.primary:hover { background: #BE441D; border-color: #BE441D; }
  :root:not([data-theme="sombre"]) button.primary:active { background: #A33A1C; border-color: #A33A1C; }
}

:root[data-theme="clair"] button:hover { background: #E2DCD3; }
:root[data-theme="clair"] .tnode.picked { background: #FAF0CE; }
:root[data-theme="clair"] .tnode.picked:hover { background: #F5E7B8; }
:root[data-theme="clair"] .carte.picked { background: #FAF0CE; }
:root[data-theme="clair"] .carte.picked.cote-nue { background: var(--neutre); }
/* L'œil au trait ardoise : sur du papier, le trait clair du thème sombre ne
   laisserait rien voir. */
:root[data-theme="clair"] .teye {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236B655C' stroke-width='1.35' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.3 8S3.7 4 8 4s6.7 4 6.7 4-2.4 4-6.7 4S1.3 8 1.3 8z'/%3E%3Ccircle cx='8' cy='8' r='1.9'/%3E%3C/svg%3E");
}
:root[data-theme="clair"] .teye.off {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238E2F2F' stroke-width='1.35' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.7 5.3C1.8 6.3 1.3 8 1.3 8s2.4 4 6.7 4c1 0 1.9-.2 2.7-.6'/%3E%3Cpath d='M12.9 10.2c1.2-1.1 1.8-2.2 1.8-2.2S12.3 4 8 4c-.6 0-1.2.1-1.8.2'/%3E%3Cpath d='M2.2 2.2l11.6 11.6'/%3E%3C/svg%3E");
}
:root[data-theme="clair"] .teye:hover { background-color: rgba(31, 41, 51, 0.08); }
:root[data-theme="clair"] button.danger:hover { background: #FDECEC; border-color: #E9B4B4; }
:root[data-theme="clair"] button.danger:active { background: #F8DADA; }
:root[data-theme="clair"] .cat-error { background: #FDECEC; border-color: #E9B4B4; }
:root[data-theme="clair"] button.primary:hover { background: #BE441D; border-color: #BE441D; }
:root[data-theme="clair"] button.primary:active { background: #A33A1C; border-color: #A33A1C; }

/* ---------------------------------------------------------------------------
   POLICES

   Space Grotesk pour l'interface, IBM Plex Mono pour tout ce qui se lit comme
   une valeur : cotes, codes, raccourcis, longueurs.

   Elles sont déclarées en LOCAL et non depuis Google Fonts — rien ne vient d'un
   CDN ici. Tant que les `.woff2` ne sont pas dans `assets/fonts/`, la
   déclaration reste sans effet et la pile de secours prend le relais : rien ne
   casse, l'interface est simplement dans la police du système. Déposer les
   fichiers suffit à la faire apparaître.
   --------------------------------------------------------------------------- */

@font-face {
  font-family: "Space Grotesk";
  src: url("../fonts/space-grotesk.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/ibm-plex-mono.woff2") format("woff2");
  font-weight: 400 500;
  font-display: swap;
}

:root {
  --police: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --police-valeur: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;
}


* { box-sizing: border-box; }

/* Un `display` déclaré ailleurs écrase l'attribut `hidden` : sans ce garde-fou,
   l'atelier (`.layout { display: grid }`) restait affiché sous les autres vues. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font: 13px/1.5 var(--police);
  color: var(--texte-2);
  background: var(--app);
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.app-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.6rem 1rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
/* Le bloc du compte est poussé au bout de la barre : `margin-left: auto` prend
   tout l'espace restant, ce qui le colle à droite quel que soit le nombre
   d'onglets. Le `#status` qui le précède garde donc sa place au centre. */
.compte {
  margin-left: auto;
  display: flex;
  align-items: center;
  /* Serré : le bloc ne tient que des icônes et une pastille, et les deux
     filets font déjà la respiration. */
  gap: 0.45rem;
  font-size: 0.8125rem;
  white-space: nowrap;
}

/* Le filet qui encadre la bascule d'affichage. Un trait plutôt qu'une barre
   verticale écrite : le glyphe change de graisse et de hauteur avec la police
   du système, et un lecteur d'écran le lirait à voix haute pour rien. */
.compte-filet {
  width: 1px;
  height: 1.1rem;
  background: var(--border);
}

/* Le compte en PASTILLE : les initiales, pas le nom entier.
   Le nom occupait le bout de la barre sur toute sa longueur — « Benjamin
   Sansot » pour dire ce qu'on sait déjà, puisqu'on est seul devant son écran.
   Le nom complet et le courriel restent en infobulle.

   Neutre, jamais en minium : l'accent dit l'ACTION, la marque et ce qu'on
   DÉSIGNE. Une pastille toujours allumée en ferait un quatrième sens. */
.compte-capsule {
  display: grid;
  place-items: center;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 0.4rem;
  border-radius: 999px;
  background: var(--neutre);
  color: var(--texte-2);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  user-select: none;
}

/* ---------------------------------------------------------------------------
   LE MENU DU COMPTE — voir `menuCompte` dans `page.js`.

   La pastille est devenue un BOUTON : elle hérite donc des touches, dont le
   cadre et le fond de survol bleutés qui n'ont rien à faire sur un jeton rond.
   On les reprend ici plutôt que d'inventer une classe de bouton nu : c'est la
   seule touche de l'outil qui soit une pastille.
   --------------------------------------------------------------------------- */
.compte-menu-hote { position: relative; display: inline-flex; }

.compte-capsule { border: 0; cursor: pointer; }
.compte-capsule:hover,
.compte-capsule[aria-expanded="true"] {
  background: var(--choix);
  border: 0;
  color: var(--texte);
}
.compte-capsule:focus-visible { outline: 2px solid var(--filet-actif); outline-offset: 2px; }

/* LA LANGUE, dans la barre du compte (espace installateur) ou dans son menu
   (atelier) : une liste courte, discrète comme les icônes voisines. Voir
   `selecteurDeLangue` dans `textes.js`. */
.compte-menu-langue { padding: 0.3rem 0.75rem; }
.compte-menu-langue .compte-langue { width: 100%; }
.compte-langue {
  height: 1.75rem;
  padding: 0 0.3rem;
  font: inherit;
  font-size: 0.75rem;
  color: var(--texte-2);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}

/* ---------------------------------------------------------------------------
   UN MENU — la feuille posée sur le reste, et sa rangée de choix.

   Écrit d'abord pour le compte, en haut à droite ; le clic droit sur une gamme
   en a eu besoin à son tour. La FORME est commune — la feuille, les traits de
   séparation, les lignes —, seul l'ANCRAGE diffère : sous la pastille pour
   l'un, au point cliqué pour l'autre. Deux menus qui ne se ressembleraient pas
   feraient deux idiomes pour un seul geste.
   --------------------------------------------------------------------------- */
.menu {
  z-index: 60;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  padding: 0.3rem;
  background: var(--overlay);
  border: 1px solid var(--filet-overlay);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 28%);
}

/* Ancré sous la pastille et calé à DROITE : la pastille ferme la barre, un
   menu aligné à gauche sortirait de l'écran. */
.compte-menu {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
}

/* Ancré AU POINT CLIQUÉ, donc `fixed` : le menu contextuel ne défile pas avec
   ce qu'il recouvre — il se referme au premier défilement, comme partout. */
.menu-flottant { position: fixed; }

/* QUI L'ON EST, en toutes lettres. C'était une infobulle, donc réservé à qui
   savait qu'il fallait survoler. */
.compte-menu-qui {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.4rem 0.55rem 0.5rem;
}
.compte-menu-nom { font-weight: 600; color: var(--texte); }
.compte-menu-role { font-size: 0.75rem; color: var(--muted); }

.menu-trait { height: 1px; margin: 0.25rem 0; background: var(--filet-overlay); }

/* Un lien et un bouton dans le même menu doivent se ressembler EXACTEMENT :
   « Journal » mène quelque part, « Se déconnecter » agit, et rien à l'œil ne
   doit distinguer les deux — c'est la même rangée de choix. */
.menu-item {
  display: block;
  width: 100%;
  padding: 0.4rem 0.55rem;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--texte-2);
  font: inherit;
  font-size: 0.8125rem;
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.menu-item:hover,
.menu-item:focus-visible {
  background: var(--neutre);
  border: 0;
  color: var(--texte);
  outline: none;
}
/* La page où l'on est déjà : ces deux destinations avaient leur onglet allumé
   dans la barre, et le perdre en descendant ici aurait été une perte. */
.menu-item.ici { background: var(--choix); color: var(--texte); }

/* Le mot qui dit quoi faire quand l'écran attend un choix — « Choisissez une
   gamme à gauche », « Choisissez un dossier à gauche ». Il vivait dans
   `accueil.css`, mais le catalogue tient le même discours au même moment, et
   il ne charge pas cette feuille-là. */
.explication {
  margin: 0 0 1rem;
  max-width: 46rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--muted);
}

/* ---------------------------------------------------------------------------
   LE LOGOTYPE QARNITAlab

   `[symbole] QARNITAlab` — le symbole en SVG posé dans la page, le mot en TEXTE.

   Pas une image : un fichier plat ne saurait pas changer de couleur avec le
   thème, et il en faudrait deux, à tenir en accord. Ici les traits du symbole
   prennent l'encre de la marque et le seul bras droit le minium ; les deux
   suivent les jetons, donc le thème, sans une ligne de script.

   Le mot en texte a un revers : tant que `space-grotesk.woff2` n'est pas dans
   `assets/fonts/`, la pile de secours prend le relais et la marque n'est pas
   dans sa police. Les graisses, les interlettrages et les proportions, eux,
   sont déjà ceux de la charte — déposer le fichier suffira.

   Toutes les mesures sont en `em`, donc relatives à la taille du mot : c'est
   ainsi que la charte les donne, et le logotype se redimensionne d'une seule
   valeur.
   --------------------------------------------------------------------------- */
.marque {
  margin: 0;
  display: flex;
  align-items: center;
  /* La charte dit 0,66 em — mesuré contre le sigle à sa taille de planche.
     Celui-ci ayant maigri d'un sixième, l'écart avait grossi à l'œil sans
     changer de valeur : un blanc qui pesait plus lourd que le dessin qu'il
     sépare. Il suit donc le sigle. */
  gap: 0.42em;
  font-size: 1.0625rem;
  line-height: 1;
  /* `CIME` et `lab` ne se séparent jamais sur deux lignes. */
  white-space: nowrap;
}
/* Le logotype est un lien vers l'accueil. Il n'en prend AUCUN attribut visible
   — ni couleur de lien, ni soulignement : la charte fixe ses deux teintes, et
   un logotype qui change d'aspect parce qu'il est cliquable n'est plus le
   logotype. Seul le curseur et le contour de focus le disent.

   La disposition passe ici : c'est le lien, désormais, qui aligne le symbole
   et le mot. */
.marque-lien {
  display: flex;
  align-items: center;
  /* La charte dit 0,66 em — mesuré contre le sigle à sa taille de planche.
     Celui-ci ayant maigri d'un sixième, l'écart avait grossi à l'œil sans
     changer de valeur : un blanc qui pesait plus lourd que le dessin qu'il
     sépare. Il suit donc le sigle. */
  gap: 0.42em;
  color: inherit;
  text-decoration: none;
}
.marque-lien:focus-visible {
  outline: 2px solid var(--filet-actif);
  outline-offset: 3px;
  border-radius: 3px;
}

/* Le symbole n'est plus carré : le poulpe est une figure debout, plus haute
   que large, et ses bras descendent sous la ligne du mot.
   
   LE RELEVÉ LE DONNE À 28 × 35 px pour un mot de 17, soit 1,65 × 2,06 em. Il est
   ici d'un sixième plus petit : à la taille de la planche, le poulpe pesait plus
   que le nom dans une barre d'en-tête haute de quarante pixels, et c'est le nom
   qu'on doit lire d'abord. Le RAPPORT, lui, ne bouge pas — c'est le dessin.
   
   Les deux mesures restent en `em` : le logotype se redimensionne d'une seule
   valeur, celle de `.marque`. */
.marque-symbole {
  flex: 0 0 auto;
  width: 1.38em;               /* relevé × 5/6 ; rapport 0,8 conservé */
  height: 1.72em;
}
/* Tout est encre SAUF le bras droit. C'est la règle du minium au symbole, et
   l'accent n'a le droit d'apparaître nulle part ailleurs dedans : ni sur la
   coiffe, ni sur la base, ni sur le bras gauche.

   Extrémités rondes, aucun contour, aucun fond, aucun trait fermé : la coiffe
   s'arrête AVANT de rejoindre la base. C'est cette ouverture, en bas à gauche,
   qui fait la marque ; la refermer donnerait un ovale.

   `stroke-width: 5.4` est la valeur du relevé à la taille du logotype. La
   planche donne 4,2 pour les grands formats : à 28 px de large, un trait de
   4,2 se délave. */
.marque-symbole path {
  fill: none;
  stroke: var(--marque-encre);
  stroke-width: 5.4;
  stroke-linecap: round;
}
.marque-symbole .bras-droit { stroke: var(--marque-accent); }
.marque-mot {
  font-family: var(--police);
  font-weight: 600;
  letter-spacing: 0.10em;
  color: var(--marque-encre);
}
.marque-produit {
  font-weight: 400;
  letter-spacing: 0.01em;
  /* L'interlettrage de `CIME` laisse un blanc après le E final ; ce retrait le
     reprend, sinon `lab` paraît décollé. */
  margin-left: -0.04em;
  color: var(--marque-accent);
}

/* Onglets de vue : mêmes touches que les autres, l'onglet courant restant
   enfoncé. C'est ce qui rend l'état lisible sans autre marquage. */
.views { display: flex; align-items: center; gap: 0.25rem; }

/* LE GROUPE DE L'OUVRAGE OUVERT.

   À gauche du filet, l'OUTIL : ce qui ne dépend d'aucun ouvrage et reste là
   quand on referme. À droite, les écrans qui ne parlent QUE de l'ouvrage
   ouvert. Ils étaient mêlés, et l'on ne savait pas ce qu'on perdait en
   refermant.

   Le filet, et un fond d'un cran : deux marques faibles valent mieux qu'une
   forte. La barre reste une barre d'onglets, elle ne devient pas deux
   barres. */
.views-ouvrage {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  justify-self: center;
  /* LE GROUPE DOIT POUVOIR RÉTRÉCIR pour que le nom de l'ouvrage s'ellipse au
     lieu de pousser les onglets hors du rail. Une boîte flex refuse par défaut
     de passer sous la largeur de son contenu ; sans cette ligne, `.ouvrage-nom`
     n'aurait jamais l'occasion de se couper. Les onglets, eux, gardent leur
     `min-width: auto` et ne cèdent pas. */
  min-width: 0;
  padding: 0.2rem 0.35rem 0.2rem 0.1rem;
  /* UN CADRE, ET PAS UN FOND. Le fond est déjà le langage de l'ONGLET COURANT
     — « une surface, pas une touche en relief », voir `.view.active`. Un fond
     de groupe par-dessus faisait deux marques différentes qui se ressemblent :
     on ne savait plus si le bloc était un groupe ou un onglet choisi. Le cadre
     dit « ceci contient », le fond dit « ceci est choisi ».

     MIS EN AVANT, SANS DEVENIR UN FOND : le cadre prend l'ACCENT — c'est une
     désignation, « voici l'ouvrage sur lequel tu travailles » — et un halo du
     même ton le détache de la barre. Un simple filet gris le laissait se
     confondre avec les liens de l'outil. */
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--border));
  border-radius: 8px;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent);
}

/* LA BARRE EN TROIS ZONES quand elle porte un ouvrage : l'OUTIL à gauche,
   l'OUVRAGE au CENTRE, le COMPTE à droite.

   Le groupe de l'ouvrage suivait les liens de l'outil, calé à gauche : on le
   cherchait du regard au milieu d'une file de mots. Au centre, il est ce qu'on
   voit en premier — c'est lui qui dit sur QUOI l'on travaille.

   UNE GRILLE ET NON UN CENTRAGE ABSOLU. Posé en `absolute` au milieu, il
   chevauchait les liens dès que l'écran rétrécissait. Ici les deux colonnes de
   côté se partagent l'espace à parts égales — la marque et le compte ont à peu
   près la même largeur, le groupe tombe donc au milieu — et la colonne des
   liens ne descend jamais sous leur largeur : faute de place, c'est le groupe
   qui se décale, jamais les mots qui se recouvrent. Le message d'état vit dans
   la colonne de droite et s'ellipse : il ne fait plus bouger la barre. */
.app-header:has(> .views-ouvrage) {
  display: grid;
  /* Le groupe CÈDE quand la place manque (`minmax(0, auto)`) : son nom s'ellipse,
     il ne mord pas sur les liens. */
  grid-template-columns: auto minmax(max-content, 1fr) minmax(0, auto) minmax(0, 1fr) auto;
  column-gap: 0.75rem;
}
.app-header:has(> .views-ouvrage) > .status {
  margin-left: 0;
  justify-self: end;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-header:has(> .views-ouvrage) > .compte { margin-left: 0; }
/* Masqué, le groupe garde sa colonne : sans lui la grille décalerait l'état et
   le compte d'une case. `display: none` le retirerait du placement. */
.app-header > .views-ouvrage[hidden] {
  display: flex !important;
  visibility: hidden;
}

/* LE CODE NOMME LE GROUPE. Le minium est réservé à l'action, à la marque et à
   la DÉSIGNATION — un code d'ouvrage en est une, c'est même la seule de cette
   barre. En chasse fixe, comme partout où l'outil montre un code. */
/* L'OUVRAGE OUVERT, EN TÊTE DU GROUPE : son nom, puis son code.

   Le nom porte l'accent — c'est une DÉSIGNATION, « voici ce sur quoi tu
   travailles », et l'accent est réservé à ça, à la marque et à l'action. Le
   code le suit en sourdine : il reste la clé, on le lit quand on en a besoin
   (un débit, une recherche au journal), pas à chaque coup d'œil.

   Même ordre et mêmes rôles que sur une carte — voir `.carte-titre` et
   `.carte-code`. Une tête de groupe qui rangerait ses deux mots autrement
   ferait hésiter une seconde à chaque fois. */
.ouvrage-ouvert {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  min-width: 0;
  padding: 0 0.35rem 0 0.5rem;
}
/* UN NOM PEUT ÊTRE LONG — « Portail coulissant deux vantaux, chantier Sansot ».
   Il cède plutôt que de pousser les onglets hors du rail : `min-width: 0` sur
   les deux, sans quoi une boîte flex refuse de passer sous la largeur de son
   contenu et l'ellipse ne se déclenche jamais. */
.ouvrage-nom {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  /* Un cran au-dessus des onglets : c'est le nom qu'on lit en entrant. */
  font-size: 0.95rem;
  color: var(--accent);
}
.ouvrage-code {
  flex: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}
.view {
  padding: 0.3rem 0.8rem;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  box-shadow: none;
  color: var(--texte-3);
}
.view:hover { background: var(--neutre); color: var(--texte-2); }
/* L'onglet courant est une surface, pas une touche en relief : sur fond sombre
   le relief ne se lit plus, c'est la VALEUR du fond qui dit lequel est actif. */
.view.active,
.view.active:hover {
  color: var(--texte-2);
  border-color: transparent;
  background: var(--neutre);
  box-shadow: none;
  transform: none;
}
a.view-link {
  white-space: nowrap;
  padding: 0.3rem 0.8rem;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  text-decoration: none;
  color: var(--texte-3);
  display: inline-flex;
  /* Par la LIGNE DE BASE, pas par le centre : le code est en chasse fixe et
     plus petit que le label, donc sa boîte est plus courte. Centrer les boîtes
     décalait les deux textes de 3 px l'un par rapport à l'autre — visible, et
     absent de l'onglet équivalent, qui est en flux normal. */
  align-items: baseline;
  box-shadow: none;
}
a.view-link:hover { background: var(--neutre); color: var(--texte-2); }

/* Le code de l'ouvrage ouvert, dans l'entrée qui y ramène. En chasse fixe et
   d'un ton en retrait : c'est une CLÉ, pas une phrase — la même convention que
   dans l'arbre et sur les cartes, où le code s'affiche et l'intitulé reste en
   infobulle. */
.view-code {
  margin-left: 0.4rem;
  font-family: var(--police-valeur);
  font-size: 0.9em;
  color: var(--texte-3);
}
.view.active .view-code,
.view:hover .view-code,
a.view-link:hover .view-code { color: inherit; }
a.view-link:active { transform: translateY(1px); }

/* LA CROIX QUI REFERME L'ATELIER, collée à l'entrée qui annonce l'ouvrage.
   Elle est un bouton À CÔTÉ et non dedans — un bouton dans un bouton n'est pas
   du HTML valable —, et c'est la marge négative qui les recolle : à l'œil, la
   croix appartient à l'entrée.

   DISCRÈTE AU REPOS, franche au survol. Une croix qui appelle l'œil autant que
   le nom de l'ouvrage ferait hésiter à chaque passage dans la barre ; celle-ci
   se laisse trouver quand on la cherche. Elle ne se masque pas pour autant :
   ce qui protège ici, c'est qu'il n'y a rien à perdre — l'ouvrage se rouvre
   d'un clic. */
.view-fermer {
  margin-left: -0.25rem;
  padding: 0 0.3rem;
  background: none;
  border: 0;
  color: var(--texte-3);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 4px;
  align-self: center;
}
.view-fermer:hover { color: var(--danger); background: var(--neutre); }
.view-fermer:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Le CADRAGE d'un objet posé : le viseur, son aide, puis les deux formules.
   Empilé et non aligné — le bouton et les champs ne sont pas au même niveau de
   lecture, l'un commande, les autres reçoivent. */
.zone-cadrage { display: flex; flex-direction: column; gap: 0.3rem; }

.status { font-size: 0.85rem; color: var(--muted); margin-left: auto; }

/* ---------------------------------------------------------------------------
   LE JOURNAL

   Un écran d'INTERROGATION, pas de parcours : les filtres sont en tête parce
   qu'ils sont le sujet, et la table qui suit n'est jamais qu'une page.
   --------------------------------------------------------------------------- */
.journal { padding: 1rem 1.2rem; display: flex; flex-direction: column; gap: 0.8rem; }
/* LA PAGE DÉFILE SOUS L'EN-TÊTE, comme le catalogue : le corps est bloqué à la
   hauteur de l'écran (l'atelier en a besoin), et un écran de liste qui ne s'en
   charge pas lui-même coupe sa table au bas de la fenêtre, sans barre. Les
   filtres restent en haut : on les retouche en cours de lecture. */
.journal, .comptes, .traductions { flex: 1 1 auto; min-height: 0; overflow: auto; }
/* `top` négatif de la marge intérieure : la barre se colle au BORD de la zone
   qui défile, et les lignes ne passent plus dans l'interstice au-dessus d'elle. */
.journal .jr-filtres, .traductions .jr-filtres { position: sticky; top: -1rem; z-index: 2; box-shadow: 0 -1rem 0 var(--app); }
/* Colonne flexible : sans ceci, la table se TASSE à la hauteur restante et
   défile dans une lucarne de deux lignes au lieu de faire défiler la page. */
.journal > *, .traductions > * { flex-shrink: 0; }

/* Les filtres s'enroulent : sept critères ne tiennent pas sur une ligne d'un
   écran étroit, et les empiler en colonne les ferait passer pour un
   formulaire de saisie. */
.jr-filtres {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.6rem 0.8rem;
  padding: 0.8rem;
  background: var(--barre);
  border: 1px solid var(--filet);
  border-radius: 8px;
}
.jr-filtres .field { min-width: 9rem; }
.jr-cherche { flex: 1 1 14rem; }
.jr-actions { display: flex; gap: 0.4rem; margin-left: auto; }

.jr-resume { margin: 0; font-size: 0.8rem; color: var(--muted); }

/* La table déborde en largeur plutôt que d'écraser ses colonnes : sept colonnes
   sur un portable ne tiennent pas, et une action tronquée ne dit plus rien. */
.jr-table { overflow-x: auto; }
.jr { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
.jr th {
  text-align: left;
  padding: 0.4rem 0.6rem;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 1px solid var(--filet);
  white-space: nowrap;
}
.jr td {
  padding: 0.35rem 0.6rem;
  border-bottom: 1px solid var(--filet);
  vertical-align: top;
}
.jr tbody tr:hover { background: var(--neutre); }

/* Les colonnes de CLÉ en chasse fixe, comme partout ailleurs : un code se
   compare caractère par caractère, une phrase se lit. */
.jr-quand, .jr-code, .jr-ms { font-family: var(--police-valeur); white-space: nowrap; }
.jr-quand, .jr-ms { color: var(--muted); }
.jr-genre { color: var(--muted); white-space: nowrap; }
.jr-issue { white-space: nowrap; color: var(--muted); }

/* UN REFUS SE VOIT. C'est la ligne qu'on cherche des yeux dans un journal — pas
   celle qui a marché. Le rouge est celui du danger, et il ne teinte que le mot
   qui le dit : une ligne entière en rouge ferait un mur dès qu'on filtre sur
   les refus, c'est-à-dire au moment où il n'a plus rien à distinguer. */
.jr-refus .jr-issue { color: var(--danger); font-weight: 600; }

.jr-suite { display: flex; justify-content: center; }

/* ---------------------------------------------------------------------------
   LES COMPTES

   Deux écrans qui se succèdent : la LISTE, puis LE COMPTE en pleine page. Ce
   n'était d'abord qu'un volet de vingt caractères posé à côté d'une liste
   presque vide — l'arbre des gammes y défilait dans une lucarne pendant que la
   moitié de l'écran restait noire.

   La table reprend celle du journal (`.jr`) et n'ajoute que ce qui lui est
   propre.
   --------------------------------------------------------------------------- */
.comptes { padding: 1rem 1.2rem; }
.cp-liste { display: flex; flex-direction: column; gap: 0.6rem; }
.cp-table { overflow-x: auto; }

/* Une ligne se DÉSIGNE : c'est elle qu'on ouvre. Le curseur l'annonce — sans
   lui, rien ne dit que la ligne entière est une cible. */
.cp tbody tr { cursor: pointer; }
.cp-designe, .cp tbody tr.cp-designe:hover { background: var(--neutre); }
.cp-designe td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.cp-nom { font-weight: 600; }
.cp-mail { font-size: 0.75rem; color: var(--muted); font-family: var(--police-valeur); }
/* « toi » : on se reconnaît dans sa propre liste, et c'est la ligne sur
   laquelle un geste malheureux coûte le plus cher. */
.cp-moi {
  margin-left: 0.4rem;
  font-style: normal;
  font-size: 0.7rem;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* UN COMPTE FERMÉ reste lisible mais s'efface : il est là pour l'histoire, pas
   pour le travail du jour. */
.cp-ferme .cp-nom, .cp-ferme .cp-mail { opacity: 0.55; }
.cp-clos { font-size: 0.7rem; color: var(--danger); }
.cp-portee { font-size: 0.8125rem; }
.cp-tout { color: var(--accent); }
.cp-rien, .cp-jamais { color: var(--muted); font-style: italic; }

/* --------------------------------------------------------- le compte, seul --

   Le formulaire est BORNÉ en largeur et centré : étiré sur un écran de deux
   mille pixels, il mettrait ses libellés à un mètre de ses champs — le défaut
   inverse de celui qu'on vient de corriger. « Pleine page » veut dire toute la
   place utile, pas toute la largeur. */
.cp-forme {
  max-width: 62rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.cp-forme-tete {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--filet);
}
.cp-forme-tete h2 { margin: 0; font-size: 1.15rem; }
.cp-forme-mail { font-family: var(--police-valeur); font-size: 0.8rem; color: var(--muted); }

/* « ← Comptes » est au formulaire ce que « ← Ouvrages » est à l'atelier : le
   même mot, la même place, le même geste. Un bouton et non un lien — il n'y a
   pas d'autre adresse, seulement un écran qui s'efface. */
.cp-retour {
  background: none;
  border: 0;
  padding: 0;
  color: var(--muted);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
}
.cp-retour:hover { color: var(--accent); }

/* L'IDENTITÉ à gauche, LA PORTÉE à droite : ce sont les deux questions du
   modèle — qui c'est, et sur quoi il travaille. Côte à côte, on les relie d'un
   coup d'œil au lieu de faire défiler entre les deux. */
.cp-colonnes {
  display: grid;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  gap: 1.4rem 2rem;
  align-items: start;
}
/* Sous cette largeur les deux colonnes deviennent illisibles : on empile. */
@media (max-width: 60rem) {
  .cp-colonnes { grid-template-columns: minmax(0, 1fr); }
}
.cp-colonne { display: flex; flex-direction: column; gap: 0.7rem; min-width: 0; }
.cp-colonne h3 {
  margin: 0;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Ce que le rôle veut dire, collé sous sa liste déroulante : la marge par
   défaut du paragraphe l'en détacherait, et il se lirait comme une remarque
   générale au lieu d'une glose du choix qu'on vient de faire. */
.cp-dit-role { margin: -0.4rem 0 0; }

/* Une case à cocher et son libellé vont SUR LA MÊME LIGNE : la case est le
   champ, le mot n'en est que l'étiquette. `.field` est déclaré PLUS BAS dans
   cette feuille et empile en colonne ; à spécificité égale c'est lui qui
   gagnait, et la case se retrouvait au-dessus de son libellé. Les deux classes,
   donc — et non un `!important`, qui aurait caché la cause au lieu de la
   régler. */
.field.field-inline { flex-direction: row; align-items: center; gap: 0.4rem; }

/* L'arbre a maintenant la place de se déployer : plus de lucarne, plus de
   défilement interne. C'est la page qui défile, comme partout ailleurs. */
.cp-gammes {
  border: 1px solid var(--filet);
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
  background: var(--barre);
}
.cp-gammes-liste { list-style: none; margin: 0; padding: 0 0 0 1.1rem; }
.cp-gammes > .cp-gammes-liste { padding-left: 0; }
.cp-gamme {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0;
  font-size: 0.875rem;
  cursor: pointer;
}
/* UNE GAMME HÉRITÉE est cochée mais inerte : le droit ne vient pas d'elle, il
   vient de son aïeule, et c'est là qu'on le retire. L'affaiblir le dit mieux
   qu'une infobulle seule, qui demande de survoler pour comprendre. */
.cp-heritee { color: var(--muted); cursor: default; }
.cp-source { font-style: normal; font-size: 0.7rem; opacity: 0.75; }

/* ---------------------------------------------------------------------------
   LES INSTALLATEURS — voir `installateurs.js`. L'arbre à cocher est celui des
   comptes (`.cp-gammes`) : donner une gamme se lit et se fait de la même façon
   aux deux endroits. Ne restent ici que la liste et ce qu'un ouvrage ajoute.
   --------------------------------------------------------------------------- */
/* Les deux classes : `arbre.css`, chargé après, centre les lignes d'arbre. */
.arbre-ligne.ins-ligne { flex-direction: column; align-items: flex-start; gap: 0.05rem; }
/* Le NOM de l'installateur, et non un `h2` : dans cette colonne, un `h2` est
   un titre de section — petit, en capitales, sourd. Voir `.pane-contenu h2`. */
.ins-titre { font-size: 1.15rem; font-weight: 600; margin-bottom: 0.2rem; }
.ins-nom { font-weight: 600; }
.ins-mail { font-size: 0.75rem; color: var(--muted); font-family: var(--police-valeur); }
.pane-contenu > #ins-fiche > .ins-mail { margin: 0 0 0.8rem; }
.ins-ferme .ins-nom, .ins-ferme .ins-mail { opacity: 0.55; }
.ins-resume { margin: 0 0 0.8rem; font-weight: 600; }
/* La gamme porte le rangement, l'ouvrage la chose : la gamme en gras, pour
   qu'on distingue d'un coup d'œil ce qui ouvre un dossier entier. */
.ins-gamme { font-weight: 600; }
.ins-code { font-family: var(--police-valeur); font-size: 0.7rem; color: var(--muted); }
.ins-brouillon { font-style: normal; font-size: 0.7rem; color: var(--danger); }
.ins-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--filet);
}
.ins-sale { font-size: 0.8rem; color: var(--accent); }

.cp-actions {
  padding-top: 0.9rem;
  border-top: 1px solid var(--filet);
}
/* LA SUPPRESSION s'écarte des deux autres : c'est le seul geste de cet écran
   qu'on ne rattrape pas, et il ne doit pas se trouver sous le doigt qui visait
   « Enregistrer ». */
.cp-supprimer { margin-left: auto; }
.status.error { color: var(--danger); font-weight: 600; }

/* ---------------------------------------------------------------------------
   « PLAN D'ABORD »

   Deux colonnes seulement : le plan, et la barre d'outils. L'arbre et le détail
   ne sont plus des colonnes — ils FLOTTENT au-dessus du plan, en `absolute`
   dans `.layout`, et ne lui retirent donc aucune largeur.

   Ce que ça change, mesuré : le plan passait de 981 px à ~1840 px sur un écran
   de 1896. Les deux colonnes fixes prenaient 860 px pour un ouvrage qui fait
   couramment 4 m de large.

   Conséquence utile : la largeur d'un panneau **ne coûte plus rien**. On peut
   donc les garder confortables — le formulaire d'une pièce a une vingtaine de
   champs — au lieu de les rétrécir jusqu'à l'illisible.
   --------------------------------------------------------------------------- */

.layout {
  flex: 1 1 auto;
  min-height: 0;
  /* Repère des panneaux flottants : ils se posent par rapport à lui, donc sous
     l'en-tête et au-dessus du bas de fenêtre, sans calcul. */
  position: relative;
  display: grid;
  /* La barre d'outils d'abord, comme dans la maquette : elle est le point de
     départ du geste, on y prend l'outil avant de toucher au plan. */
  grid-template-columns: var(--rail-largeur) minmax(0, 1fr);
  gap: 1px;
  background: var(--filet);
}
.pane { background: var(--bg); overflow: auto; min-height: 0; min-width: 0; }

/* Un panneau flottant : une carte posée sur le plan. */
.pane-flottant {
  position: absolute;
  z-index: 4;
  /* Sous la réglette du haut, jamais dessus. C'est depuis elle qu'on TIRE un
     repère : la partie couverte n'était pas seulement cachée, elle n'était
     plus saisissable. */
  top: calc(var(--reglette) + 0.85rem);
  /* Il ne descend jamais plus bas que la fenêtre : c'est son CORPS qui défile,
     pas la carte, sinon l'en-tête et son bouton de repli partaient hors champ. */
  max-height: calc(100% - var(--reglette) - 1.7rem);
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
  background: var(--panneau);
  border: 1px solid var(--panneau-filet);
  border-radius: 6px;
  /* Une ombre portée, pas un simple filet : c'est elle qui dit que la carte est
     POSÉE sur le plan et non découpée dedans. */
  box-shadow: 0 12px 32px rgb(0 0 0 / 45%);
}

/* Décalé de la barre d'outils, sinon il passerait dessous — et de la réglette
   de gauche, pour la même raison qu'en haut : on y tire des repères. */
.pane-tree {
  left: calc(var(--rail-largeur) + var(--reglette) + 0.85rem);
  width: var(--arbre-large);
}
/* DEVANT : le volet qu'on vient d'ouvrir ou de toucher — voir `devant()` dans
   `page.js`. Deux volets de droite partagent la même place, l'import et le
   détail, et celui qu'on regarde doit passer dessus. */
.pane-flottant.devant { z-index: 5; }
.pane-side { right: 0.85rem; width: 28.5rem; }

/* LE BORD GAUCHE SE TIRE — voir `brancheLargeur`. Une bande de quelques pixels,
   invisible au repos : la souris la trouve par son curseur, et un filet d'accent
   paraît au survol pour dire qu'elle est bien là. */
.pane-bord {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 6px;
  cursor: ew-resize;
  z-index: 2;
  touch-action: none;
}
.pane-bord:hover,
.pane-flottant.en-elargissement .pane-bord { box-shadow: inset 2px 0 0 var(--accent); }
/* Le bloc de gauche se tire par son bord DROIT. */
.pane-bord.pane-bord-droit { left: auto; right: 0; }
.pane-bord.pane-bord-droit:hover,
.pane-flottant.en-elargissement .pane-bord.pane-bord-droit { box-shadow: inset -2px 0 0 var(--accent); }
.pane-flottant.en-elargissement { user-select: none; }

.pane-tete {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.35rem 0.5rem 0.75rem;
  border-bottom: 1px solid var(--panneau-filet);
  /* La carte se saisit par sa tête — voir `brancheFenetres`. */
  user-select: none;
}

/* La tête est la POIGNÉE : le corps est plein de champs et de listes, un glissé
   qui les emporte rendrait la saisie impossible. Le curseur le dit — c'est la
   seule chose qui annonce que la fenêtre se déplace. */
.pane-poignee { cursor: grab; touch-action: none; }
.pane-flottant.en-deplacement .pane-poignee { cursor: grabbing; }
/* Pendant le glissé, la fenêtre passe devant les autres et cesse de répondre
   au survol : on déplace un objet, on ne le parcourt pas. */
.pane-flottant.en-deplacement {
  z-index: 9;
  user-select: none;
}

.pane-titre {
  flex: 1;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--panneau-sourd);
}

.pane-corps {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 0.9rem;
}

/* Replié : il ne reste que la tête, et la carte se réduit d'elle-même. Le
   bouton reste donc là où on l'a laissé — c'est ce qui permet de rouvrir sans
   aller chercher ailleurs. */
.pane-flottant.replie .pane-corps { display: none; }
.pane-flottant.replie { max-height: none; }

/* Le chevron, tracé en CSS : un émoji n'a pas la même chasse d'un système à
   l'autre. Il pointe vers le bas quand le panneau est ouvert. */
.pane-replier {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  box-shadow: none;
  cursor: pointer;
  position: relative;
}
.pane-replier:hover { background: var(--dossier); }

/* LE MENU DU PANNEAU : trois points, comme partout ailleurs qu'un menu se
   propose. Même taille et même survol que le chevron de repli, à côté duquel
   il vit — deux boutons de tête qui ne se ressembleraient pas donneraient
   l'impression que l'un des deux est arrivé par accident. */
.pane-menu {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  box-shadow: none;
  cursor: pointer;
  position: relative;
  color: var(--panneau-sourd);
}
.pane-menu:hover { background: var(--dossier); }

/* TOUT FERMER : même taille, même survol que ses deux voisins ; deux chevrons
   qui remontent l'un sur l'autre, le geste « tout replier » des explorateurs. */
.pane-tout-fermer {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  box-shadow: none;
  cursor: pointer;
  position: relative;
}
.pane-tout-fermer:hover { background: var(--dossier); }
.pane-tout-fermer::before,
.pane-tout-fermer::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  margin: auto;
  width: 0.38rem;
  height: 0.38rem;
  border-left: 2px solid var(--panneau-sourd);
  border-top: 2px solid var(--panneau-sourd);
  transform: rotate(45deg);
}
.pane-tout-fermer::before { top: 0.42rem; }
.pane-tout-fermer::after { top: 0.72rem; }
.pane-menu::before {
  content: '⋯';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
}
.pane-replier::before {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid var(--panneau-sourd);
  border-bottom: 2px solid var(--panneau-sourd);
  transform: rotate(45deg);
  transform-origin: 55% 55%;
  transition: transform 0.12s ease-out;
}
.pane-flottant.replie .pane-replier::before { transform: rotate(-135deg); }
/* Les seuls textes posés à même le panneau : le gris sourd du reste de
   l'interface y serait illisible. */
.pane-tree .empty, .pane-side .empty { color: var(--panneau-sourd); }
.pane-side .main-head h2 { color: var(--panneau-texte); }
.pane-workspace { display: flex; flex-direction: column; padding: 0; background: var(--scene); overflow: hidden; }

.toolbar { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.75rem; }

/* Boutons — des surfaces, plus des touches en relief.
   Le relief tenait à une lumière venant du haut : sur fond sombre il ne se lit
   plus, et un dégradé clair y ferait une tache. Ce qui distingue les états est
   donc la VALEUR du fond, comme dans la maquette : au repos une surface
   neutre, au survol un cran plus clair, à l'appui un cran plus sombre. */
button {
  font: inherit;
  /* Un libellé coupé en deux ne ressemble plus à une touche. La barre d'outils
     passe à la ligne si besoin, la touche non. */
  white-space: nowrap;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--filet-champ);
  border-radius: 4px;
  background: var(--neutre);
  color: var(--texte-2);
  cursor: pointer;
  box-shadow: none;
  transition: background-color 90ms, border-color 90ms, transform 90ms;
}
button:hover { background: #26303C; border-color: var(--filet-actif); color: var(--texte); }

button:active,
button.active {
  transform: translateY(1px);
  background: var(--champ);
  border-color: var(--filet-champ);
}

/* Le clavier doit voir la même touche que la souris. */
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

button:disabled {
  opacity: 0.45;
  cursor: default;
  transform: none;
  box-shadow: none;
  background: var(--champ);
}

/* La touche qui engage. Elle prend le minium en aplat, et l'ENCRE par-dessus :
   le blanc n'y tiendrait que 4,02:1. L'accent de sélection est le même minium,
   mais en trait — l'un se clique, l'autre désigne. */
button.primary {
  color: var(--action-texte);
  border-color: var(--action);
  background: var(--action);
  box-shadow: none;
}
button.primary:hover { background: #F58F6A; border-color: #F58F6A; color: var(--action-texte); }
button.primary:active,
button.primary.active { background: #E0653A; border-color: #E0653A; }

button.danger { color: var(--danger); border-color: var(--filet-champ); }
button.danger:hover { background: #3A2126; border-color: #6E3238; color: var(--danger); }
button.danger:active { background: #2A181B; }

/* ---------------------------------------------------------------------------
   L'ARBRE DE L'ATELIER

   La pastille, le chevron, le compteur et la sélection viennent d'`arbre.css`,
   partagés avec les gammes de l'accueil et le rangement du catalogue. Ne reste
   ici que ce qui n'existe QUE dans l'atelier : les genres de feuille et leurs
   puces, l'icône de dossier, l'œil, la sélection multiple, les valeurs et les
   erreurs portées par une ligne.

   Le panneau est dense : c'est la seule raison pour laquelle ses lignes sont
   plus petites que celles de l'accueil.
   --------------------------------------------------------------------------- */
.tnode { font-size: 0.8rem; }

/* Une ligne masquée s'efface, mais pas son œil : c'est le seul bouton qui la
   ramène, et le pâlir avec le reste le rendait le plus difficile à voir de la
   ligne alors qu'il en est l'action. */
.tnode.is-hidden > :not(.teye) { opacity: 0.42; }

/* Sélection multiple au Ctrl+clic : distincte de la sélection simple, qui elle
   ouvre le détail à droite. Même grammaire que la sélection simple — un
   liseré, pas un lavis — mais au jaune signal, pour qu'on ne confonde pas
   « ce que je regarde » avec « ce que je m'apprête à déplacer ». */
.tnode.picked {
  background: #3A2F14;
  box-shadow: inset 3px 0 0 var(--alerte);
}
.tnode.picked:hover { background: #4A3B18; }

/* Le libellé d'un dossier : plus appuyé que celui d'une feuille, et rien de
   plus. La capitale et l'interlettrage sont partis avec le reste — l'arbre de
   l'accueil n'en a pas, et deux poids suffisent à dire la hiérarchie. */
.tfolder .tlabel {
  font-weight: 600;
  color: var(--panneau-texte);
}
/* Le dossier est dessiné, pas écrit : un émoji change de forme d'un système à
   l'autre, n'a pas la même chasse et jure avec le reste de l'interface.

   Ouvert, il passe à l'ocre — la teinte de la cote dans le logotype. C'est la
   seule couleur de l'arbre, et elle ne sert qu'à ça : dire où on est. Le minium
   reste à l'action, le rouge au danger. */
.tfolder .tlabel::before {
  content: '';
  display: inline-block;
  width: 13px;
  height: 13px;
  margin-right: 0.38rem;
  vertical-align: -2px;
  background: no-repeat center / contain
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23a29a90' d='M1.6 4.1a1.6 1.6 0 0 1 1.6-1.6h2.75c.5 0 .98.24 1.28.65l.72.98h4.85a1.6 1.6 0 0 1 1.6 1.6v6.7a1.6 1.6 0 0 1-1.6 1.6H3.2a1.6 1.6 0 0 1-1.6-1.6z'/%3E%3C/svg%3E");
}
.tfolder.is-open .tlabel::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23e0a52e' d='M1.6 4.1a1.6 1.6 0 0 1 1.6-1.6h2.75c.5 0 .98.24 1.28.65l.72.98h4.85a1.6 1.6 0 0 1 1.6 1.6v1.02H4.5a1.6 1.6 0 0 0-1.53 1.14L1.6 12.2z'/%3E%3Cpath fill='%23e0a52e' fill-opacity='.55' d='M4.5 7.75h10.24a.8.8 0 0 1 .77 1.02l-1.2 4.2a1.6 1.6 0 0 1-1.54 1.16H2.35a.8.8 0 0 1-.77-1.03l1.39-4.2a1.6 1.6 0 0 1 1.53-1.15z'/%3E%3C/svg%3E");
}
.tfolder.is-empty .tlabel::before { opacity: 0.4; }
.tfolder.is-empty .tlabel { color: var(--panneau-sourd); }
/* « Non classé » n'est pas un dossier : ni icône, ni capitale, et l'italique
   dit qu'il désigne un état plutôt qu'un rangement. */
.tfolder.muted .tlabel { font-weight: 600; color: var(--panneau-sourd); font-style: italic; }
.tfolder.muted .tlabel::before { display: none; }

/* Un dossier désigné garde son intitulé au blanc franc : le liseré dit où l'on
   est, le texte confirme. */
.tfolder.choisie .tlabel { color: var(--texte); }

.tjite .tlabel { font-family: ui-monospace, monospace; font-weight: 400; }

/* Puces dessinées : un rond pour un JITÉ (un repère, sans épaisseur), une barre
   pour une pièce (de la matière). Le texte du script sert de repli sémantique
   mais n'est pas rendu. */
/* La géométrie et le rond par défaut viennent d'`.arbre-puce` (`arbre.css`),
   partagés avec le catalogue et l'accueil : une puce tient la place d'un
   chevron, pour que dossiers et feuilles s'alignent. Ne restent ici que les
   FORMES propres à l'atelier. */
.tbullet.part::before {
  width: 10px;
  height: 4px;
  background: #7a8798;
}
/* UNE VARIABLE : UN LOSANGE, là où un repère porte un rond.

   Un repère est un POINT dans l'espace — sans épaisseur, d'où le rond. Une
   variable ne se pose nulle part : elle se cite. Il lui fallait une silhouette
   à elle, et le vocabulaire de cette colonne était déjà bien pris — barre
   couchée pour une pièce, barre debout pour une lame, CARRÉ ÉVIDÉ pour une
   zone. Un carré plein se serait lu comme une zone mal dessinée.

   LE LOSANGE EST LE SEUL CONTOUR QUI RESTE, et c'est une chance : à cette
   taille, des pointes se distinguent d'un cercle bien mieux que des angles
   droits. Il est aussi DILATÉ — 9,9 px de diagonale contre 5 px de diamètre —
   parce qu'un premier essai à taille égale ne se voyait pas.

   MÊME TEINTE, en revanche : repère et variable sont deux JITÉ, cités de la
   même façon dans les mêmes formules. Deux couleurs auraient dit « deux
   états », ce que la colonne réserve à masqué, absent et en erreur. */
.tbullet.variable::before {
  border-radius: 0;
  width: 7px;
  height: 7px;
  transform: rotate(45deg);
}
/* Une lame est de la matière, comme une pièce — même teinte, même aplat — mais
   DEBOUT : c'est une barre refendue, et sa forme le dit. La barre couchée est
   la pièce, la barre dressée est la lame. */
.tbullet.lame::before {
  width: 4px;
  height: 10px;
  background: #7a8798;
}

/* UN POINT NOMMÉ : une croix, la même que sur la scène. Ni rond (le repère), ni
   losange (la variable) — un point qu'on pose, pas un trait ni un nombre.
   Dessinée en deux traits pour rester nette à cette taille. */
.tbullet.point::before {
  width: 9px;
  height: 9px;
  border-radius: 0;
  background:
    linear-gradient(currentColor, currentColor) center / 9px 1.6px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.6px 9px no-repeat;
}
/* Les champs d'un départ se succèdent comme ceux d'un point de pièce. */
.point-depart { display: flex; flex-direction: column; gap: 0.35rem; }
.point-form .field-help.erreur { color: var(--danger); font-weight: 600; }

/* Une zone est une surface : un carré évidé, ni point ni barre. */
.tbullet.zone::before {
  width: 9px;
  height: 9px;
  border: 1.5px solid #7A8B8F;
  background: rgba(92, 106, 110, 0.45);
}

/* La puce d'un bord qui est un POINT : la croix, comme dans l'arbre. */
.zone-bord .tbullet.point::before {
  width: 9px; height: 9px; border-radius: 0;
  background:
    linear-gradient(currentColor, currentColor) center / 9px 1.6px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.6px 9px no-repeat;
}

/* Le piquage est le geste principal du formulaire d'une zone : bouton pleine
   largeur, et franchement enfoncé tant que le mode est actif — on clique
   ensuite sur la scène, loin du formulaire, et il faut voir de là qu'il l'est
   encore. */
.zone-pique {
  display: block;
  width: 100%;
  margin: 0.15rem 0 0.35rem;
  font-weight: 600;
}
.zone-pique.active,
.zone-pique.actif {
  color: #fff;
  border-color: #A33A1C;
  background: linear-gradient(#A33A1C, #C4471F);
  box-shadow: inset 0 2px 4px rgba(60, 20, 8, 0.45);
  transform: translateY(1px);
}

/* Bords d'une zone : une liste courte, chaque ligne retirable. */
.zone-bords { display: flex; flex-direction: column; gap: 0.2rem; margin-bottom: 0.4rem; }
.zone-bord { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; }
.zone-bord-code { flex: 1; font-family: ui-monospace, monospace; }
.tpart .tlabel { font-family: ui-monospace, monospace; }
.tval { font-size: 0.72rem; color: var(--texte-3); font-variant-numeric: tabular-nums; }
.tnode.choisie .tval { color: var(--accent); }
.terror { font-size: 0.72rem; font-weight: 600; color: var(--danger); }
.tmuted { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }

/* L'œil, dessiné lui aussi. Discret au repos, net au survol de la ligne, et
   toujours pleinement visible quand il signale un élément masqué.

   Il n'a plus qu'un seul dessin. Il en avait deux — un clair pour la barre
   teintée du dossier, un ardoise pour les feuilles blanches — parce que ces
   deux surfaces coexistaient dans la colonne. Les feuilles n'ont plus d'aplat,
   dossiers et feuilles reposent sur le même fond, et c'est donc le THÈME, et
   lui seul, qui décide de la teinte du trait. */
.teye {
  flex: none;
  margin-left: auto;
  width: 1.1rem;
  height: 1.1rem;
  padding: 0;
  border: 0;
  cursor: pointer;
  opacity: 0.42;
  background: no-repeat center / 13px 13px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23d3dbe5' stroke-width='1.35' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.3 8S3.7 4 8 4s6.7 4 6.7 4-2.4 4-6.7 4S1.3 8 1.3 8z'/%3E%3Ccircle cx='8' cy='8' r='1.9'/%3E%3C/svg%3E");
}
.teye.off {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23f87171' stroke-width='1.35' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.7 5.3C1.8 6.3 1.3 8 1.3 8s2.4 4 6.7 4c1 0 1.9-.2 2.7-.6'/%3E%3Cpath d='M12.9 10.2c1.2-1.1 1.8-2.2 1.8-2.2S12.3 4 8 4c-.6 0-1.2.1-1.8.2'/%3E%3Cpath d='M2.2 2.2l11.6 11.6'/%3E%3C/svg%3E");
}
.tnode:hover .teye, .teye:focus-visible { opacity: 0.8; }
.teye.off, .tnode:hover .teye.off { opacity: 1; }
.teye:hover { opacity: 1; background-color: rgba(255, 255, 255, 0.16); }

/* Espace de travail */
/* ---------------------------------------------------------------------------
   Barre d'outils de la scène — colonne verticale à droite de l'espace de
   travail. Trois groupes séparés par un filet : créer, voir, cadrer.
   --------------------------------------------------------------------------- */

.rail {
  width: var(--rail-largeur);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.5rem;
  background: var(--panneau);
  /* Elle peut dépasser en hauteur sur un petit écran : elle défile seule
     plutôt que d'écraser ses touches. */
  overflow-y: auto;
  overflow-x: hidden;
}

.rail-groupe {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--panneau-filet);
}

/* Le dernier groupe ne porte pas de filet : il ne sépare de rien. */
.rail-groupe:last-child { border-bottom: 0; padding-bottom: 0; }

/* Pas de cadre au repos : une icône seule sur le fond de la barre. Un contour
   sur chacune faisait neuf rectangles dans une colonne de 52 px, et c'est le
   quadrillage qu'on voyait avant les outils. Ce qui répond, c'est le fond —
   teinté au survol, plus dense quand l'outil est enclenché.

   `.btn-icone` est la MÊME touche, hors du rail : le thème et la sortie, en
   haut à droite. Un seul jeu de règles, parce que c'est un seul objet — une
   icône sur laquelle on appuie. La copier pour l'en-tête, c'était s'exposer à
   ce que les deux dérivent l'une de l'autre. */
.rail-btn,
.btn-icone {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  color: var(--texte-3);
  background: none;
  /* AUCUN CADRE AU REPOS, et c'est délibéré. Le carré encadré est réservé aux
     pictogrammes du MÉTIER — ceux qui nomment une gamme, un profil, un décor —
     où il tient les espèces alignées dans une colonne. Une commande, elle, se
     saisit : son fond n'apparaît qu'au survol et à l'état enfoncé, là où il DIT
     quelque chose. Les encadrer aussi rendrait les deux familles indistinctes,
     et ferait du rail un mur de boîtes. */
  border: 0;
  border-radius: 5px;
  cursor: pointer;
  box-shadow: none;
  transition: background-color 90ms, color 90ms;
}

.rail-btn:hover,
.btn-icone:hover { background: var(--accent); color: #FFFFFF; }

.rail-btn:focus-visible,
.btn-icone:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.rail-btn svg,
.btn-icone svg {
  width: 1.25rem;
  height: 1.25rem;
  /* Les icônes sont tracées, pas remplies : un seul jeu de réglages ici plutôt
     que les mêmes attributs répétés sur chaque tracé du document. */
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Un outil enclenché — JITÉ masqués, vue 3D, pose de barre en cours.
   Le minium d'ACTION, plus dense que le survol : sans cet écart, un outil
   enclenché et un outil simplement survolé se ressembleraient.

   `.btn-icone.actif` en portait un second, le crayon de l'en-tête. Il n'y a
   plus de mode à enclencher : ce qu'on propose se décide sur le rôle, et un
   rôle ne s'allume pas. La règle reste pour le rail seul. */
.rail-btn.active,
.rail-btn.active:hover {
  color: #FFFFFF;
  border: 0;
  background: var(--action);
  transform: none;
}

/* LES RÉGLAGES DE LA VUE 3D, posés sur elle.
   Un bandeau flottant en haut à droite : la scène se regarde depuis le milieu,
   et un coin est le seul endroit qui ne masque presque jamais l'ouvrage.
   `z-index` parce que `Scene3D` ajoute son canevas APRÈS ce bloc. */
.vue3d-outils {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.5rem 0.25rem 0.3rem;
  border: 1px solid var(--panneau-filet);
  border-radius: 8px;
  /* Le fond de la 3D change avec le mode présentation : le bandeau garde donc
     le fond de l'interface, à peine translucide, pour rester lisible sur les
     deux. Repli en couleur pleine là où `color-mix` manque. */
  background: var(--panneau);
  background: color-mix(in srgb, var(--panneau) 90%, transparent);
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.22);
}

/* La glissière de puissance, couchée : sur la scène il y a la largeur, et une
   glissière horizontale se règle du geste le plus court. */
.vue3d-jauge {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding-left: 0.25rem;
  border-left: 1px solid var(--panneau-filet);
  color: var(--texte-3);
}
.vue3d-jauge-icone {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}
.vue3d-jauge input[type="range"] {
  width: 6.5rem;
  margin: 0;
  padding: 0;
  accent-color: var(--action);
  cursor: ew-resize;
  background: none;
}
.vue3d-jauge input[type="range"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}
.vue3d-jauge-val {
  min-width: 2.6rem;
  text-align: right;
  font-family: var(--police-valeur);
  font-variant-numeric: tabular-nums;
  font-size: 0.68rem;
  color: var(--texte-3);
}

/* La barre oblique de l'œil n'apparaît que lorsque les JITÉ sont masqués :
   l'icône dit alors l'état, pas seulement l'action. */
.rail-barre { display: none; }
.rail-btn.active .rail-barre { display: inline; }

.ws-toolbar-actions button.active {
  color: #fff;
  border-color: #A33A1C;
  background: linear-gradient(#A33A1C, #C4471F);
  box-shadow: inset 0 2px 4px rgba(60, 20, 8, 0.45);
}
.ws-select {
  display: flex; align-items: center; gap: 0.3rem;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted);
}
.ws-select select { text-transform: none; letter-spacing: normal; color: var(--text); }
/* `relative` parce que la bulle de survol se pose dedans, en absolu. */
.workspace { flex: 1; overflow: hidden; position: relative; }
.ws-svg {
  width: 100%;
  height: 100%;
  display: block;
  background: var(--scene);
  cursor: grab;
  touch-action: none;
}
.ws-svg:active { cursor: grabbing; }
.ws-svg text { font-family: system-ui, sans-serif; user-select: none; }

/* Bulle de survol — le détail de l'élément visé.
   `pointer-events: none` est vital : la bulle suit le curseur, donc elle est
   toujours à deux doigts de passer dessous. Si elle captait le pointeur, elle
   déclencherait un pointerleave sur la scène, se cacherait, le survol
   reviendrait, elle réapparaîtrait — un clignotement sans fin. */
.ws-tip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  max-width: 22rem;
  padding: 0.45rem 0.6rem;
  background: #1f2933;
  color: #e8edf3;
  border: 1px solid #3a4855;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
  font-size: 0.78rem;
  line-height: 1.35;
}
.ws-tip-tete { font-weight: 700; font-size: 0.84rem; color: #fff; }
.ws-tip-sous { color: #9fb0c2; margin-bottom: 0.3rem; }
/* Deux colonnes : les intitulés alignés à gauche, les valeurs contre eux.
   `max-content` laisse la colonne des clés à la largeur de la plus longue. */
.ws-tip-liste {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.05rem 0.7rem;
  margin: 0.25rem 0 0;
}
.ws-tip-liste dt { color: #9fb0c2; }
.ws-tip-liste dd { margin: 0; font-variant-numeric: tabular-nums; }
.ws-tip-alerte {
  margin: 0.35rem 0 0;
  padding: 0.3rem 0 0 1.1rem;
  border-top: 1px solid #3a4855;
  color: #fbbf24;
}
.ws-tip-alerte li { margin-top: 0.15rem; }
.ws-tip-alerte li:first-child { margin-top: 0; }

/* Saisie d'une teinte RAL : la pastille, le code, le nom.
   La pastille est petite et cernée : sur un fond clair, un 9010 sans bordure
   n'aurait aucun contour et passerait pour un trou. */
.ral-picker { display: flex; align-items: center; gap: 0.45rem; }
.ral-pastille {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid #94a3b8;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
/* Rien de choisi, ou hors nuancier : un damier plutôt qu'un aplat, pour que
   l'absence de teinte ne se confonde pas avec une teinte pâle. */
.ral-pastille.vide {
  background:
    linear-gradient(45deg, #cbd5e1 25%, transparent 25%, transparent 75%, #cbd5e1 75%),
    linear-gradient(45deg, #cbd5e1 25%, transparent 25%, transparent 75%, #cbd5e1 75%) 6px 6px,
    #fff;
  background-size: 12px 12px;
}
.ral-picker .ral-code { width: 6rem; flex: 0 0 auto; }
.ral-nom { color: var(--muted); font-size: 0.85em; }
.ral-nom.inconnu { color: #b45309; }

/* Le nuancier. `position: relative` sur le champ, absolue sur le panneau :
   il se pose PAR-DESSUS le formulaire au lieu de l'écarter — sans quoi ouvrir
   le nuancier ferait sauter tout ce qui est en dessous. */
.ral-picker { position: relative; flex-wrap: wrap; }
.ral-panneau {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.3rem);
  left: 0;
  width: min(23rem, 90vw);
  padding: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.22);
}
.ral-recherche { width: 100%; margin-bottom: 0.4rem; }
.ral-familles { display: flex; flex-wrap: wrap; gap: 0.2rem; margin-bottom: 0.45rem; }
.ral-famille {
  padding: 0.15rem 0.4rem;
  font-size: 0.72rem;
  background: none;
  border: 1px solid var(--line);
  box-shadow: none;
  color: var(--muted);
}
.ral-famille.active { background: var(--text); color: var(--surface); border-color: var(--text); }
/* Une grille à pas fixe : les codes font tous quatre chiffres, donc les cases
   n'ont aucune raison de varier de largeur. Elle défile au-delà de six rangs
   plutôt que de pousser le panneau hors de l'écran. */
.ral-grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(3.1rem, 1fr));
  gap: 2px;
  max-height: 14rem;
  overflow-y: auto;
}
.ral-case {
  padding: 0.4rem 0;
  font-size: 0.7rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  border: 1px solid rgba(0, 0, 0, 0.18);
  border-radius: 0;
  box-shadow: none;
  cursor: pointer;
}
/* Liseré du chiffre, dans la couleur opposée à l'encre : sur les 22 teintes de
   valeur moyenne, ni le noir ni le blanc n'atteignent 3:1 contre le fond, et
   c'est le contour qui rend le code lisible. */
.ral-case.sur-sombre { text-shadow: 0 0 2px rgba(0, 0, 0, 0.85); }
.ral-case.sur-clair { text-shadow: 0 0 2px rgba(255, 255, 255, 0.9); }
.ral-case:hover { outline: 2px solid var(--text); outline-offset: -2px; }
.ral-case.active { outline: 3px solid var(--text); outline-offset: -3px; }

/* Côté droit — détail d'un JITÉ */
.main-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.75rem; }
.main-head h2 { margin: 0; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.main-head-actions { display: flex; gap: 0.35rem; flex: none; }

/* Le filet sépare les deux niveaux du panneau : au-dessus la création, en
   dessous l'élément sélectionné et ce qui s'applique à lui. */
.pane-side .toolbar {
  margin-bottom: 0.65rem;
  padding-bottom: 0.65rem;
  border-bottom: 1px solid var(--panneau-filet);
}

.empty { color: var(--muted); padding: 1rem 0; font-size: 0.85rem; }

/* Formulaire */
.jite-form {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 1rem;
  margin-bottom: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.jite-form h3 { margin: 0 0 0.3rem; font-size: 0.95rem; }
.field { display: flex; flex-direction: column; gap: 0.2rem; }
.field > span { font-size: 0.8rem; color: var(--muted); }
/* Champs de saisie : rectangulaires, compacts, un cran plus petits que le
   texte courant. Le formulaire des pièces empile beaucoup de champs — chaque
   millimètre gagné en hauteur se voit. */
.field input, .field select, .field textarea,
.pt-detail select, .pt-detail input,
.ep-trigger, .ep-search,
.ws-select select,
.len-row input[type="number"] {
  font-family: inherit;
  font-size: 0.8rem;
  padding: 0.28rem 0.45rem;
  color: var(--texte-2);
  border: 1px solid var(--filet-champ);
  border-radius: 4px;
  background: var(--champ);
}
/* Le champ en cours d'édition se signale par sa bordure — c'est le seul repère
   qui dit où l'on tape. C'est le minium, à 5,63:1 contre le champ sombre et
   4,02:1 contre le champ clair — bien au-delà du seuil de 3:1 d'un repère. */
.field input:focus, .field select:focus, .field textarea:focus,
.pt-detail select:focus, .pt-detail input:focus,
.ep-trigger:focus-visible, .ep-search:focus,
.ws-select select:focus,
.len-row input[type="number"]:focus {
  outline: none;
  border-color: var(--filet-actif);
  box-shadow: inset 0 0 0 1px var(--filet-actif);
  color: var(--texte);
}
/* Les valeurs se lisent en chiffres alignés, pas en texte courant. */
/* `.tcount` a disparu avec le compte des dossiers — voir `arbre.css`. */
.field input[type="number"], .len-row input[type="number"], .tval {
  font-family: var(--police-valeur);
  font-variant-numeric: tabular-nums;
}
.field > span { font-size: 0.76rem; }
/* UNE FORMULE : pleine largeur, monospace, redimensionnable, DEUX LIGNES AU
   MOINS. Citer un repère produit des expressions longues — c'est tout
   l'intérêt des noms de variable — qu'un champ d'une ligne tronque à la
   lecture, et l'on ne relit jamais ce qu'on n'a pas sous les yeux.

   La règle ne dépend plus d'un `.field` : les sept champs de formule de l'outil
   passent par `formule.js`, et celui-ci pose la classe où qu'il atterrisse. */
textarea.formula {
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.79rem;
  line-height: 1.45;
  resize: vertical;
  min-height: 3.2rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.field input.narrow { max-width: 7rem; }
.field-help { font-size: 0.72rem; color: var(--muted); }
/* Champ neutralisé par un autre réglage : visible mais clairement hors jeu. */
.field.is-off > span:first-child, .field.is-off input { opacity: 0.5; }
.field.is-off .field-help { opacity: 1; color: #b45309; }
/* Rang d'un point de passage : le mode « deux points » ne lit que 1 et 2. */
.pt-rang {
  flex: none; width: 1.15rem; height: 1.15rem;
  background: var(--accent); color: #fff;
  font-size: 0.68rem; font-weight: 700; line-height: 1.15rem; text-align: center;
}
.field-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.mini.pick { font-size: 0.7rem; padding: 0.1rem 0.4rem; }
/* Enfoncé, il veut dire « je désigne un élément sur la scène ». Il prend le
   même minium enfoncé que les autres bascules : le minium est la couleur de
   l'action, et désigner en est une. */
.mini.pick.active,
.mini.pick.actif {
  color: #fff;
  border-color: #A33A1C;
  background: linear-gradient(#A33A1C, #C4471F);
  box-shadow: inset 0 2px 4px rgba(60, 20, 8, 0.45);
}
/* `minmax(0, 1fr)` et non `1fr` : une colonne `1fr` ne descend pas sous la
   largeur naturelle de son champ, et trois champs côte à côte débordaient d'un
   volet flottant — qui défilait alors en largeur. */
.row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.6rem; }
.field-check { display: flex; align-items: center; gap: 0.45rem; font-size: 0.85rem; }
.field-check input { width: 1rem; height: 1rem; }
.form-actions { display: flex; gap: 0.5rem; margin-top: 0.3rem; }

/* CE QUE L'INSTALLATEUR POURRA TOUCHER — une section à part, et pas une case
   perdue au milieu des autres. Elle ne décrit pas le repère : elle l'OUVRE à
   quelqu'un d'autre, sur un autre site, et cette décision-là mérite qu'on la
   voie. Même cadre que les sections de points, pour ne pas inventer une
   troisième façon d'encadrer un groupe de champs. */
.reglage-groupe {
  border: 1px solid var(--filet-panneau);
  border-radius: 4px;
  padding: 0.2rem 0.7rem 0.7rem;
  margin: 0.15rem 0 0;
  min-inline-size: 0;
  background: var(--app);
}
.reglage-groupe legend { font-size: 0.72rem; color: var(--muted); padding: 0 0.3rem; }
.reglage-groupe .field-check { margin-top: 0.35rem; }
/* Une case désactivée reste LISIBLE : c'est le motif juste en dessous qui
   compte, et l'estomper avec elle le rendrait illisible au moment où on en a
   le plus besoin. */
.reglage-groupe .field-check input:disabled { opacity: 0.45; }
.reglage-groupe .field-check input:disabled + span { color: var(--muted); }

.reglage-bloc { margin-top: 0.5rem; }
.reglage-bloc > .field + .reglage-bornes { margin-top: 0.5rem; }
.reglage-bloc .field input { min-width: 0; width: 100%; box-sizing: border-box; }
/* Les deux bornes se lisent ensemble — « de… à… » est une seule phrase. */
.reglage-bornes { display: flex; gap: 0.5rem; }
.reglage-bornes > .field { flex: 1 1 0; min-width: 0; }
/* L'ACCROCHE RÉGLABLE — migration 177 : la case du second axe se détache du
   bloc du premier, sinon elle se lit comme son dernier champ. */
.point-form .reglage-bloc + .field-check { margin-top: 0.9rem; }
.point-form .reglage-bloc > p.field-help { margin: 0.25rem 0 0.5rem; }

/* Sections du formulaire d'une pièce.
   Le fond légèrement teinté est ce qui les sépare vraiment : les champs, eux,
   sont blancs, si bien que chaque section se lit comme un bloc et que le
   formulaire ne ressemble plus à une seule colonne de cadres identiques. Le
   navigateur interrompt de lui-même la bordure derrière la légende — inutile
   de lui donner un fond. */
/* ---------------------------------------------------------------------------
   ONGLETS DE FORMULAIRE

   Les sections d'un formulaire long deviennent des onglets. Ils sont ACCOLÉS à
   la section qu'ils ouvrent — pas de trait entre eux — pour qu'on lise une
   chemise ouverte et non deux blocs empilés : c'est ce qui fait la métaphore,
   et sans elle un onglet n'est qu'un bouton.
   --------------------------------------------------------------------------- */

.onglets {
  /* Des colonnes ÉGALES, et non une rangée qui se casse où elle peut : six
     onglets dans un panneau étroit débordaient en deux lignes déchiquetées, et
     une rangée déchiquetée ne se lit plus comme une rangée. `auto-fill` et non
     `auto-fit` : la dernière ligne incomplète garde la largeur d'une colonne
     au lieu de s'étirer sur toute la barre. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: 2px;
  margin: 0.4rem 0 0;
  /* `.jite-form` espace ses blocs de 0,6rem. Cet écart a du sens entre deux
     champs, aucun entre un onglet et la chemise qu'il ouvre : une marge ne
     peut pas annuler un `gap`, on le reprend donc ici. */
  margin-bottom: -0.6rem;
  /* LA LIGNE DE BASE. C'est elle, plus que la forme des onglets, qui fait lire
     une rangée : les fermés s'y posent, l'ouvert la traverse. */
  border-bottom: 1px solid var(--filet-panneau);
}

.onglet {
  padding: 0.38rem 0.55rem;
  /* Ni capitales ni interlettrage : les onglets de l'en-tête — `.view`, l'autre
     rangée d'onglets de l'outil — n'en ont pas. Des micro-capitales espacées
     se lisent moins bien ET prennent un quart de large en plus, ce qui coûtait
     une colonne entière dans le panneau. */
  font-size: 0.75rem;
  font-weight: 600;
  text-align: center;
  /* Un intitulé trop long se coupe plutôt que d'élargir sa colonne : toutes
     les colonnes sont solidaires, une seule qui s'étire les déforme toutes. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--texte-3);
  /* Un CORPS, pour qu'on les compte. Sans fond ni filet, un onglet fermé
     n'était qu'un mot gris posé là : on voyait UNE section ouverte, pas six
     chemises dont une est tirée. */
  background: var(--neutre);
  border: 1px solid var(--filet-panneau);
  /* Arrondi en haut seulement : le bas se fond dans la section. */
  border-radius: 4px 4px 0 0;
  /* Posés SUR la ligne de base, qu'ils recouvrent d'un pixel. */
  margin-bottom: -1px;
  box-shadow: none;
}

.onglet:hover { color: var(--texte); border-color: var(--texte-3); }

/* ---------------------------------------------------------------------------
   LA MÊME RANGÉE, DANS UNE PAGE et non dans un panneau étroit — les trois
   sujets d'une gamme, à l'accueil. Voir `barreDOnglets`.

   Des colonnes ÉGALES n'ont plus de sens ici : trois onglets sur onze cents
   pixels donneraient trois pavés de trois cent soixante. Ils prennent donc la
   largeur de leur mot, alignés à gauche, comme les onglets de l'en-tête.

   Et pas de marge négative : celle de `.onglets` rattrape l'espacement propre
   à `.jite-form`, qui n'existe pas dans une page. */
.onglets.onglets-large {
  display: flex;
  flex-wrap: wrap;
  margin: 0 0 1rem;
}

/* LES ONGLETS DU PANNEAU « OUVRAGE » : un jeu sous la barre.

   `.onglets` porte une marge basse NÉGATIVE, et à raison : dans `.jite-form`
   elle annule le `gap` de 0,6 rem que la chemise mettrait entre l'onglet et ce
   qu'il ouvre. Ici il n'y a pas de `gap` à annuler — l'arbre suit directement
   — et la marge négative remontait donc le premier dossier CONTRE la barre.

   Une marge héritée d'un contexte qui n'est pas le sien : le genre de défaut
   qu'on ne voit qu'une fois les deux écrans côte à côte.

   La règle porte sur le PANNEAU et non sur l'identifiant de la barre : c'est
   le contexte qui commande — « dans un panneau d'arbre, la barre respire » —
   et le jour où un second panneau prendra des onglets, il n'aura rien à
   redemander. */
.pane-tree .onglets { margin-bottom: 0.45rem; }
/* TROIS ONGLETS SUR UNE RANGÉE — Structure, Variables, Points (migration 139).
   La grille à colonnes de 6,5 rem en logeait trois tant que l'arbre ne
   défilait pas ; la barre de défilement apparue, « Points » passait seul à la
   ligne. Dans ce panneau les onglets sont en nombre connu : une rangée, à
   parts égales, et l'intitulé se coupe plutôt que de casser la rangée. */
.pane-tree .onglets { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
.onglets-large .onglet { padding: 0.45rem 1rem; font-size: 0.8125rem; }

/* LE COMPTE sur l'onglet : savoir qu'un sujet est vide sans avoir à l'ouvrir.
   En sourdine — c'est une précision, pas le titre — et jamais en minium, que la
   charte réserve à l'action, à la marque et à ce qu'on désigne. */
.onglet-compte {
  margin-left: 0.45rem;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--texte-3);
  font-variant-numeric: tabular-nums;
}
.onglet.actif .onglet-compte { color: var(--texte-2); }

/* L'onglet OUVERT : la chemise est tirée. Il prend la surface de sa section et
   son filet du bas s'efface — les deux ne font plus qu'une pièce.
   Et il porte le liseré de minium, la même marque que la ligne DÉSIGNÉE dans
   l'arbre : c'est le même fait, c'est ce qu'on regarde. */
.onglet.actif,
.onglet.actif:hover {
  color: var(--texte);
  background: var(--app);
  border-color: var(--filet-panneau);
  border-bottom-color: var(--app);
  box-shadow: inset 0 3px 0 var(--accent);
  transform: none;
}

.onglet:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* La section reprend là où la ligne de base s'arrête : ni second filet, ni
   arrondi en haut. Sans ça on lisait deux boîtes empilées au lieu d'une
   chemise ouverte. */
.onglets ~ .pt-group {
  margin-top: 0;
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  padding-top: 0.6rem;
}

/* Ce que la légende portait EN PLUS de son titre — le « + » des points de
   passage — remonte en tête de section, à droite. */
.onglet-extras {
  display: flex;
  justify-content: flex-end;
  gap: 0.35rem;
  margin: 0.35rem 0 0.15rem;
}

.pt-group {
  border: 1px solid var(--filet-panneau);
  border-radius: 4px;
  padding: 0.2rem 0.7rem 0.7rem;
  margin: 0.15rem 0 0;
  background: var(--app);
  /* Un `fieldset` vaut `min-inline-size: min-content` par défaut : la section
     à deux colonnes refusait de descendre sous la largeur naturelle de ses
     champs et dépassait des autres sur la droite. */
  min-inline-size: 0;
}
.pt-group .field, .pt-group .row { min-width: 0; }
.pt-group .field input, .pt-group .field select { min-width: 0; width: 100%; box-sizing: border-box; }
/* Espacement entre champs voisins d'une même section. En passant `.pt-group`
   en conteneur flex on l'aurait obtenu d'un coup, mais les sections de points
   règlent leur propre écartement par des filets et des marges internes : la
   règle ne vise donc que les champs mis côte à côte. */
.pt-group > .field + .field,
.pt-group > .row + .field,
.pt-group > .field + .row { margin-top: 0.5rem; }
.pt-group legend {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.35rem;
  font-size: 0.69rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--texte-3);
}
/* Pastille accent : un repère de section, pas une décoration — c'est elle
   qu'on suit du regard en parcourant un formulaire long. */
.pt-group legend::before {
  content: '';
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}
.point-block { display: flex; flex-direction: column; gap: 0.35rem; padding: 0.35rem 0; border-top: 1px solid var(--border); }
.point-block:first-of-type { border-top: none; }
.pt-head { display: flex; gap: 0.35rem; align-items: center; }
.pt-mode { flex: 1; }
.pt-detail { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.pt-detail select, .pt-detail input { flex: 1; min-width: 6rem; }

/* Une barre encore posée en coordonnées : ses x/y ACCOMPAGNENT le sélecteur
   d'élément au lieu de le remplacer, sur leur propre ligne, en dessous. */
.pt-coords { flex: 1 0 100%; display: flex; gap: 0.35rem; flex-wrap: wrap; }
.pt-coords input { flex: 1 1 6rem; min-width: 6rem; }
.pt-coords .field-help { flex: 1 0 100%; margin: 0; }

/* Les deux bouts d'une pièce, dans la même chemise « Contraintes ». Un
   sur-titre suffit à les séparer : ce sont deux réglages du même objet, pas
   deux sujets — un second cadre les aurait éloignés l'un de l'autre. */
.pt-bout + .pt-bout { margin-top: 0.6rem; padding-top: 0.5rem; border-top: 1px solid var(--filet-panneau); }
.pt-bout h4 {
  margin: 0 0 0.3rem;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--texte-3);
}
/* Le premier bloc de chaque bout ne porte pas le filet de `.point-block` : le
   sur-titre sépare déjà. */
.pt-bout .point-block { border-top: none; padding-top: 0; }

/* ---------------------------------------------------------------------------
   LE VISEUR — voir `piquage.js`.

   Il accompagne tout champ qui désigne un élément de la scène. L'enveloppe
   reprend la souplesse du champ qu'elle remplace, sinon la ligne se
   réorganiserait autour d'elle et les champs cesseraient de s'aligner d'un
   bloc à l'autre.
   --------------------------------------------------------------------------- */
.champ-pique { display: flex; gap: 0.3rem; align-items: stretch; flex: 1 1 12rem; min-width: 0; }

/* LES DEUX ÉCARTS D'UN POINT, CÔTE À CÔTE — ils disent une POSITION, pas deux
   réglages, et empilés ils prenaient la moitié de la fiche. Deux colonnes tant
   que le panneau les porte ; en dessous de 26 rem, elles retombent l'une sous
   l'autre plutôt que de réduire les champs à une fente. */
.pt-ecarts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.4rem 0.6rem;
  align-items: start;
}
.pt-ecarts .field { margin: 0; min-width: 0; }
/* LE VISEUR EN TÊTE DU CHAMP, et non centré sur sa hauteur : la zone POUSSE
   avec la formule, et un bouton qui descendrait avec elle cesserait d'être là
   où la main le cherche. */
.pt-ecarts .champ-pique { align-items: flex-start; }
.pt-ecarts .champ-pique > .formula { flex: 1 1 auto; min-width: 0; }

/* UNE LIGNE AU REPOS, autant qu'il en faut ensuite — voir `champFormule`. Pas
   de poignée de redimensionnement : la hauteur suit le texte, et une poignée
   qui la contredit donne deux vérités. */
textarea.formula.pousse {
  resize: none;
  overflow: hidden;
  min-height: 2.1rem;
}
.champ-pique > select, .champ-pique > .ep { flex: 1 1 auto; min-width: 0; }
/* LA FORMULE PREND LA PLACE à côté de son viseur — sans cette règle elle
   tenait dans une fente. */
.champ-pique > .formula { flex: 1 1 auto; min-width: 0; }

/* DANS UNE COLONNE, le `flex: 1 1 12rem` ci-dessus devient une HAUTEUR.
   Il est écrit pour une rangée — `.pt-detail`, où le champ s'aligne avec ses
   voisins —, et `label.field` est un `flex` en colonne : la base de 12 rem s'y
   appliquait donc à la hauteur, et le viseur s'étirait sur 192 pixels. Le
   sélecteur qui vivait là remplissait l'espace et masquait la faute ; elle est
   apparue le jour où il est parti. */
.field > .champ-pique { flex: 0 0 auto; align-items: center; }

/* CE QU'ON A PIQUÉ, écrit à côté du viseur — « D'après » d'un ouvrage neuf.
   Ce n'est pas une saisie : on ne tape pas un code d'ouvrage, on le vise à
   l'écran. Elle prend donc la forme d'un champ sans en être un, pour que la
   ligne garde son alignement avec les vrais champs du formulaire. */
.dapres-choix {
  flex: 1 1 auto;
  min-width: 0;
  align-self: center;
  padding: 0.35rem 0.5rem;
  border: 1px dashed var(--filet-champ);
  border-radius: 4px;
  font-size: 0.85rem;
  color: var(--texte-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Vierge : le trait reste, le mot s'efface — la place est là, elle attend. */
.dapres-choix.vide { color: var(--muted); font-style: italic; }

.pique {
  flex: none;
  width: 1.85rem;
  padding: 0;
  font-size: 0.9rem;
  line-height: 1;
  color: var(--texte-3);
  background: var(--champ);
  border: 1px solid var(--filet-champ);
  border-radius: 4px;
  box-shadow: none;
  cursor: pointer;
}

.pique:hover { color: var(--texte); border-color: var(--filet-actif); }

/* Le viseur ARMÉ prend le minium plein : c'est une action en cours, et la scène
   attend un clic. Même signal que l'outil enclenché du rail. */
.pique.actif,
.pique.actif:hover {
  color: var(--action-texte);
  background: var(--action);
  border-color: var(--action);
  transform: none;
}
/* Petite touche : même relief, en plus court. */
button.mini { padding: 0.05rem 0.4rem; font-size: 0.85rem; line-height: 1.4; }
.through-mode { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid var(--border); font-size: 0.8rem; }
.through-mode label { display: flex; align-items: center; gap: 0.4rem; }
/* Mode conservé alors qu'il ne s'applique plus au nombre de points : visible
   parce que c'est celui de la pièce, mais signalé comme un reste à corriger. */
.through-mode label.is-stale { color: #b45309; font-weight: 600; }
.field-help.warn { color: #b45309; font-weight: 600; }

/* Catalogue matière */
.catalogue { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 1.25rem; }
/* Une ligne trop longue ne se lit pas : le contenu est donc bridé. La règle ne
   vise QUE les vues à une colonne — sur la page Catalogue, les enfants directs
   sont les deux colonnes de la grille, et les brider centrait l'arbre au milieu
   de sa propre colonne. La page Catalogue, elle, a rejoint la coquille commune
   et ne passe plus par ici — voir `.explorateur`. */
#debit.catalogue > *, #nomenclature.catalogue > *, #parametrage.catalogue > * {
  max-width: 920px;
  margin-left: auto;
  margin-right: auto;
}
.cat-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
/* Ce panneau n'a pas de fond à lui : il laisse voir celui de la page. Les
   réserves du pictogramme au tampon doivent donc prendre `--bg` et non
   `--surface`, qui est le fond des panneaux de l'explorateur. Voir
   `.icone-tampon` et la note de `.apercu-picto`. */
.catalogue { --surface: var(--bg); }
.cat-head h2 {
  margin: 0;
  font-size: 1.1rem;
  /* Le pictogramme s'aligne sur le mot, comme dans les titres de section
     de l'explorateur — voir `.pane-contenu h2`. */
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* ---------------------------------------------------------------------------
   L'EXPLORATEUR — l'arbre à gauche, ce qu'il contient à droite.

   DEUX PAGES, UNE SEULE COQUILLE : les ouvrages et le catalogue matière. Ce
   sont le même geste — on choisit un rangement, on voit ce qu'il tient — et ils
   se présentaient pourtant différemment : l'accueil en deux panneaux pleine
   hauteur, le catalogue en page qui défile avec une colonne bordée d'un filet.
   D'un écran à l'autre, on ne reconnaissait plus l'outil.

   Ces règles vivaient dans `accueil.css`, que le catalogue ne charge pas. Elles
   sont ici, sans nom de page : ce n'est pas « l'accueil », c'est un explorateur,
   et le prochain écran de ce genre n'aura rien à recopier.
   --------------------------------------------------------------------------- */
.page-explorateur {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
}

.explorateur {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  /* La même largeur qu'à l'atelier — voir `arbre.css`. */
  /* `--arbre-colonne` : la largeur tirée à la poignée — voir `brancheSeparateur`
     dans `page.js`. Sans elle, la largeur d'origine. */
  grid-template-columns: var(--arbre-colonne, minmax(var(--arbre-etroit), var(--arbre-large))) 1fr;
  /* UN FILET D'UN PIXEL, obtenu par la gouttière sur fond de bordure : deux
     panneaux qui se touchent se séparent mieux ainsi qu'avec un `border`, qui
     appartiendrait à l'un des deux. */
  gap: 1px;
  background: var(--border);
  /* Le volet d'édition FLOTTE au-dessus du contenu, comme le DÉTAIL de
     l'atelier flotte au-dessus du plan : il faut donc un repère pour l'ancrer.
     Absolu, il sort du flux et ne crée pas une troisième colonne. */
  position: relative;
}

/* LA POIGNÉE DE SÉPARATION, posée SUR le filet et plus large que lui : un
   pixel ne se vise pas. Invisible au repos — le filet suffit à séparer —, elle
   s'allume au survol et pendant le glissé pour dire qu'elle se tire. */
.explorateur-separateur {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 9px;
  margin-left: -4px;
  z-index: 3;
  cursor: col-resize;
  touch-action: none;
  outline: none;
}
.explorateur-separateur::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 3px;
  width: 3px;
  background: transparent;
  transition: background 0.12s;
}
.explorateur-separateur:hover::after,
.explorateur-separateur:focus-visible::after,
.separateur-tire .explorateur-separateur::after { background: var(--accent); }
/* Pendant le glissé, le curseur ne change pas en survolant une carte ou un
   champ, et rien ne se sélectionne sous lui. */
.separateur-tire { cursor: col-resize; user-select: none; }

/* LA COLONNE DE L'ARBRE est un PANNEAU, pas une marge. Le fond sombre la
   sépare du contenu sans qu'on ait à tracer quoi que ce soit, et c'est le même
   fond que le volet de structure de l'atelier : trois écrans, un seul endroit
   où l'on parcourt. */
.pane-arbre {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--panneau);
  color: var(--panneau-texte);
}

.pane-arbre .toolbar {
  flex: 0 0 auto;
  padding: 0.6rem;
  border-bottom: 1px solid var(--panneau-filet);
  margin-bottom: 0;
}

/* L'ARBRE DÉFILE SEUL, la page ne défile pas. Une page entière qui défile
   emporte l'arbre avec le contenu : on perd de vue où l'on est dès qu'on
   descend dans une longue liste de fiches. */
.pane-arbre .tree-root,
.pane-arbre .cat-arbre {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 0.5rem;
  margin: 0;
}

.pane-contenu {
  min-height: 0;
  overflow: auto;
  padding: 1.25rem 1.5rem;
  /* LE CONTENU EN RETRAIT — demande du 15/09 : « fais en sorte que tout se
     détache bien ». L'arbre et l'en-tête sont des barres (--surface) ; ce qu'on
     parcourt est posé plus bas (--bg), et les cartes remontent au-dessus
     (--carte). Trois plans, dans les deux thèmes, au lieu d'un seul aplat que
     seuls des filets découpaient. */
  background: var(--bg);
}

/* LE FIL D'ARIANE. On descend dans un arbre ; sans lui, on ne sait plus d'où
   l'on vient ni comment remonter d'un cran — il faut viser dans l'arbre, ce
   qui suppose de l'avoir gardé sous les yeux. */
.fil {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 1rem;
  font-size: 0.8125rem;
  flex-wrap: wrap;
}
/* À la racine le fil ne dit rien — voir `fil.js`. Sans cette règle il garderait
   sa marge, et le contenu descendrait d'un cran pour un blanc que personne
   n'aurait demandé. */
.fil:empty { display: none; }
.fil-lien {
  padding: 0;
  font: inherit;
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
}
.fil-lien:hover { text-decoration: underline; }
.fil-sep { color: var(--muted); }
.fil-ici { font-weight: 600; }

/* LE TITRE D'UNE SECTION de contenu — « Sous-gammes (3) », « Profils (12) ».
   Petit, en capitales et en retrait : il RANGE, il n'annonce pas. Un titre qui
   pèse autant que les fiches qu'il coiffe fait lire l'écran par ses intitulés
   plutôt que par son contenu. */
.pane-contenu h2 {
  margin: 1.5rem 0 0.6rem;
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.pane-contenu > h2:first-child,
.pane-contenu > *:first-child > h2:first-child { margin-top: 0; }

/* Le premier bloc ne se détache pas du haut de la colonne. */
.contenu > h2:first-child { margin-top: 0; }

/* ---------------------------------------------------------------------------
   LES PICTOGRAMMES DU MÉTIER — voir `icones.js`.

   DEUX FAMILLES D'ICÔNES, et il ne faut pas les mélanger : les COMMANDES — le
   crayon, le soleil, les outils du rail — sont tracées sur une grille de 24 au
   trait 1,6 ; les OBJETS du modèle — une gamme, un profil, un décor — le sont
   sur une grille de 48 au trait 2,6. Un pictogramme d'objet posé dans un bouton
   d'outil s'y lirait deux fois trop épais, et l'inverse deux fois trop maigre.
   --------------------------------------------------------------------------- */
.icone { display: block; width: 1em; height: 1em; flex: 0 0 auto; }

/* L'écriture de PLAN : aucun plein, un trait constant. Les tracés qui portent
   l'accent le déclarent eux-mêmes et l'emportent sur cette règle. */
.icone-trait {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* L'ÉCRITURE AU TAMPON POSE SON PICTOGRAMME SUR UNE PLAQUE — voir les jetons
   `--picto-*`. C'est la planche qui le veut ainsi, et elle est constante : son
   bloc « tampon » encadre chaque dessin, son bloc « trait » les laisse nus. La
   plaque appartient donc à l'ÉCRITURE et non à l'endroit où l'on s'en sert :
   déclarée ici une fois, elle n'est recopiée ni dans l'arbre, ni dans les titres
   de section, ni dans la feuille de l'espace de commande. Ces endroits-là ne
   disent plus que la taille du dessin.

   `--surface` REPREND LE FOND DE LA PLAQUE. Le tampon n'entoure pas ses détails,
   il les ÉVIDE : les barreaux d'un portail, le perçage d'une pièce sont du
   papier découpé dans l'encre, et ce papier doit valoir le fond sur lequel il
   tombe. Ce fond est désormais celui de la plaque, quoi qu'il y ait dessous —
   c'est ce qui a permis de retirer la reprise au survol dans `arbre.css`. */
.icone-tampon {
  width: var(--picto-large);
  height: var(--picto-haut);
  padding: var(--picto-marge) 0;
  border: var(--picto-filet);
  border-radius: var(--picto-rayon);
  background: var(--picto-fond);
  --surface: var(--picto-fond);
}

/* L'écriture au TAMPON : des masses, jamais de contour. Ce qui n'est pas
   déclaré prend la couleur du texte — le noir du dessin d'origine. */
.icone-tampon { fill: currentColor; stroke: none; }

/* LE PICTOGRAMME D'UN TITRE DE SECTION. Il se cale sur la hauteur des
   capitales et non sur la ligne entière : posé sur la ligne de base d'un titre
   en petites capitales, il paraîtrait flotter au-dessus du mot. */
.pane-contenu h2 {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}
/* Le DESSIN garde ses 1,15 em ; la plaque se calcule autour — `.icone-tampon`. */
.pane-contenu h2 .icone { --picto-encre: 1.15em; }

/* LE PICTOGRAMME D'UNE CARTE DE RANGEMENT, à la place de la vignette.

   MÊME ENCOMBREMENT qu'une photo d'ouvrage — 16/9 — et non la taille du dessin :
   c'est l'alignement qui fait lire deux cartes comme une seule grille, pas la
   mesure du contenu. Le pictogramme se centre dedans, en retrait : il dit ce
   qu'on regarde, il n'est pas ce qu'on regarde. */
.apercu-picto {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 3px;
  background: var(--bg);
  color: var(--texte-3);
}
/* PAS DE PLAQUE SUR UNE VIGNETTE, et c'est la seule exception. Ces deux
   pictogrammes-là tiennent déjà dans un cadre visible — l'emprise 16 / 9 de la
   vignette — et une plaque dedans ferait une boîte dans une boîte. Les réserves
   reprennent alors le fond de la vignette : `inherit` va rechercher celui que
   `.apercu-picto` pose au-dessus, plutôt que d'en nommer un deuxième ici. */
.apercu-picto .icone,
.apercu-ouvrage.vide .icone {
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 0;
  background: none;
  --surface: inherit;
}
/* La vignette a son propre fond, plus sombre que celui de la page : les
   réserves du tampon doivent le prendre, sinon elles se lisent en relief. */
.apercu-picto { --surface: var(--bg); }

/* UNE CARTE-DESTINATION — une sous-gamme, un sous-rayon. Elle se clique en
   entier : ce n'est pas une fiche qu'on lit, c'est un endroit où l'on entre. */
.carte-gamme { cursor: pointer; }
/* Le pictogramme s'anime avec la carte : c'est elle qu'on désigne, pas lui. */
.carte-gamme:hover .apercu-picto { color: var(--accent); }
.carte-gamme:hover { border-color: var(--accent); }

/* CE QU'UN ARBRE DIT PENDANT QU'IL CHARGE. Un mot plutôt qu'une colonne vide :
   l'attente est courte, mais une colonne blanche pendant une demi-seconde se
   lit comme « il n'y a rien », et c'est ce qu'on croit avant de comprendre. */
.vide {
  padding: 0.5rem;
  font-size: 0.8125rem;
  color: var(--panneau-sourd);
  list-style: none;
}
/* UNE PARTIE VIDE qui propose de se remplir depuis le fichier du client : le
   mot, puis le bouton, sans l'étirer sur toute la largeur. Voir `echange.js`. */
.vide-echange { display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem; padding: 0.5rem; }

/* ---------------------------------------------------------------------------
   LA CARTE ET SA GRILLE — communes à l'accueil et au catalogue.

   Elles vivaient dans `accueil.css`. Une fiche de profil n'a pourtant aucune
   raison de se présenter autrement qu'un ouvrage : c'est le même geste — on
   parcourt du regard, on choisit. Les petits encadrés valent mieux que la
   ligne pleine largeur qu'avait le catalogue, où trois informations
   s'étiraient d'un bord à l'autre de l'écran.
   --------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------
   LE FILTRE D'UNE GRILLE — voir `grilleFiltree` dans `carte.js`.

   À DROITE et discret : ce n'est pas le sujet de la page, c'est un outil qu'on
   va chercher. À gauche, au-dessus de la première carte, il aurait pris la
   place du regard — celle où l'on cherche ce qu'on est venu voir.
   --------------------------------------------------------------------------- */
.filtre {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.filtre-champ {
  width: 14rem;
  max-width: 100%;
  padding: 0.3rem 0.5rem;
  font-size: 0.8125rem;
}
/* « 3 sur 27 », et rien tant qu'on n'a rien tapé : un compte qui ne bouge pas
   n'apprend rien, et il ferait double emploi avec celui de l'onglet. */
.filtre-compte { font-size: 0.75rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* RIEN NE CORRESPOND. La grille vide ne dirait rien d'elle-même — on croirait
   la page cassée plutôt que le filtre trop étroit. */
.grille.rien::after,
.cat-list.rien::after {
  content: 'Rien ne correspond.';
  color: var(--muted);
  font-size: 0.85rem;
  font-style: italic;
}

.grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.75rem;
}

/* UNE GRILLE DÉCOUPÉE EN BLOCS TITRÉS — voir `grilleGroupee`.

   Le socle d'une gamme reçoit des CHAPITRES entiers du référentiel, et le
   chapitre est conservé : trente cartes d'affilée effaceraient ce qu'on vient
   d'y verser par blocs.

   Le titre est SOURD et en capitales : il nomme un rangement, il ne réclame
   pas le regard. Les cartes restent ce qu'on lit.

   ET IL S'OUVRE. Le bloc se replie sur son titre, comme une branche d'arbre :
   même case, même signe, même geste — on ne réapprend pas à ranger selon
   l'écran où l'on se trouve. `--trait` n'existe que dans `.arbre` : il faut le
   redonner ici, sinon la bordure de la case est invalide et la case disparaît
   sans rien dire. */
.grilles { display: flex; flex-direction: column; gap: 1.25rem; }

.grille-titre {
  --trait: var(--filet-champ);
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  /* Le titre ne va pas plus loin que son texte : cliquer trois centimètres à
     droite du dernier mot, dans le vide, ne doit pas replier le bloc. */
  align-self: flex-start;
  user-select: none;
}
.grille-titre:hover { color: var(--texte-2); }

/* Le compte de ce que le bloc tient. Discret : c'est un renseignement, pas un
   intitulé — et il compte ce que le bloc CONTIENT, pas ce que le filtre en
   laisse voir. */
.grille-compte {
  font-variant-numeric: tabular-nums;
  opacity: 0.65;
}

/* REPLIÉ : les cartes s'en vont, le titre reste. Le filtre, lui, passe outre —
   voir `barreDeFiltre` : chercher dans un dossier fermé doit le traverser. */
.grille-groupe.replie > .grille { display: none; }
.grilles.cherche .grille-groupe.replie > .grille { display: grid; }

.carte {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.75rem;
  text-align: left;
  font: inherit;
  color: var(--text);
  /* UN FOND À ELLE — demande du 15/09 : sur le fond du panneau, la carte ne se
     détachait que par son filet. Toutes les cartes, dans toutes les rubriques. */
  background: var(--carte);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 1px 3px rgb(0 0 0 / 18%);
  transition: border-color 90ms, box-shadow 90ms;
}
/* Au survol, la carte se relève un peu et son filet s'allume : c'est une cible. */
.carte:hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  box-shadow: 0 3px 10px rgb(0 0 0 / 22%);
}

/* Un dessin en tête de carte : la barre telle qu'elle arrive. Il prend toute
   la largeur et s'y met à l'échelle — le SVG porte un `viewBox`, ses attributs
   de taille ne sont qu'un défaut. */
.carte-apercu {
  margin-bottom: 0.35rem;
  padding: 0.3rem;
  background: var(--bg);
  border-radius: 4px;
}
.carte-apercu > svg { display: block; width: 100%; height: auto; }

/* LE DESSIN d'un décor. Il garde SES proportions — un motif étiré n'est plus le
   motif —, d'où `contain` et une hauteur bornée plutôt qu'un format imposé.

   Le damier de fond n'est pas décoratif : un décor est presque toujours ajouré,
   et posé sur le fond uni de la carte, on ne distingue pas ce qui est plein de
   ce qui est vide. C'est justement la seule chose qu'on a besoin de voir. */
.apercu-decor {
  display: block;
  width: 100%;
  max-height: 8rem;
  object-fit: contain;
  margin-bottom: 0.35rem;
  padding: 0.3rem;
  border-radius: 4px;
  background:
    conic-gradient(from 90deg, var(--neutre) 25%, transparent 0 50%, var(--neutre) 0 75%, transparent 0)
    0 0 / 12px 12px,
    var(--bg);
}

/* Le dépôt d'un fichier, dans le formulaire : le champ, puis ce qu'il a
   choisi. On ne remplace pas un dessin sans le voir. */
.decor-depot { display: grid; gap: 0.5rem; }
.decor-depot .apercu-decor { max-height: 10rem; }

/* LES TEXTURES DESSINÉES — migration 178 : la tuile répétée et teintée, sur la
   carte comme dans la fiche. La toile garde son rapport : c'est la répétition
   qu'on juge, une tuile étirée tromperait. */
.apercu-texture {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 1;
  margin-bottom: 0.35rem;
  border-radius: 4px;
  border: 1px solid var(--filet);
}
.apercu-texture.grand { aspect-ratio: 480 / 220; }
/* Le code collé se lit en chasse fixe : on y cherche une balise, pas une phrase. */
.cat-form .texture-svg-code {
  width: 100%;
  box-sizing: border-box;
  min-height: 8rem;
  font-family: var(--mono, ui-monospace, Consolas, monospace);
  font-size: 0.75rem;
  white-space: pre;
  resize: vertical;
}
.texture-relief { display: flex; align-items: center; gap: 0.6rem; }
.texture-relief input[type="range"] { flex: 1 1 auto; }
.texture-relief-valeur { min-width: 4.5rem; font-variant-numeric: tabular-nums; color: var(--texte-2); }
.texture-apercu { display: grid; gap: 0.4rem; margin-top: 0.5rem; }

/* LES ARTICLES POSÉS SUR UNE ACCROCHE — migration 183 : un encadré par article. */
.articles-poses { display: grid; gap: 0.5rem; margin: 0.3rem 0; }
.article-pose { border: 1px solid var(--filet); border-radius: 6px; padding: 0.45rem 0.55rem; display: grid; gap: 0.3rem; }
.article-pose-tete { display: flex; gap: 0.4rem; align-items: center; }
.article-pose-tete select { flex: 1 1 auto; min-width: 0; }

/* LA FORME 3D D'UN ARTICLE — migration 182 : un bloc par volume, et l'aperçu
   posé sur un bout de montant. */
.volumes-liste { display: grid; gap: 0.6rem; margin: 0.4rem 0; }
.volume-bloc { border: 1px solid var(--filet); border-radius: 6px; padding: 0.5rem 0.6rem; display: grid; gap: 0.35rem; }
.volume-tete { display: flex; align-items: center; justify-content: space-between; }
.apercu-volumes { position: relative; height: 18rem; border-radius: 6px; overflow: hidden; border: 1px solid var(--filet); }

/* LES VARIANTES DE PROFIL D'UNE PIÈCE — migration 181 : une ligne par variante,
   « Si », la condition, le profil, et la croix. */
.variantes-liste { display: grid; gap: 0.35rem; margin: 0.3rem 0; }
.variante-ligne { display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr) auto; gap: 0.4rem; align-items: start; }
.variante-ligne .variante-si { padding-top: 0.35rem; font-size: 0.8rem; color: var(--texte-2); }
.variante-ligne select, .variante-ligne textarea { min-width: 0; width: 100%; box-sizing: border-box; }

.cat-aide {
  margin: 0.2rem 0 0.6rem;
  font-size: 0.75rem;
  color: var(--muted);
}

/* La PHOTO d'un ouvrage : une bande 16/9, exactement le format dans lequel elle
   est prise. `cover` ne recadre donc rien — il est là comme garde-fou pour les
   photos d'avant, prises au format du volet 3D. */
.apercu-ouvrage {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 3px;
}

/* Pas encore de photo : la bande reste, vide. Un TIRETÉ et non un aplat —
   c'est ce qui dit « il n'y a rien ici » plutôt que « ça charge ». Même
   convention que la ligne d'arbre qui ne range rien. */
.apercu-ouvrage.vide {
  border: 1px dashed var(--border);
  background: none;
  /* Le cadre peut porter le pictogramme de ce qui viendra s'y ranger — voir
     `carteNouveau` dans `accueil.js`. Vide, la grille ne change rien. */
  display: grid;
  place-items: center;
}

.carte-code {
  font-family: ui-monospace, monospace;
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  color: var(--muted);
}

/* Une carte se manœuvre comme une ligne d'arbre : le clic la DÉSIGNE, le
   double-clic l'OUVRE. Elle en porte donc le LISERÉ d'accent — la même marque
   pour « voici ce que je regarde », qu'on soit dans une colonne ou dans une
   grille.

   Elle garde en plus un fond teinté, que la ligne d'arbre a perdu : une carte
   est une surface posée sur le fond de la page, et rien d'autre ne la
   distingue de ses voisines. Une ligne d'arbre, elle, est tenue par sa branche
   et par son rang — l'aplat n'y ajoutait rien et empâtait la colonne. */
.carte[data-cle] { cursor: pointer; }
/* LE SURVOL DIT CE QU'ON VISE, sur toute carte qu'on désigne — profil, article,
   décor, cote, ouvrage : le même bord que la carte d'une sous-gamme. Seules les
   destinations l'avaient, et une grille de fiches ne répondait pas à la souris. */
.carte[data-cle]:hover { border-color: var(--accent); }

/* L'ÉTIQUETTE D'UNE CARTE — le genre d'une fiche du catalogue, au-dessus de son
   titre : profils, articles et décors partagent une grille, c'est la carte qui
   dit ce qu'elle est. Sourde, pas une couleur par genre : le genre se lit, il ne
   doit pas faire de la grille un nuancier. */
.carte-etiquette {
  align-self: flex-start;
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.carte.choisie {
  background: var(--choix-fort);
  box-shadow: inset 3px 0 0 var(--accent);
  border-color: var(--accent);
}

.carte-titre { font-weight: 600; }

.carte-sous {
  font-size: 0.75rem;
  color: var(--muted);
}

.carte-note {
  font-size: 0.75rem;
  font-style: italic;
  color: var(--muted);
}

.carte-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: auto;
  padding-top: 0.6rem;
}

.carte-actions button { font-size: 0.75rem; }

/* LE LISERÉ du haut : une bande de touches d'angle, posée SUR la carte.

   Elle ne prend aucune place dans le flux — une rangée de touches en bas
   doublait la hauteur de chaque fiche alors qu'on parcourt une grille pour
   reconnaître, pas pour agir.

   Elle est TOUJOURS VISIBLE. Je l'avais d'abord fait apparaître au survol, en
   me disant que trente croix à la fois feraient du bruit : on ne la trouvait
   plus. Une commande qu'il faut deviner n'existe pas. Elle est discrète par sa
   taille et sa couleur, pas par son absence. */
.carte-bandeau {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  z-index: 1;
  display: flex;
  gap: 0.2rem;
}
/* Le titre d'une carte qui porte un liseré s'arrête avant lui : la croix ne se
   pose plus sur la dernière lettre du nom. */
.carte:has(> .carte-bandeau) > .carte-titre { padding-right: 1.9rem; }

/* LES PASTILLES D'ÉTAT D'UN OUVRAGE, SOUS SA PHOTO — voir `cartesDouvrages`.
   Une ligne à elles, qui passe à la suivante s'il le faut : elles ne cachent
   ni le nom ni la photo. */
.carte-etats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem;
  margin: 0 0 0.2rem;
  pointer-events: none;
}

/* PUBLIÉ — une pastille, pas une touche : c'est un état qu'on lit en parcourant
   la grille, et rien ne s'y clique. Elle ne paraît que sur les ouvrages publiés ;
   marquer aussi les autres mettrait une pastille sur chaque carte, qui ne
   distinguerait plus rien.

   En minium, comme tout ce qui désigne : la charte réserve cette couleur à
   l'action, à la marque et à la désignation, et « publié » est bien ce qui
   désigne un ouvrage parmi ses voisins. Sur fond plein plutôt qu'en texte seul —
   la pastille se pose sur la photo, dont on ne sait pas la couleur. */
.carte-publie {
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* La carte entière se saisit au glissé : la pastille ne doit pas intercepter
     le geste, elle n'a rien à en faire. */
  pointer-events: none;
}

/* La case « Publié » et sa glose, dans le volet d'édition. Un filet la sépare
   du nom et de la gamme : ce n'est pas une propriété de plus, c'est une mise en
   vente. */
.champ-publication {
  margin-top: 0.8rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--filet);
}
.champ-publication .explication { margin: 0.3rem 0 0; }

/* « À VALIDER » : creuse — pas encore chez les installateurs. Pleine quand
   c'est à celui qui regarde de la valider. */
.carte-a-valider {
  background: transparent;
  color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.carte-a-valider.a-toi { background: var(--accent); color: #fff; box-shadow: none; }

/* « OBJET À IMPORTER » : grise et creuse — il ne part pas chez les installateurs,
   rien ne doit le faire lire comme une mise en vente. Voir la migration 116. */
.carte-objet {
  background: var(--surface);
  color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--border);
}

.champ-circuit .circuit-titre { font-size: 0.8rem; color: var(--muted); }

/* LA VERSION FIGÉE OUVERTE — migration 121 : un bandeau sous l'en-tête, et ce
   qui écrit s'éteint. Le serveur refuse de toute façon ; ceci évite de cliquer
   pour l'apprendre. */
.bandeau-version {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.45rem 1rem;
  background: color-mix(in srgb, var(--accent) 16%, var(--surface));
  border-bottom: 1px solid var(--accent);
  font-size: 0.85rem;
}
.lecture-seule .jite-form button[type="submit"],
.lecture-seule [data-action="ajouter"],
.lecture-seule #bouton-annuler { display: none; }
.champ-circuit .circuit-etat { margin: 0.3rem 0 0; }
.champ-circuit .circuit-dernier q { display: block; margin-top: 0.2rem; font-style: italic; }
/* LES TOUCHES DU CIRCUIT, en trois rangs par ce qu'elles font — voir
   `champCircuit` : l'acte en tête, contouré à l'accent (« Enregistrer », plein,
   reste le seul bouton plein du formulaire) ; les versions à regarder en liens ;
   le retrait, discret et rouge, sous un filet. Le motif prend toute la largeur. */
.champ-circuit .circuit-touches { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.7rem; }
.champ-circuit .circuit-actes,
.champ-circuit .circuit-ligne { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.champ-circuit .circuit-actes:empty,
.champ-circuit .circuit-voir:empty,
.champ-circuit .circuit-retraits:empty { display: none; }
.champ-circuit button.circuit-acte { color: var(--accent); border-color: var(--accent); background: transparent; }
.champ-circuit button.circuit-acte:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); color: var(--action-texte); }
.champ-circuit .circuit-voir { display: flex; flex-wrap: wrap; gap: 0.2rem 1.1rem; }
.champ-circuit button.circuit-lien,
.champ-circuit button.circuit-retrait {
  padding: 0;
  border: 0;
  background: none;
  font-size: 0.85rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: transparent;
}
.champ-circuit button.circuit-lien { color: var(--texte-2); text-decoration-color: var(--filet-champ); }
/* Le bloc se FERME d’un filet : ses touches agissent tout de suite, Enregistrer dessous non. */
.champ-publication.champ-circuit { padding-bottom: 0.8rem; border-bottom: 1px solid var(--filet); }
.champ-circuit button.circuit-lien:hover,
.champ-circuit button.circuit-retrait:hover { background: none; text-decoration-color: currentColor; }
.champ-circuit button.circuit-lien:hover { color: var(--accent); }
.champ-circuit .circuit-retraits { padding-top: 0.6rem; border-top: 1px dashed var(--filet); }
.champ-circuit button.circuit-retrait { color: var(--danger); }
.champ-circuit button.circuit-lien:focus-visible,
.champ-circuit button.circuit-retrait:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.champ-circuit .circuit-motif textarea { width: 100%; box-sizing: border-box; }
.champ-circuit .circuit-bilan:empty { display: none; }
.champ-circuit .circuit-bilan { margin-top: 0.5rem; }
.champ-circuit .circuit-defauts { color: var(--danger); font-size: 0.85rem; }
.champ-circuit .circuit-defauts p { margin: 0 0 0.25rem; }
.champ-circuit .circuit-defauts ul { margin: 0; padding-left: 1.1rem; }
.champ-circuit .circuit-defauts li + li { margin-top: 0.2rem; }

.carte-croix {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  font-size: 1rem;
  line-height: 1;
  color: var(--texte-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: none;
  cursor: pointer;
}
.carte-croix:hover,
.carte-croix:focus-visible {
  color: #FFFFFF;
  background: var(--danger);
  border-color: transparent;
  outline: none;
}

/* La liste du catalogue EST cette grille. Les titres de rayon et le
   formulaire, eux, prennent toute la largeur : ce ne sont pas des fiches, et
   les glisser dans une colonne les aurait fait passer pour telles. */
.cat-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.75rem;
  align-items: start;
}
.cat-list > h3,
.cat-list > form,
.cat-list > p { grid-column: 1 / -1; }

/* Les sections de la vue Catalogue — rangement, profils, quincaillerie. Un
   filet et de l'air les séparent : ce sont trois choses distinctes, et un
   simple espacement les aurait laissées se lire comme une suite. */
/* `.cat-second` — le filet qui séparait les trois inventaires — a disparu :
   leurs titres de section les séparent maintenant, exactement comme
   « Sous-gammes » et « Ouvrages » à l'accueil. Deux séparations pour une seule
   coupure, c'en était une de trop. */

/* LA PAGE CATALOGUE employait ici sa propre grille, sa propre colonne bordée
   d'un filet et sa propre largeur de contenu. Tout cela est parti dans la
   coquille commune — voir `.explorateur` plus haut : c'est le même écran que
   les ouvrages, il n'avait aucune raison de se présenter autrement.

   Ne reste de `.catalogue` que ce qui sert aux DEUX AUTRES vues qui empruntent
   la classe, à l'atelier : le Débit et le Paramétrage. Elles n'ont qu'une
   colonne et défilent, ce qui est bien leur cas. */

.cat-arbre-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.cat-arbre-tete h2 { margin: 0; font-size: 0.95rem; }

/* La pastille, le chevron, le compteur et la sélection viennent d'`arbre.css` :
   c'est le même arbre que celui des gammes, il se lit pareil. Ne reste ici que
   ce que celui-ci a en propre — un intitulé qui est un BOUTON, parce que le
   chevron et le nom sont deux cibles distinctes, et deux actions au survol. */
.cat-arbre-nom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  font-size: 0.85rem;
}
.cat-arbre-libelle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Plus de puce de FICHE : l'arbre du catalogue ne range que des rayons. La
   distinction profil / article — une barre qui se débite, un rond qui se
   compte — se lit maintenant sur la carte, où la fiche vit. */

.cat-arbre-item.choisie .cat-arbre-nom { color: var(--accent); font-weight: 600; }

/* « Tout le catalogue » et « Non rangé » encadraient l'arbre. Les deux ont
   disparu : l'un ne rangeait rien, l'autre n'a plus rien à ranger. L'arbre ne
   montre que des rayons. */

/* Les deux actions n'apparaissent qu'au survol de leur ligne : elles seraient
   du bruit sur chaque dossier en permanence, et la colonne est étroite. */
.cat-arbre-action { opacity: 0; padding: 0.05rem 0.3rem; }
.cat-arbre-item:hover .cat-arbre-action,
.cat-arbre-action:focus-visible { opacity: 1; }

/* `.cat-dossier-titre` a disparu avec la liste groupée qu'il coiffait : la
   colonne de droite ne montre plus qu'UN dossier, celui qu'on a désigné, et
   son nom est déjà en surbrillance dans l'arbre. */

/* ---------------------------------------------------------------------------
   LES DEUX RAYONS DU CATALOGUE — matière, puis famille d'usage.

   Deux niveaux de titre, et ils ne se ressemblent pas : la matière porte un
   filet et de l'espace au-dessus, la famille n'est qu'un sur-titre en petites
   capitales. Un seul niveau lisible aurait suffi à ranger, pas à HIÉRARCHISER
   — et la hiérarchie est justement ce qu'on vient chercher : dans quoi je
   travaille, puis quelle pièce.

   Le premier titre n'a pas de marge haute : il touche déjà le bandeau.
   --------------------------------------------------------------------------- */
.cat-matiere {
  margin: 1.4rem 0 0.2rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--filet-panneau);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--texte);
}
.cat-list > .cat-matiere:first-child { margin-top: 0; }

.cat-famille {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0.7rem 0 0;
  font-family: var(--police-valeur);
  font-size: 0.68rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--texte-3);
}

/* `.cat-compte` est parti avec ce titre, et `.arbre-compte` l'a suivi : une
   ligne de dossier ne compte plus ce qu'elle tient. Voir `arbre.css`. */

/* Sélecteur d'élément en arborescence */
.ep { position: relative; flex: 1 1 12rem; min-width: 0; }
.ep-trigger {
  display: flex; align-items: center; gap: 0.4rem; width: 100%;
  text-align: left; cursor: pointer;
}
.ep-trigger:hover { border-color: var(--filet-actif); }
.ep-trigger.is-empty { color: var(--muted); }
.ep-current { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ep-current b { font-family: ui-monospace, monospace; font-weight: 600; }
.ep-empty { flex: 1; font-style: italic; }
.ep-caret { flex: none; font-size: 0.65rem; color: var(--muted); }

.ep-panel {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 3px);
  max-height: 17rem; overflow: auto;
  background: var(--overlay); border: 1px solid var(--filet-overlay);
  box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14); padding: 0.35rem;
}
.ep-search { width: 100%; margin-bottom: 0.3rem; }
/* La liste est elle aussi une arborescence : elle prend la mise en forme
   commune d'`arbre.css`, et ne garde ici que le nécessaire — un dossier n'y est
   pas cliquable, et une feuille est un bouton. */
.ep-group { display: contents; }
.ep-folder { cursor: default; font-weight: 600; color: var(--texte-2); }
.ep-leaf {
  width: 100%;
  border: 0;
  font: inherit;
  font-size: 0.8rem;
  text-align: left;
}
.ep-mark { flex: none; font-size: 0.7rem; color: var(--accent); }
.ep-mark.part { color: #64748b; font-size: 0.8rem; }
.ep-code { font-family: ui-monospace, monospace; flex: none; }
.ep-lab { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.74rem; color: var(--muted); }
.ep-none { margin: 0.5rem; color: var(--muted); font-size: 0.8rem; }

/* Débit */
.debit-note { color: var(--muted); font-size: 0.85rem; margin: 0 0 1.25rem; }
.debit-block { background: var(--surface); border: 1px solid var(--border); padding: 0.9rem 1rem; margin-bottom: 0.9rem; }
.debit-block h3 { margin: 0; font-size: 0.95rem; }
.debit-sub { margin: 0.15rem 0 0.75rem; color: var(--muted); font-size: 0.8rem; }
.debit-table { width: 100%; border-collapse: collapse; font-size: 0.87rem; }
.debit-table th { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); font-weight: 600; }
.debit-table th, .debit-table td { text-align: left; padding: 0.35rem 0.6rem; }
.debit-table th { padding-top: 0; }
/* Les bords du tableau se calent sur le texte du bloc, pas sur un creux. */
.debit-table th:first-child, .debit-table td:first-child { padding-left: 0; }
.debit-table th:last-child, .debit-table td:last-child { padding-right: 0; }
.debit-table td { border-top: 1px solid var(--border); overflow-wrap: break-word; }

/* L'ALIGNEMENT SUIT LA CLASSE, JAMAIS LE RANG.
   Il se decidait ici au numero de colonne — `th:nth-child(2)` a droite,
   `th:nth-child(3)` a gauche — pendant que le JS marquait deja ses cellules
   `num`. Deux verites pour une meme colonne : le jour ou le debit a gagne sa
   colonne Cintrage, tous les en-tetes ont glisse d'un cran et PIECES est parti
   se poser a droite d'une colonne alignee a gauche. Une seule regle desormais,
   et c'est la cellule qui la porte : `num` a droite, tout le reste a gauche. */
.debit-table th.num, .debit-table td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Ce que le tenon ajoute, SOUS la cote de coupe : un renseignement, pas une
   cote de plus — il ne doit pas se lire comme une seconde longueur, ni elargir
   la colonne des cotes au point de les desaligner. */
.debit-tenon { display: block; color: var(--muted); font-size: 0.85em; }

/* LE SENS ET LE RAYON DU CINTRE. Deux lignes du bloc Cintrage qui ne sont pas
   des ORIGINES — elles ne prennent donc pas de bouton radio, et se lisent en
   retrait comme le repere fleche. */
.champ-sens, .champ-rayon { display: block; margin-top: 0.3rem; font-size: 0.9rem; }
.champ-rayon input { width: 6rem; }

/* LE TENON D'UN BOUT, sous l'élément qui le tient — voir `part-form.js`.
   Sur une ligne, parce que c'est une précision et non une contrainte de plus :
   la barre est déjà tenue quand on se demande si elle pénètre. */
.pt-tenon { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.35rem; }
.pt-tenon .pt-tenon-mm { width: 6rem; }

/* LE DEBIT EN DEUX PARTIES — le plan de coupe, puis le reglage de butee.
   Un intertitre sourd les separe : ce sont deux documents, pas deux moities
   d'un tableau. */
.debit-part { margin-top: 0.9rem; }
.debit-part h4 {
  margin: 0 0 0.4rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}

/* UNE GRILLE FIXE POUR LES TABLEAUX QUI SE REPETENT.
   Ces trois tableaux reviennent une fois par profile : tant que chacun
   calibrait ses colonnes sur son propre contenu, LONGUEUR tombait a 291 px
   dans un bloc et a 373 px dans le suivant, et l'oeil ne pouvait plus
   descendre la page. Les largeurs sont donc posees une fois pour toutes ;
   seule la derniere colonne — celle des noms de pieces — prend ce qui reste. */
.debit-plan, .debit-coupes, .nomen-coupes { table-layout: fixed; }
.debit-plan th:nth-child(1) { width: 5.5rem; }
.debit-plan th:nth-child(3) { width: 7rem; }
/* La colonne des cotes prend la largeur de son PLUS LARGE contenu — ici la
   note de tenon —, et pas un pouce de plus : un nombre calé à droite dans une
   colonne trop large laisse un vide qui detache le tableau du bloc. */
.debit-coupes th:nth-child(1) { width: 11rem; }
.debit-coupes th:nth-child(2) { width: 6rem; }
.debit-coupes th:nth-child(3) { width: 4rem; }
.nomen-coupes th:nth-child(1) { width: 8rem; }
.nomen-coupes th:nth-child(2) { width: 4rem; }

/* Le numero de barre accompagne, il ne se compare pas. */
.debit-plan td:first-child { white-space: nowrap; color: var(--muted); }
.debit-chute-source { font-style: italic; }

/* Ce qu'aucune scie ne peut suivre : dit en clair, au-dessus du plan. */
.debit-refus {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--danger);
}

.debit-total { margin: 0.75rem 0 0; padding-top: 0.6rem; border-top: 1px solid var(--border); font-size: 0.85rem; color: var(--muted); }

/* LA NOMENCLATURE réemploie le bloc du Débit — même largeur de page, même
   encadré, même tableau — parce que c'est la même famille de lecture : une
   feuille d'atelier. Ce qui suit ne dit que ce qui lui est propre.

   `.nomen-table` ne porte plus aucun alignement : il n'y avait de rattrapage à
   faire que tant que le Débit rangeait ses colonnes par leur rang. */

/* Une pièce plus longue que sa barre : elle ne se coupe pas. Le minium est
   réservé à l'action et à la marque — ici c'est un REFUS de la matière, pas
   une commande : on le dit par le fond, sans emprunter l'accent. */
.nomen-alerte td { background: color-mix(in srgb, var(--danger) 14%, transparent); }
.nomen-alerte td:first-child::after { content: ' — plus longue que la barre'; color: var(--danger); font-size: 0.8em; }

/* Ce qui accompagne sans réclamer le regard : l'intitulé derrière un code,
   la liste des zones derrière un compte. */
.debit-block .sourd { color: var(--muted); }

.nomen-refus { color: var(--danger); }

/* LE PRIX DE REVIENT, en tete. Le chiffre a ACHETER porte, les autres
   accompagnent : c'est celui-la qu'on vient chercher, et le lire d'abord evite
   de confondre avec la matiere consommee. */
.nomen-prix-chiffres { margin: 0.4rem 0 0; }
.nomen-prix-achete { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.nomen-prix-quoi { font-size: 0.9rem; }
.nomen-prix-sourd { color: var(--muted); font-size: 0.85rem; }
.nomen-cout { font-variant-numeric: tabular-nums; }

/* Ce qui n'a pas de prix : la raison plutot qu'un zero. Le tiret des tableaux
   porte la raison en infobulle — la colonne n'a pas la place d'une phrase. */
.nomen-refus { color: var(--danger); }

.nomen-liste { margin: 0; padding-left: 1.1rem; color: var(--muted); font-size: 0.85rem; }
.nomen-liste li { margin: 0.15rem 0; }
.nomen-liste code { font-size: 0.9em; color: var(--texte-2); }

/* LES RÉSERVES : ce que la liste ne sait pas encore. Sans encadré — elles ne
   sont pas une section de plus à compter, mais la marge du document. */
.nomen-reserves { margin: 1.25rem 0 0; padding-top: 0.9rem; border-top: 1px solid var(--border); }
.nomen-reserves h3 { margin: 0 0 0.4rem; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted); }
.nomen-absents .nomen-liste code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }


/* LA FICHE D'UNE LAME. Ce qu'elle MONTRE est calculé — rang, largeur, surface —
   et se lit donc comme un relevé, pas comme un formulaire : des définitions, et
   non des champs grisés qui laisseraient croire qu'on peut les corriger. */
.lame-fiche {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.75rem;
  margin: 0.75rem 0;
  font-size: 0.85rem;
}
.lame-fiche dt { color: var(--muted); }
.lame-fiche dd { margin: 0; font-variant-numeric: tabular-nums; }
.lame-reglage { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--border); }

/* LES TROIS VALEURS D'UN REPÈRE REÇU : ce que la source dit, ce que la scène a
   calculé, et ce que l'hôte impose. En liste de définitions parce que c'en est
   une — un terme, sa valeur — et non en tableau, qui aurait promis des colonnes
   comparables. */
.imf-valeurs {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.7rem;
  margin: 0.6rem 0;
}
.imf-valeurs dt { color: var(--muted); font-size: 0.9em; }
.imf-valeurs dd { margin: 0; }

/* REÇU D'UN AUTRE OUVRAGE : ni tiré, ni masqué, ni rangé ailleurs.

   PLUS DE LISERÉ MINIUM. Il disait « ça vient d'ailleurs » ligne par ligne, ce
   qui était juste tant qu'une ligne reçue voisinait avec des lignes propres.
   Dans un volet qui n'a QUE de l'importé — les variables d'un ouvrage reçu, par
   exemple — il se pose sur tout, et une marque que portent toutes les lignes ne
   distingue plus rien : elle repeint la colonne en orange et ne dit plus rien.

   Le minium est d'ailleurs réservé à l'ACTION et à la désignation. En faire un
   état permanent le dépensait pour rien, et rendait la ligne désignée — même
   minium, même bord gauche — indiscernable de ses voisines.

   Ce qui reste le dit aussi bien, et sans couleur : l'ITALIQUE du libellé, la
   ligne un peu éteinte, et surtout le DOSSIER D'IMPORT qui coiffe la branche et
   porte le nom de l'ouvrage d'où tout cela vient. Le liseré répétait à chaque
   ligne ce que la branche annonçait déjà une fois.

   Le retrait de la bordure REMET les lignes reçues dans l'alignement de
   l'arbre : `.tnode` n'en a pas, les lignes importées étaient décalées de 2 px. */
.tnode.is-importe { opacity: 0.85; }
.tnode.is-importe .tlabel { font-style: italic; }

/* ── LE BLOC D'IMPORT ─────────────────────────────────────────────────────
   LE CHOIX D'UN OUVRAGE À IMPORTER : les cartes de l'accueil, en RANGÉE — la
   photo à gauche, le nom et le code à droite —, parce que le volet est étroit
   et qu'une liste se parcourt de haut en bas. `grid-template-areas` range ce
   que `carte()` pose dans son ordre à elle : le DOM ne change pas, l'œil
   trouve la photo d'abord. Le filtre reste en tête quand la liste défile. */
.imp-choix {
  max-height: 46vh;
  overflow-y: auto;
  margin-bottom: 0.8rem;
  padding-right: 0.2rem;
}
.imp-choix .grille { grid-template-columns: 1fr; gap: 0.4rem; }
.imp-choix .grilles { gap: 0.9rem; }
.imp-choix .filtre {
  position: sticky;
  top: 0;
  z-index: 1;
  justify-content: flex-start;
  margin-bottom: 0.5rem;
  padding-bottom: 0.4rem;
  background: var(--panneau);
}
.imp-choix .filtre-champ { flex: 1 1 auto; width: auto; }

.carte-import {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  grid-template-areas: 'apercu titre' 'apercu code' 'apercu etats' 'apercu sous';
  align-content: start;
  column-gap: 0.65rem;
  row-gap: 0.1rem;
  padding: 0.45rem 0.6rem;
  cursor: pointer;
}
.carte-import .carte-apercu { grid-area: apercu; margin: 0; padding: 0.15rem; align-self: center; }
.carte-import .carte-titre { grid-area: titre; }
.carte-import .carte-code { grid-area: code; }
.carte-import .carte-etats { grid-area: etats; margin: 0.15rem 0 0; }
.carte-import .carte-sous { grid-area: sous; }
.carte-import:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ÉTEINTE : l'ouvrage courant. Montré plutôt que retiré — un ouvrage absent
   de la liste ferait chercher pourquoi. */
.carte-import.eteinte { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
.carte-import.eteinte:hover { border-color: var(--border); }

.imp-liste { list-style: none; margin: 0 0 0.8rem; padding: 0; }
.imp-liste { list-style: none; margin: 0 0 0.8rem; padding: 0; }
.imp-liste li { display: block; padding: 0.15rem 0; }
.imp-liste .imp-ligne,
.imp-orphelins .imp-liste li { display: flex; align-items: center; gap: 0.4rem; }

/* UN RÉGLAGE QUI NE DÉSIGNE PLUS RIEN. Décalé sous son import et sourd : ce
   n'est pas une panne — l'ouvrage se dessine très bien sans lui — mais un
   travail resté sans objet, dont il faut décider. */
.imp-orphelins {
  margin: 0.2rem 0 0.4rem 1.2rem;
  padding-left: 0.6rem;
  border-left: 2px solid var(--muted);
}
.imp-orphelins .imp-liste { margin-bottom: 0; }

/* LES HOMONYMES : même retrait que les orphelins, mais au liseré de l'action —
   ce n'est pas un travail resté sans objet, c'est un travail qu'on s'épargne. */
.imp-homonymes {
  margin: 0.2rem 0 0.4rem 1.2rem;
  padding-left: 0.6rem;
  border-left: 2px solid var(--accent);
}
.imp-homonymes .field-help { margin: 0 0 0.2rem; }
.imp-homonymes .imp-liste { margin-bottom: 0.3rem; }
.imp-homonymes .imp-liste li { display: flex; align-items: center; gap: 0.3rem; }

.imp-pied {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.8rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--trait-2, rgba(128, 128, 128, 0.3));
}

.imp-choisi { flex: 1; color: var(--muted); font-size: 0.9em; }

/* LE NOM D'ICI ET LE PRÉFIXE, côte à côte — migration 148. Le nom se lit, le
   préfixe s'écrit dans les formules : deux champs, deux longueurs. Un préfixe
   long ferait des codes illisibles — « pg » passe, « pilier_de_gauche » non. */
.imp-nommage {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.6rem 1rem;
  width: 100%;
  margin-bottom: 0.6rem;
}
.imp-nommage .field { margin: 0; }
.imp-nommage .field-help { flex: 1 1 100%; margin: 0; }
.imp-nom { width: 14rem; }
.imp-prefixe { width: 6rem; font-family: ui-monospace, monospace; }

/* RETOURNÉ — migration 157. La case s'aligne sur le bas des champs, et le
   bouton ⇆ d'un import retourné s'allume. */
.imp-case { display: inline-flex; align-items: center; gap: 0.35rem; padding-bottom: 0.35rem; }
.imp-retourne.actif { background: var(--accent); color: var(--accent-texte, #fff); border-color: var(--accent); }
.tnode .tretourne { margin-left: 0.35rem; color: var(--muted); font-style: normal; }

/* Sous quels noms un import est entré. Discret : c'est un rappel, pas une
   alarme. */
.imp-badge {
  font-family: ui-monospace, monospace;
  font-size: 0.85em;
  color: var(--muted);
}

/* `.cat-card` et sa demi-douzaine de règles ont disparu : une fiche est
   désormais une `.carte`, comme un ouvrage. Ne restent que l'erreur, et
   l'aperçu tel qu'il paraît DANS UN FORMULAIRE — où il n'est pas en tête de
   carte mais au fil de la saisie. */
.cat-error { color: var(--danger); font-size: 0.85rem; background: #2A181B; border: 1px solid #6E3238; padding: 0.5rem 0.7rem; }
.cat-meta { font-size: 0.8rem; color: var(--muted); margin: 0.25rem 0; }
/* L'éditeur de FORME : une consigne par ligne, dans l'ordre où elles se
   suivent. Les colonnes sont fixes pour que l'œil descende la liste sans
   rechercher où est le rayon à chaque ligne — même si les champs d'une droite
   et ceux d'un arc ne sont pas les mêmes. */
.chemin-liste { display: flex; flex-direction: column; gap: 0.3rem; }
.chemin-ligne {
  display: grid;
  grid-template-columns: 6.5rem 1fr 1fr auto auto auto;
  gap: 0.3rem;
  align-items: center;
}
/* Un champ caché garde sa colonne : sans ça, les lignes se décalent selon
   qu'elles portent une droite ou un arc, et la liste devient illisible. */
.chemin-ligne > [hidden] { display: block !important; visibility: hidden; }
.chemin-actions { display: flex; gap: 0.35rem; margin-top: 0.45rem; }

/* Ce que la forme donne sans qu'on l'ait saisi. Détaché de l'aide au-dessus
   par un filet : ce ne sont pas des explications mais des COTES, et elles
   changent à chaque frappe. */
.chemin-bilan {
  margin: 0.55rem 0 0;
  padding-top: 0.45rem;
  border-top: 1px solid var(--filet-panneau);
  color: var(--texte-2);
  font-variant-numeric: tabular-nums;
}
.chemin-bilan:empty { display: none; }

/* Un repère PILOTÉ : sa formule ne s'applique plus, une pièce lui impose sa
   valeur. En minium, parce que c'est la seule ligne de la fiche qui contredise
   le champ juste au-dessus. */
.field-help.pilote { color: var(--action); }
/* Et dans l'arbre, la valeur pilotée se distingue de celle qu'on a tapée. */
.tval.pilote { color: var(--action); }

.cat-apercu { background: var(--bg); padding: 0.2rem; }
.cat-apercu-live { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; margin-top: 0.6rem; }
.row-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.chips { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.chip { font-size: 0.75rem; padding: 0.1rem 0.5rem; border: 1px solid var(--border); background: var(--bg); }
.chip.default { border-color: var(--accent); color: var(--accent); font-weight: 600; }

.cat-form {
  background: var(--surface);
  border: 1px solid var(--accent);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 0.8rem;
}
.cat-form h3 { margin: 0; font-size: 0.95rem; }
/* Dans le VOLET, le formulaire n'est plus une carte posée dans la grille : le
   bloc flottant l'encadre déjà. */
#cat-fiche-form .cat-form { margin: 0; padding: 0; border: 0; background: none; }

.len-block { border-top: 1px solid var(--border); padding-top: 0.6rem; }
.len-block-head { display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem; color: var(--muted); margin-bottom: 0.4rem; }
.len-rows { display: flex; flex-direction: column; gap: 0.4rem; }
.len-row { display: grid; grid-template-columns: 1fr 1fr auto auto; gap: 0.4rem; align-items: center; }
.len-default { display: flex; align-items: center; gap: 0.25rem; font-size: 0.8rem; color: var(--muted); }

@media (max-width: 1100px) {
  .layout { grid-template-columns: 1fr; height: auto; }
  .workspace { min-height: 60vh; }
}

/* ---------------------------------------------------------------------------
   LES RÈGLES DE FABRICATION — la vue. Voir `regles.js`.

   La liste est faite de CARTES, comme partout ailleurs — la table qu'elle avait
   d'abord faisait un langage de plus dans une application qui en a assez. Ce
   qui lui est propre, c'est le VERDICT en tête du liseré, et il obéit à la
   charte des couleurs : tenue en sourdine — c'est l'état normal, il ne crie
   pas —, cassée en carmin pour un refus et en ambre pour un avertissement, non
   examinée en ambre aussi : c'est une mise en garde, pas une faute de
   l'ouvrage.
   --------------------------------------------------------------------------- */
.verdict { display: inline-block; min-width: 1.2em; text-align: center; font-weight: 700; cursor: help; }
.verdict-tenue    { color: var(--muted); }
.verdict-cassee   { color: var(--danger); }
.verdict-prevenue { color: #b45309; }
.verdict-inconnue { color: #b45309; }
.verdict-eteinte  { color: var(--muted); opacity: 0.6; }
/* ---------------------------------------------------------------------------
   LE SOCLE D'UNE GAMME — les cotes qu'elle emploie, AVANT ses règles et avant
   son contenu. Voir `socle.js`.

   L'ordre de lecture dit la dépendance : une gamme déclare d'abord ce qu'elle
   MESURE, ensuite ce qu'elle INTERDIT — une règle cite les cotes du socle —,
   et enfin ce qu'elle range.
   --------------------------------------------------------------------------- */
.gamme-socle { margin-bottom: 1.25rem; }
.gamme-socle:empty { display: none; }

.carte-cote-gamme .carte-titre { padding-right: 5.5rem; }
.carte-cote-gamme .carte-code { overflow-wrap: anywhere; }
.carte-cote-gamme.modifiable { cursor: pointer; }
.carte-cote-gamme.modifiable:hover { border-color: var(--accent); }
.carte-cote-gamme.modifiable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* UNE COTE HÉRITÉE se lit mais ne s'édite pas d'ici : la changer changerait la
   valeur de toutes ses sœurs. En retrait, avec un filet tireté qui dit qu'elle
   vient d'ailleurs — et le bouton « Surcharger ici » pour en poser une propre. */
.carte-cote-gamme.heritee { border-style: dashed; }
.carte-cote-gamme.heritee .carte-titre { color: var(--texte-2); }

/* RÉGLABLE : une pastille d'état, à la place qu'occupe « publié » sur un
   ouvrage. Le minium, parce que c'est ce qui DÉSIGNE cette cote parmi les
   autres — celle que l'installateur aura sous la main. */
.socle-pastille {
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--action-texte);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  pointer-events: none;
}

/* ---------------------------------------------------------------------------
   LE TRAIT DE COTE, en tête d'une carte de socle — voir `traitDeCote`.

   C'est LA différence de silhouette entre une cote et une règle, et elle dit
   quelque chose : une cote MESURE, elle porte donc le trait qu'on tire sur un
   plan, couché pour une largeur et dressé pour une hauteur, avec sa valeur
   écrite dessus ; une règle JUGE, elle n'a pas de trait mais un filet de
   gravité sur son bord. On ne les confond plus, et la confusion levée apprend
   ce que chacune est.
   --------------------------------------------------------------------------- */
.cote-trait {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.2rem;
}
.cote-glyphe {
  flex: 0 0 auto;
  width: 2.75rem;
  height: 0.75rem;
  fill: none;
  stroke: var(--texte-3);
  stroke-width: 1.2;
  stroke-linecap: round;
}
.cote-glyphe .plein { fill: var(--texte-3); stroke: none; }

/* DEBOUT pour une hauteur. Le glyphe pivote, la valeur reste lisible — une cote
   verticale s'annote horizontalement sur un plan, on ne tourne pas la tête.

   Il RACCOURCIT en pivotant : une rotation ne change pas la boîte de mise en
   page, et les 2,75 rem couchés devenaient 2,75 rem de HAUT, qui débordaient
   d'une bande qui n'en fait que 2,2. */
.cote-trait.debout .cote-glyphe { width: 1.75rem; transform: rotate(-90deg); }

.cote-valeur {
  font-family: ui-monospace, monospace;
  font-size: 0.8125rem;
  color: var(--texte);
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   UNE CARTE COCHÉE, et la barre de ce qu'on fait du lot.

   COCHER N'EST PAS DÉSIGNER, et les deux marques ne se ressemblent donc pas :
   la carte DÉSIGNÉE porte le liseré d'ACCENT — « voici ce que je regarde » —,
   la carte COCHÉE le même liseré au JAUNE SIGNAL — « celle-ci part avec les
   autres ». On peut regarder l'une et en emporter cinq.

   Ce n'est pas une invention : c'est mot pour mot la règle de `.tnode.picked`,
   écrite pour l'arbre et pour la même raison. Même grammaire — un liseré, pas
   un lavis —, même teinte. Les cotes du référentiel se manœuvrent en lot
   depuis qu'on les ajoute à une gamme, elles héritent donc de la marque.
   --------------------------------------------------------------------------- */
.carte.picked {
  background: #3A2F14;
  box-shadow: inset 3px 0 0 var(--alerte);
  border-color: var(--alerte);
}

/* LA BARRE DU LOT : ce qu'on a coché, et ce qu'on peut en faire. Elle ne paraît
   que cochée — voir `majBarreLot`.

   AU JAUNE SIGNAL, comme les cartes qu'elle compte, et non en minium : elle
   parle du LOT. Le minium reste pour ce qui agit — la touche qu'elle porte, et
   qui est bien la seule chose ici qui engage quelque chose.

   ELLE FLOTTE EN BAS DU PANNEAU, PAR-DESSUS LE CONTENU. Posée au-dessus de la
   grille, elle poussait toutes les cartes à chaque case cochée : la carte
   qu'on visait partait sous le pointeur. Et collée en haut, elle masquait le
   titre du chapitre.

   Comment : le panneau devient une colonne, et le porte-barre — de hauteur
   NULLE, donc sans place dans le flux — est poussé au pied (`margin-top:
   auto`) et y colle en défilant (`sticky`). La barre, elle, sort de ce
   porte-barre vers le haut. Le contenu garde en bas une marge fixe, pour que la
   dernière rangée remonte au-dessus d'elle. */
#cotes .pane-contenu { display: flex; flex-direction: column; }
#cote-contenu { padding-bottom: 5rem; }
.lot-flottant {
  position: sticky;
  bottom: 0;
  z-index: 3;
  flex: none;
  height: 0;
  margin-top: auto;
}
.lot-barre {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.75rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--neutre);
  border: 1px solid var(--alerte);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}
.lot-barre[hidden] { display: none; }

.lot-compte {
  font-weight: 600;
  color: var(--texte);
}

/* CE QUI NE PARTIRA PAS, et pourquoi. Il pousse les touches à droite : c'est
   la seule chose ici qu'on LIT, le reste s'actionne. */
.lot-detail {
  flex: 1 1 14rem;
  font-size: 0.8125rem;
  color: var(--muted);
}

.lot-barre .field-help { flex: 1 1 auto; margin: 0; }

/* UNE COTE NUE — sans nom de variable, ou sans sens. Cochée, elle porte la
   marque SOURDE et non le jaune signal : elle fait partie de ce qu'on a
   désigné, pas de ce qui partira. C'est la distinction qui manquait — on en
   cochait cinq, il en arrivait une, et rien ne disait lesquelles.

   Elle n'est marquée QUE COCHÉE. Cent trente-sept cartes éteintes en
   permanence donneraient un référentiel en berne, alors que ces cotes-là se
   lisent et se documentent très bien ; c'est seulement pour un lot qu'elles ne
   valent pas. */
.carte.picked.cote-nue {
  background: var(--neutre);
  box-shadow: inset 3px 0 0 var(--texte-3);
  border-color: var(--texte-3);
}

/* ---------------------------------------------------------------------------
   LA CARTE D'IDENTITÉ D'UNE COTE, en tête de sa fiche.

   La fiche du socle ne montrait que le LIEN — une valeur, des bornes — et
   résumait la cote en une ligne grise. On ouvrait donc une fiche sans y
   trouver ce qu'on venait y chercher : de quoi cette cote parle, où elle se
   relève, sur quel genre d'ouvrage, de quel côté, dans quelle unité.

   ELLE NE RESSEMBLE PAS AUX CHAMPS qui la suivent, et c'est le but : un aplat
   sourd, pas de cadre de saisie, rien qui invite à écrire. On LIT le haut de la
   fiche, on RÈGLE le bas. Le jour où l'on voudra la changer, c'est « Cotes et
   jeux » qu'il faudra ouvrir — la dernière ligne le dit.
   --------------------------------------------------------------------------- */
.cote-identite {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.75rem 0.875rem;
  margin-bottom: 1rem;
  background: var(--neutre);
  border: 1px solid var(--filet);
  border-radius: 6px;
}

/* L'INTITULÉ porte la taille d'un titre de carte, pas celle d'un h3 : le titre
   du volet dit déjà « Cote « entre_piliers » » juste au-dessus, et deux titres
   de même poids l'un sur l'autre ne se hiérarchisent plus. */
.cote-identite-nom {
  font-weight: 600;
  color: var(--texte);
}

.cote-identite-code {
  font-family: ui-monospace, monospace;
  font-size: 0.8125rem;
  color: var(--accent);
  overflow-wrap: anywhere;
}

/* LA SITUATION : chapitre · typologie · sens · côté. Quatre mots courts sur une
   ligne, séparés par des points médians — quatre champs titrés auraient fait un
   formulaire de ce qui est une carte d'identité. */
.cote-identite-situation {
  font-size: 0.75rem;
  color: var(--muted);
}

.cote-identite-detail,
.cote-identite-note {
  margin: 0.25rem 0 0;
  font-size: 0.8125rem;
  color: var(--texte-2);
}
.cote-identite-note { font-style: italic; color: var(--muted); }

.cote-identite .field-help { margin-top: 0.5rem; }

/* LE FILET DE GRAVITÉ d'une règle, sur son bord gauche. Les mêmes deux
   couleurs que le verdict et la pastille : la même chose ne change pas de
   teinte d'un endroit à l'autre. */
.regle-refus-bord { border-left: 3px solid var(--danger); }
.regle-avertit-bord { border-left: 3px solid #b45309; }

/* MIN ET MAX CÔTE À CÔTE : ce sont les deux bouts d'une même plage, et les
   empiler ferait deux champs en colonne où l'on ne verrait plus le couple.

   Cette règle existait pour le domaine de fabrication et elle est partie avec
   lui ; le socle en a le même besoin, pour les bornes d'un réglage. */
.cotes-paire {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.4rem;
}

/* ---------------------------------------------------------------------------
   CE QU'UNE GAMME IMPOSE — ses règles, à l'accueil, AU-DESSUS de ce qu'elle
   range. Voir `Regles.#serre()` dans `regles.js`.

   Des CARTES, comme tout ce que cette page montre. Ni titre de section, ni
   filet : cette page a justement retiré les siens parce qu'ils coupaient la
   grille en deux, et une table à six colonnes précédée d'un pavé refaisait la
   faute en pire.
   --------------------------------------------------------------------------- */
.gamme-regles { margin-bottom: 1.25rem; }
.gamme-regles:empty { display: none; }

/* LE CODE laisse la place du liseré, qui flotte au-dessus de lui.

   La réserve ne suffit pas seule : un code d'un seul tenant ne s'y plie pas et
   la traverse — `largeur_maximale_de_fabrication_alu` passait sous la pastille,
   vu au banc. Il faut lui DONNER le droit de se couper n'importe où : les
   césures ordinaires n'ont pas prise sur un mot sans espace. */
.carte-regle .carte-titre { padding-right: 5.5rem; }
.carte-regle .carte-code { overflow-wrap: anywhere; }

.carte-regle.modifiable { cursor: pointer; }
.carte-regle.modifiable:hover { border-color: var(--accent); }
.carte-regle.modifiable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Une règle éteinte se lit encore — on peut la rallumer — mais en retrait. */
.carte-regle.regle-eteinte { opacity: 0.55; }

/* LA CONDITION EN PIED, sous un filet : c'est la mécanique de la règle, on la
   lit après ce qu'elle veut dire. Elle passe à la ligne plutôt que de déborder —
   une carte ne défile pas. */
.carte-pied {
  margin-top: auto;
  padding-top: 0.5rem;
  border-top: 1px solid var(--filet);
}
.regle-si {
  display: block;
  font-family: ui-monospace, monospace;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--texte-2);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* LA GRAVITÉ, dans le liseré, à la place qu'occupe « publié » sur un ouvrage.
   Les deux couleurs sont celles du verdict de l'atelier — carmin pour ce qui
   refuse, ambre pour ce qui prévient : c'est la même chose qui est dite, et
   elle ne doit pas changer de teinte d'un écran à l'autre.

   Le refus est en aplat, l'avertissement en filet seul : l'un arrête, l'autre
   accompagne, et le poids visuel doit le dire avant qu'on lise le mot. */
.regle-pastille {
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* La carte entière se clique : la pastille n'a rien à intercepter. */
  pointer-events: none;
}
.regle-refus { background: var(--danger); color: var(--barre); }
.regle-avertit { border: 1px solid #b45309; color: #b45309; background: var(--barre); }


.regle-form { margin-top: 1.5rem; max-width: 40rem; }
.regle-form h3 { margin: 0 0 0.75rem; font-size: 0.95rem; }
.regle-form textarea { font-family: ui-monospace, monospace; font-size: 0.85rem; width: 100%; box-sizing: border-box; }
.regle-form .form-actions button + button { margin-left: 0.5rem; }

/* Une pièce ou une zone ABSENTE — sa condition de présence est fausse sur la
   scène actuelle. En retrait, pas masquée : elle s'édite, elle attend son
   état. Voir la migration 057. */
.tnode.is-absente .tlabel { opacity: 0.55; }
.tabsente { color: var(--muted); font-size: 0.72rem; margin-left: 0.35rem; white-space: nowrap; }

/* ---------------------------------------------------------------------------
   LE RÉFÉRENTIEL DES COTES — la vue. Voir `cotes.js` et la migration 058.

   Le filtre par typologie (commun, battant, coulissant) et ses pastilles sont
   partis avec la typologie elle-même — décision du 15/09, migration 136.
   --------------------------------------------------------------------------- */


/* Un rangement vide : en retrait, jamais masqué. Voir `cotes.js`. */
.cote-vide { opacity: 0.45; }

/* LA CARTE D'UNE COTE DU RÉFÉRENTIEL. Même visage que sur le socle d'une gamme
   — c'est la même grandeur —, avec en plus les deux champs qu'on y écrit.

   La table à six colonnes qui vivait ici a disparu : elle était le seul
   tabulaire d'une application qui range tout en cartes, et la même cote
   paraissait sous deux formes selon l'écran. */
/* Le liseré FLOTTE au-dessus du haut de la carte : la bande de valeur et le
   type lui laissent la place, sinon deux pastilles viennent s'asseoir sur
   une cote longue. */
/* Deux lignes de valeur, pas plus : une énumération entière — « béton · pavé ·
   gravier · terre » — déséquilibrerait la bande, et la note dit le reste. Ce
   n'est plus l'alignement des titres qui l'exige, il est structurel ; c'est la
   régularité de la grille. */
.carte-cote-ref .carte-apercu { height: 3.1rem; overflow: hidden; }
.carte-cote-ref .cote-valeur {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* LE TITRE laisse la place du liseré, qui flotte au-dessus du haut de la carte.
   C'est lui qui est en tête depuis que l'alignement est structurel ; la réserve
   l'a suivi. */
.carte-cote-ref .carte-titre { padding-right: 5.5rem; }
.carte-cote-ref .carte-sous { font-style: italic; }

/* LES DEUX CHAMPS, en pied, sous un filet : ce sont les seules choses qu'on
   écrit ici. Côte à côte tant qu'il y a la place, l'un sous l'autre sinon — le
   nom de variable est long, le sens tient en un mot. */
.cote-champs { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: flex-end; }
.cote-champ { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.cote-champ > span {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.cote-champ:first-child { flex: 0 0 auto; }
.cote-champ:last-child { flex: 1 1 9rem; }

/* LE SENS : une liste fermée, à peine plus large que son plus long mot. */
.cote-sens-choix { font-size: 0.8rem; padding: 0.2rem 0.3rem; }

/* LE CÔTÉ, dans le liseré à côté de la typologie : deux états qu'on lit en
   parcourant, et qui ne se cliquent ni l'un ni l'autre. Deux cartes voisines
   portent le même intitulé — « Aplomb de pilier » deux fois — et c'est ce mot
   qui dit laquelle on nomme. */
.cote-chip-cote { color: var(--texte-2); }
.carte-cote-ref .carte-bandeau .cote-chip { pointer-events: none; }
.cote-chip {
  display: inline-block;
  font-size: 0.68rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.12rem 0.4rem;
  border: 1px solid var(--filet-panneau);
  border-radius: 3px;
  background: var(--neutre);
  white-space: nowrap;
}

/* Le nom de variable : une saisie en chasse fixe, parce que c'en est un — il
   s'écrira tel quel dans une formule. Assez large pour `largeur_entre_piliers`. */
.cote-code {
  width: 100%;
  min-width: 0;
  font-family: ui-monospace, monospace;
  font-size: 0.8rem;
  padding: 0.2rem 0.4rem;
}

/* ------------------------------------------------------------------------ */
/* LES TRADUCTIONS — le journal pour la forme : une barre de filtres, un      */
/* tableau qu'on lit par pages. Voir `traductions.js`.                        */
/* ------------------------------------------------------------------------ */
.traductions { padding: 1rem 1.2rem; display: flex; flex-direction: column; gap: 0.8rem; }
.tr-fiche { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.6rem 1.2rem; }
.tr-fiche .field { min-width: 12rem; }
.tr-avance { font-size: 0.8125rem; color: var(--muted); }
.tr-neuve { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.6rem; }
.tr-neuve .field { min-width: 9rem; }
.tr-refus-liste { margin: 0; padding-left: 1.2rem; font-size: 0.8rem; color: var(--danger); }
.tr-cle { font-family: var(--police-valeur); font-size: 0.75rem; color: var(--muted); white-space: nowrap; }
.tr-reference { color: var(--muted); max-width: 32rem; }
.tr td { vertical-align: top; }
.tr-texte { width: 100%; min-width: 16rem; min-height: 2.2rem; resize: vertical; font: inherit; }
.tr-ok .tr-texte { box-shadow: inset 3px 0 0 var(--action); }
.tr-refuse .tr-texte { box-shadow: inset 3px 0 0 var(--danger); }

/* ═══════════════════════════════════════════════════════════════════════════
   LA PAGE NE PARAÎT QU'UNE FOIS LA SESSION CONNUE.

   Sans session, l'atelier se dessinait une demi-seconde avant d'être remplacé
   par l'écran de connexion. Le HTML des pages qui demandent une session porte
   donc `data-session="attente"` sur <html> ; `paraitre()`, dans `http.js`,
   remplace la valeur dès qu'on sait qui est là — et la page sort.

   L'animation est le FILET DE SÉCURITÉ : si le JavaScript ne répond pas du
   tout, la page paraît au bout de quatre secondes plutôt que de rester
   blanche. `visibility` et non `display` : la mise en page est déjà faite
   quand la page sort, il n'y a pas de saut.
   ═══════════════════════════════════════════════════════════════════════════ */
html[data-session="attente"] body {
  visibility: hidden;
  animation: session-secours 4s step-end forwards;
}
@keyframes session-secours { to { visibility: visible; } }
