/* ui.css — vocabulaire visuel partagé (voir CLAUDE.md "Navigation par
   panneau latéral"). shell.css garde son rôle : le rail de navigation.
   Celui-ci porte ce qui se répète d'un écran à l'autre — en-tête de page,
   listes sélectionnables, panneau de détail et ses composants.

   Valeurs reprises des maquettes validées (.design/*.dc.html), pas
   arrondies : elles s'écartent volontairement des défauts Bootstrap
   (séparateurs plus légers, bordures de boutons plus douces). */

/* ── Barres de défilement fines ────────────────────────────────────────
   Retour utilisateur : les barres système sont lourdes à côté de
   l'interface. Les deux mécanismes coexistent : `scrollbar-width` /
   `scrollbar-color` (standard — Firefox, Chrome ≥ 121, qui ignore alors
   les pseudo-éléments), et `::-webkit-scrollbar` pour Safari/anciens
   Chrome. Piste transparente, pouce gris clair arrondi, 6px : elle se
   voit quand on la cherche, pas avant. `scrollbar-color` est hérité,
   `scrollbar-width` ne l'est pas — d'où le `*`. */
:root { scrollbar-color: #d1d5db transparent; }
* { scrollbar-width: thin; }
*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 999px; }
*::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
*::-webkit-scrollbar-corner { background: transparent; }

/* ── Code Python à fond sombre ─────────────────────────────────────────
   Le script d'un appel run_script (chat) ou d'un tour d'agent : fond
   sombre + coloration par app.js::highlightPython (spans .py-*). Palette
   one-dark, relue pour le contraste sur #111827. Un seul endroit, deux
   écrans. */
pre.code-dark { background:#111827; color:#e2e8f0; border-radius:.4rem; padding:.55rem .7rem; }
.code-dark .py-kw { color:#c678dd; }
.code-dark .py-str { color:#98c379; }
.code-dark .py-num { color:#d19a66; }
.code-dark .py-cm { color:#7f848e; font-style:italic; }
.code-dark .py-bi { color:#61afef; }
.code-dark .py-def { color:#e5c07b; }
.code-dark .py-dec { color:#56b6c2; }

/* ── Hauteur de contrôle unique ─────────────────────────────────────────
   Retour utilisateur : "+ Nouveau fournisseur" (bouton d'en-tête) et
   "Enregistrer" (cartouche du panneau) n'avaient pas la même hauteur —
   chaque famille de contrôle (bouton, champ, select) avait sa propre
   formule padding+ligne, jamais confrontées entre elles. Un seul token,
   posé une fois : boutons, champs texte, `<select>` et le sélecteur
   d'icône de domaine s'y calent tous, quelle que soit la page ou la
   zone (en-tête, cartouche de panneau, ligne de formulaire) — c'est la
   confrontation ENTRE zones qui manquait, pas la cohérence au sein
   d'une zone (déjà correcte). Valeur choisie proche de l'ancienne
   hauteur de `.field-ui` (~35.6px) : c'est elle qui bougeait le moins,
   les boutons (31px/28px selon la variante) sont ceux qui gagnent
   quelques pixels. Ne s'applique jamais à `<textarea>` (`.field-grow`,
   contenu d'une source...), qui doit rester libre de grandir avec son
   contenu — seule la version mono-ligne (`input`/`select`) est
   concernée, voir la règle `.field-ui` plus bas. */
:root {
  --control-h: 2.25rem;
  /* ── Grammaire des CTA (voir CLAUDE.md « Grammaire des CTA ») ──
     Noir = ACTION (au plus un bouton principal par zone visible),
     violet = ÉTAT (sélection, interrupteur, punaise, pastille choisie),
     rouge = DESTRUCTIF en filet (jamais d'aplat rouge : réservé aux
     badges d'alerte), gris = icône seule avec un seul accent de survol. */
  --c-cta: #212529; --c-cta-hover: #000; --c-cta-ink: #fff;
  --c-ghost-ink: #374151; --c-ghost-border: #d0d5dd; --c-ghost-hover-bg: #f8fafc; --c-ghost-hover-ink: #111827;
  --c-danger-text: #b42318; --c-danger-border: #f0c2bd; --c-danger-bg: #fef3f2;
  --c-accent: #6d28d9; --c-accent-bg: #f5f3ff; --c-accent-border: #c4b5fd; --c-accent-text: #5b21b6;
  --c-icon: #9ca3af; --c-icon-hover-bg: #f3f4f6; --c-icon-hover-ink: #111827;
}

/* ── En-tête de page ─────────────────────────────────────────────────
   Le couple titre + ligne de contexte : le titre dit où on est, la
   ligne dessous dit ce que l'écran contient. */
.page-head { display:flex; align-items:baseline; justify-content:space-between; gap:1rem; margin-bottom:.9rem; }
.page-title { font-size:1.05rem; font-weight:650; letter-spacing:-.01em; color:#111827; margin:0; }
.page-sub { font-size:.78rem; color:#6b7280; margin-top:.15rem; line-height:1.5; }

/* ── Boutons ─────────────────────────────────────────────────────────
   Bordure #d0d5dd plutôt que le #6c757d de Bootstrap : moins dur à
   côté des séparateurs clairs des listes.
   Hauteur fixe (`--control-h`, box-sizing:border-box) plutôt qu'un
   padding vertical qui varie avec la taille de police — c'est ce qui
   garantit qu'un bouton d'en-tête et un bouton de cartouche s'alignent
   pile, même si `.btn-sm-ui` réduit encore la police/le padding
   horizontal. */
.btn-primary-ui, .btn-ghost-ui, .btn-danger-ui {
  height:var(--control-h); box-sizing:border-box;
  font-size:.78rem; padding:0 .7rem; border-radius:.4rem; cursor:pointer;
  white-space:nowrap; font-family:inherit; line-height:1.3;
  /* inline-flex + gap : un bouton porte une icône ET un libellé, alignés
     sur la même ligne de base optique sans bricolage de margin — et
     c'est ce même centrage flex qui centre verticalement le texte
     maintenant que le padding vertical est retiré au profit de la
     hauteur fixe. */
  display:inline-flex; align-items:center; justify-content:center; gap:.35rem;
}
.btn-primary-ui svg, .btn-ghost-ui svg, .btn-danger-ui svg { flex-shrink:0; }
.btn-primary-ui { background:var(--c-cta); color:var(--c-cta-ink); border:1px solid var(--c-cta); }
.btn-primary-ui:hover:not(:disabled) { background:var(--c-cta-hover); }
.btn-ghost-ui { background:#fff; color:var(--c-ghost-ink); border:1px solid var(--c-ghost-border); }
.btn-ghost-ui:hover:not(:disabled) { background:var(--c-ghost-hover-bg); color:var(--c-ghost-hover-ink); }
.btn-primary-ui:disabled, .btn-ghost-ui:disabled, .btn-danger-ui:disabled { opacity:.5; cursor:default; }
/* Lien-action (renommer, liens inline) : texte seul, souligné au survol —
   remplace le `btn-link` Bootstrap qui ne vivait que sur Interroger. */
.btn-link-ui { background:none; border:0; padding:0; color:var(--c-ghost-ink); font-family:inherit; font-size:.8rem; cursor:pointer; }
.btn-link-ui:hover { color:var(--c-ghost-hover-ink); text-decoration:underline; }
/* Icône seule (cartouche de carte, menu de bulle, « + » de groupe…) : gris,
   un seul accent de survol. Base commune ; la taille et la forme (rond,
   carré) restent à l'appelant. `.on` = ÉTAT, donc violet. */
.btn-icon-ui { display:inline-flex; align-items:center; justify-content:center; border:0; background:none; padding:0;
               color:var(--c-icon); cursor:pointer; border-radius:.3rem; }
.btn-icon-ui:hover:not(:disabled) { background:var(--c-icon-hover-bg); color:var(--c-icon-hover-ink); }
.btn-icon-ui:disabled { opacity:.3; cursor:default; }
.btn-icon-ui.on { color:var(--c-accent); }
/* Variante compacte (cartouche de panneau, bouton "‹ Liste"...) : police
   et padding horizontal réduits pour la densité, mais MÊME hauteur —
   c'était justement le point manquant, deux tailles de boîte pour ce
   qui devrait être une seule grille verticale dans toute l'app. */
.btn-sm-ui { font-size:.75rem; padding:0 .6rem; }
.btn-danger-ui { background:#fff; color:var(--c-danger-text); border:1px solid var(--c-danger-border); }
.btn-danger-ui:hover:not(:disabled) { background:var(--c-danger-bg); }

/* ── Liste sélectionnable ────────────────────────────────────────────
   Le liseré de sélection est la pièce maîtresse : il dit en permanence
   QUELLE ligne le panneau est en train de montrer — ce qu'une popin,
   qui recouvre tout, ne pouvait pas dire. */
.list-card { background:#fff; border:1px solid #e5e7eb; border-radius:.6rem; overflow:hidden; }
.row-item {
  display:flex; align-items:center; gap:.8rem; padding:.75rem .9rem;
  border-bottom:1px solid #eef0f3; cursor:pointer; background:none; width:100%;
  text-align:left; font-family:inherit; border-left:0; border-right:0; border-top:0;
}
.row-item:last-child { border-bottom:none; }
.row-item:hover { background:#fbfcfd; }
.row-item.selected { background:var(--c-accent-bg); box-shadow: inset 3px 0 0 var(--c-accent); }
.row-item.selected:hover { background:#f2eeff; }
/* Indicateur de sensibilité — vert public / ambre restreint. Vivait dans
   interroger.html ; remonté ici dès qu'un deuxième écran en a eu besoin
   (même consolidation que .md-body), pour que le code couleur ne dérive
   pas d'un écran à l'autre. */
.sensitivity-dot { display:inline-block; width:.5rem; height:.5rem; border-radius:50%; flex-shrink:0; }
.sensitivity-dot-public { background:#22c55e; }
.sensitivity-dot-restreint { background:#f59e0b; }

/* Intertitre entre deux listes d'un même écran (Système / Personnel,
   Utilisateurs / Rôles) : la sélection reste unique et circule de l'une
   à l'autre, ce sont deux sections d'une même liste, pas deux tableaux. */
.list-section { font-size:.7rem; text-transform:uppercase; letter-spacing:.04em; color:#6b7280; font-weight:600; margin:1.3rem 0 .5rem; }
.list-section:first-child { margin-top:0; }

/* Objectif / Todo (voir CLAUDE.md « Objectif enrichi ») — Todo est un
   Objectif auto-défini (parcoursCategory), jamais un objet séparé, donc
   une simple variante de couleur plutôt qu'un nouveau composant. Promu
   ici au 2e usage réel (parcours.html ET interroger.html en ont besoin,
   même règle déjà suivie pour .md-body/.sensitivity-dot ci-dessus).
   `border-left`, jamais `box-shadow` : .row-item.selected possède déjà
   le box-shadow inset sur ce même bord pour la SÉLECTION — les deux
   coexistent sur des propriétés CSS différentes plutôt que de s'écraser.
   Violet déjà établi (interroger.html, avant cette promotion) ; bleu pour
   Todo, non réservé ailleurs au niveau chrome (violet=Objectif,
   ambre=sensibilité/privé, sarcelle=citations, vert=public — vérifié par
   grep avant de choisir). */
:root {
  --c-objectif: #6d28d9; --c-objectif-bg: #f5f3ff; --c-objectif-border: #c4b5fd; --c-objectif-text: #5b21b6;
  --c-todo: #2563eb; --c-todo-bg: #eff6ff; --c-todo-border: #bfdbfe; --c-todo-text: #1d4ed8;
}
.row-item-objectif { border-left: 3px solid var(--c-objectif); }
.row-item-todo { border-left: 3px solid var(--c-todo); }
.list-section-objectif { color: var(--c-objectif-text); }
.list-section-todo { color: var(--c-todo-text); }
.chip-objectif { background: var(--c-objectif-bg); border-color: var(--c-objectif-border); color: var(--c-objectif-text); }
.chip-todo { background: var(--c-todo-bg); border-color: var(--c-todo-border); color: var(--c-todo-text); }

.row-main { flex:1; min-width:0; }
.row-name { font-size:.85rem; font-weight:600; color:#111827; }
.row-meta { font-size:.72rem; color:#6b7280; margin-top:.1rem; }
.row-aside { flex-shrink:0; display:flex; align-items:center; gap:.4rem; }

.status-dot { width:.5rem; height:.5rem; border-radius:50%; display:inline-block; flex-shrink:0; }
.status-running { background:#0d6efd; animation:ui-pulse 1.2s ease-in-out infinite; }
.status-done { background:#198754; }
.status-failed { background:#dc3545; }
.status-cancelled { background:#6c757d; }
.status-idle { background:#d0d5dd; }
@keyframes ui-pulse { 0%,100% { opacity:1; } 50% { opacity:.35; } }

/* ── Pastilles neutres (les couleurs sémantiques restent ailleurs) ── */
.pill-ui { font-size:.68rem; padding:.15rem .45rem; border-radius:.3rem; background:#eef2f6; color:#475467; }

/* ── Tableaux ───────────────────────────────────────────────────────── */
.tbl-ui { width:100%; border-collapse:collapse; background:#fff; }
.tbl-ui th {
  font-size:.68rem; text-transform:uppercase; letter-spacing:.04em; color:#6b7280;
  font-weight:600; padding:.5rem .6rem; text-align:left; background:#fbfcfd;
  border-bottom:1px solid #eef0f3;
}
.tbl-ui td { padding:.55rem .6rem; font-size:.78rem; color:#374151; border-bottom:1px solid #f1f3f5; }
.tbl-ui tr:last-child td { border-bottom:none; }

/* ── Le maître-détail ────────────────────────────────────────────────
   La liste ne disparaît jamais : c'est ce qui permet de passer d'un
   objet à l'autre sans rien fermer, et donc ce qui rend l'abandon
   d'édition rare (voir la garde dans panel.js). */
.master-detail { display:flex; flex:1; min-width:0; height:100%; }
.master-pane { flex:1; min-width:0; padding:1.5rem; overflow-y:auto; }
/* Panneau doublé (600 → 1200px). Fluide entre les deux plutôt que fixe à
   1200 : à 1200px fixes, un écran de 1440 ne laisserait que ~20px à la
   liste, et le maître-détail perdrait tout son sens — le liseré ne dirait
   plus quelle ligne on regarde. clamp() donne la pleine largeur doublée
   sur grand écran sans jamais écraser la liste en dessous.
   (Le bouton x1/x2 évoqué reste à faire, quand le contenu central le
   demandera.) */
.detail-pane {
  width:clamp(600px, 55vw, 1200px); flex-shrink:0;
  background:#fff; border-left:1px solid #e5e7eb;
  display:flex; flex-direction:column; overflow:hidden;
}
.detail-pane[hidden] { display:none; }

/* Écran étroit : le panneau prend toute la place, la liste s'efface
   derrière un retour explicite. Pas de troisième colonne écrasée. */
.master-detail.narrow .master-pane { display:none; }
.master-detail.narrow .detail-pane { width:auto; min-width:0; flex:1; border-left:none; }

/* ── Interrupteur on/off ─────────────────────────────────────────────
   Remplace toutes les cases à cocher : l'état se lit d'un coup d'œil à
   la position ET à la couleur, pas à un petit signe. L'`<input>` reste
   une vraie case à cocher sous le décor — même valeur, même `checked`,
   mêmes sélecteurs côté JS, et le clavier continue de fonctionner. */
.switch { position:relative; display:inline-block; width:2.1rem; height:1.2rem; flex-shrink:0; }
.switch input { position:absolute; inset:0; width:100%; height:100%; margin:0; opacity:0; cursor:pointer; z-index:1; }
.switch .track { position:absolute; inset:0; background:#d1d5db; border-radius:999px; transition:background .18s ease; }
.switch .track::before {
  content:""; position:absolute; top:.15rem; left:.15rem; width:.9rem; height:.9rem;
  background:#fff; border-radius:50%; box-shadow:0 1px 3px rgba(16,24,40,.3);
  transition:transform .18s ease;
}
.switch input:checked + .track { background:var(--c-accent); }
.switch input:checked + .track::before { transform:translateX(.9rem); }
.switch input:focus-visible + .track { outline:2px solid #ddd6fe; outline-offset:2px; }
.switch input:disabled { cursor:default; }
.switch input:disabled + .track { opacity:.45; }

.switch-sm { width:1.75rem; height:1rem; }
.switch-sm .track::before { width:.72rem; height:.72rem; top:.14rem; left:.14rem; }
.switch-sm input:checked + .track::before { transform:translateX(.75rem); }

.switch-row { display:flex; align-items:flex-start; gap:.6rem; padding:.3rem 0; cursor:pointer; }
.switch-row .switch-label { font-size:.8rem; color:#374151; line-height:1.45; }
.switch-row input:disabled ~ .switch-label { color:#9ca3af; }
/* Multi-sélection (domaines, rôles, prismes) : une colonne de plus dès
   que le panneau est large, ce qui évite une liste interminable. */
.switch-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(14rem, 1fr)); gap:0 1.2rem; }

/* ── Attente ─────────────────────────────────────────────────────────
   Pour ce qui prend visiblement du temps — un appel LLM, typiquement. */
.spinner-ui {
  display:inline-block; width:.85rem; height:.85rem; vertical-align:-.1rem;
  border:2px solid #ddd6fe; border-top-color:#6d28d9; border-radius:50%;
  animation:spin .7s linear infinite;
}
@keyframes spin { to { transform:rotate(360deg); } }
.loading-note { display:flex; align-items:center; gap:.5rem; font-size:.78rem; color:#6b7280; }
.panel-back { display:none; align-items:center; gap:.35rem; }
.master-detail.narrow .panel-back { display:inline-flex; }

/* ── En-tête du panneau ─────────────────────────────────────────────
   Le label de type dit CE QU'ON REGARDE sans avoir à le déduire. */
/* Cartouche d'actions : le séparateur isole le destructif du reste, pour
   qu'un clic sur « Supprimer » ne soit jamais un dérapage depuis
   « Enregistrer ». */
.panel-actions-sep { width:1px; height:1.15rem; background:#e5e7eb; margin:0 .2rem; display:inline-block; vertical-align:middle; }

.panel-head { padding:1.1rem 1.25rem .5rem; border-bottom:1px solid #f1f3f5; flex-shrink:0; }
.panel-head-row { display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; }
.panel-type { font-size:.68rem; text-transform:uppercase; letter-spacing:.05em; color:#6b7280; }
.panel-title { font-size:1.02rem; font-weight:650; letter-spacing:-.01em; color:#111827; margin-top:.15rem;
  display:flex; align-items:center; gap:.4rem; }
.panel-actions { display:flex; align-items:center; gap:.45rem; flex-shrink:0; }
/* Le cartouche est un seul <span> : sans flex+gap ici, ses boutons se
   collaient les uns aux autres (le gap de .panel-actions ne s'applique
   qu'à ses enfants directs). */
.panel-custom-actions { display:inline-flex; align-items:center; gap:.45rem; }
.panel-close { background:none; border:0; color:#9ca3af; font-size:1.05rem; cursor:pointer; padding:0 .2rem; line-height:1; }
.panel-close:hover { color:#4b5563; }
.panel-body { padding:1rem 1.25rem; overflow-y:auto; flex:1; display:flex; flex-direction:column; gap:.85rem; }
/* Un enfant de conteneur flex se COMPRIME par défaut : sans ceci, un long
   rapport écrase le sélecteur d'exécutions et l'en-tête au-dessus de lui
   (défaut observé). Le corps défile déjà, rien ne doit se comprimer. */
.panel-body > * { flex-shrink:0; }

/* Bloc qui reste sous les yeux quand le contenu en dessous est long : on
   garde en permanence QUELLE exécution on regarde. Les marges négatives
   compensent le padding de .panel-body — sinon le contenu défilerait
   visiblement dans l'espace laissé au-dessus du bloc collé. */
.panel-sticky {
  position:sticky; top:0; z-index:2; background:#fff;
  margin:-1rem -1.25rem 0; padding:1rem 1.25rem .7rem;
  border-bottom:1px solid #f1f3f5;
  display:flex; flex-direction:column; gap:.7rem;
}

/* Badge « modifié » : ce qui déclenche la garde est ce qui se voit. */
.dirty-badge { font-size:.7rem; font-weight:500; color:#b45309; background:#fef3c7; padding:.1rem .4rem; border-radius:.3rem; margin-left:.35rem; vertical-align:middle; }

/* ── Onglets ────────────────────────────────────────────────────────── */
.panel-tabs { display:flex; gap:1.25rem; margin-top:.7rem; }
.tab-ui {
  padding:.55rem .1rem; font-size:.8rem; color:#6b7280; background:none; border:0;
  border-bottom:2px solid transparent; cursor:pointer; font-family:inherit; white-space:nowrap;
}
.tab-ui:hover { color:#374151; }
.tab-ui.on { color:#111827; font-weight:600; border-bottom-color:#111827; }
.tab-count { color:#9ca3af; font-weight:500; margin-left:.25rem; }

/* ── Sélecteur horizontal ────────────────────────────────────────────
   Motif des exécutions, mais générique : tout choix d'un élément dans
   une petite série s'y prête. */
.pill-select { display:flex; gap:.4rem; align-items:center; flex-wrap:wrap; }
.pill-opt {
  font-size:.72rem; padding:.3rem .6rem; border-radius:999px; cursor:pointer;
  border:1px solid #e5e7eb; background:#fff; color:#4b5563; font-family:inherit; white-space:nowrap;
}
.pill-opt:hover { border-color:var(--c-ghost-border); color:var(--c-ghost-hover-ink); }
/* Pastille CHOISIE = un état, pas une action : violet clair (famille des
   chips), plus le noir plein qui la faisait ressembler au bouton principal. */
.pill-opt.on { background:var(--c-accent-bg); border-color:var(--c-accent-border); color:var(--c-accent-text); font-weight:600; }
.pill-more { color:#9ca3af; font-size:.72rem; padding-left:.15rem; }
/* Pastille en <a> (barre de navigation entre écrans admin) plutôt qu'en
   <button> (choix d'état) : ne touche que a.pill-opt, jamais les usages
   existants en bouton. */
a.pill-opt { text-decoration:none; }

/* ── Colonne de menu entre écrans d'administration ────────────────────
   "Safe zone" de l'utilisateur : une colonne de menu VERTICALE qui a
   l'air de faire partie de la page — jamais de fond qui la
   délimiterait, elle flotte à gauche du contenu. Injectée par
   app.js::renderAdminSubnav() sur les 5 écrans regroupés derrière
   l'unique entrée "Paramètres" du rail. (Première version posée en
   barre horizontale en haut — retour utilisateur : "pour moi le rail
   est un rail vertical", corrigé ici, même mécanisme JS inchangé.) */
.admin-subnav-pills {
  flex-direction:column; align-items:flex-start; flex-wrap:nowrap;
  gap:.3rem; flex-shrink:0; padding-top:.15rem;
}
/* Icône + hauteur alignée sur --control-h : ces 5 entrées sont une vraie
   navigation (on y clique pour changer d'écran), pas un sélecteur d'état
   compact comme les autres .pill-opt (portée d'un prisme, choix de
   modèle...) — elles méritent le même gabarit qu'un CTA plutôt qu'une
   pastille plus discrète. Scopé à .admin-subnav-pills pour ne toucher
   aucun autre usage de .pill-opt ailleurs dans l'app. */
.admin-subnav-pills .pill-opt {
  height:var(--control-h); box-sizing:border-box; width:100%;
  padding:0 .85rem; font-size:.76rem;
  display:inline-flex; align-items:center; gap:.5rem;
}
.admin-subnav-pills .pill-opt svg { flex-shrink:0; }

/* Conteneur commun aux 5 écrans admin : la colonne de pastilles à
   gauche, le contenu (maître-détail ou page à section) à droite —
   STRICTEMENT le même squelette HTML sur les 5 pages (`#app-shell` puis
   `.admin-page-wrap` en enfants directs du `<body>`, plus aucune passe
   par `.app-page-content` sur settings/instructions) : c'est ce qui
   garantit une disposition identique plutôt que deux mises en page
   voisines mais différentes selon le contenu. (Correction : avant,
   domains/users/providers vivaient nus sous `.app-shell-layout` tandis
   que settings/instructions passaient par `.app-page-content` — deux
   chaînes de padding différentes, d'où une colonne de pastilles qui ne
   démarrait pas au même endroit d'un type d'écran à l'autre.) Le padding
   de page vit ICI, une seule fois, pour les deux types de contenu.
   `align-items:flex-start` : la colonne de pastilles n'a pas à s'étirer
   sur toute la hauteur, elle flotte en haut à gauche — le contenu, lui,
   gère son propre défilement (règle juste en dessous), jamais la ligne
   entière. */
.admin-page-wrap {
  display:flex; flex-direction:row; align-items:flex-start; gap:2rem;
  flex:1; min-width:0; height:100%; box-sizing:border-box; padding:1.5rem;
}
/* Sélecteurs de descendance, jamais une modification de .master-detail/
   .container elles-mêmes : ne s'appliquent que dans .admin-page-wrap,
   donc jamais sur sources/agents/prisms (hors périmètre, aucun ancêtre
   .admin-page-wrap) ni sur un usage générique de .container ailleurs
   dans l'app. flex:1 1 0% + min-height:0 plutôt que le height:100%
   hérité de la règle de base — évite de dépendre du calcul de
   flex-basis via une hauteur en % dans un contexte flex-row.
   .container gagne le même traitement que .master-detail porte déjà :
   c'est LUI qui défile désormais (`overflow-y:auto`), remplaçant le
   rôle que jouait `.app-page-content` avant cette correction — la
   colonne de pastilles, à côté, ne défile jamais avec lui. */
.admin-page-wrap .master-detail { flex:1 1 0%; min-height:0; }
.admin-page-wrap > .container { flex:1 1 0%; min-width:0; height:100%; overflow-y:auto; }

/* Largeur harmonisée avec settings.html/instructions.html — réservé aux
   3 écrans admin en maître-détail (domains/users/providers). Ne touche
   JAMAIS .master-pane elle-même (encore utilisée telle quelle par
   sources/agents/prisms, hors périmètre) : un modificateur, posé
   uniquement sur ces 3 pages. Le panneau de détail garde sa largeur/
   mécanique clamp() intacte — seul l'INTÉRIEUR de master-pane se
   recentre. */
.master-pane.admin-pane > * { max-width:900px; margin-left:auto; margin-right:auto; }

/* ── Bloc d'en-tête synthétique ─────────────────────────────────────
   Rend un run lisible AVANT d'entrer dans le rapport. */
.headline-card { background:#f8fafc; border:1px solid #eef0f3; border-radius:.5rem; padding:.8rem .9rem; }
.headline-title { font-size:.92rem; font-weight:650; line-height:1.35; letter-spacing:-.01em; color:#111827; }
.headline-sub { font-size:.78rem; color:#4b5563; margin-top:.25rem; }
.headline-meta { display:flex; gap:.9rem; margin-top:.6rem; font-size:.7rem; color:#6b7280; flex-wrap:wrap; }

/* ── Corps de rapport ────────────────────────────────────────────────
   Une seule définition pour trois usages : le panneau, la carte du
   tableau de bord et le fil de conversation, qui le réécrivaient
   chacun de leur côté. */
.md-body { font-size:.8rem; line-height:1.55; color:#374151; }
.md-body h1 { font-size:.95rem; font-weight:650; color:#111827; margin:0 0 .5rem; letter-spacing:-.01em; }
.md-body h2 { font-size:.82rem; font-weight:650; color:#111827; margin:1rem 0 .35rem; }
.md-body h3 { font-size:.8rem; font-weight:600; color:#111827; margin:.8rem 0 .3rem; }
.md-body p { margin:0 0 .5rem; }
.md-body ul, .md-body ol { margin:0 0 .6rem; padding-left:1.05rem; }
.md-body li { margin-bottom:.2rem; }
.md-body code { background:#f1f3f5; padding:.1rem .25rem; border-radius:.25rem; font-size:.92em; }
.md-body a { color:#4338ca; }

/* Formules KaTeX (voir app.js::renderMathIn) — pas scopé à .md-body, sert
   aussi .md-content (interroger.html, gabarit compact séparé). Une
   équation longue en mode bloc ($$...$$) peut dépasser la largeur d'un
   panneau ou d'une bulle de chat : elle défile plutôt que de casser la
   mise en page, comme un bloc de code déjà géré ailleurs. */
.katex-display { overflow-x:auto; overflow-y:hidden; padding:.2rem 0; }

/* ── Sources ramenées par une recherche web ──────────────────────────
   Le chaînon manquant : on voyait la requête et la durée, jamais CE QUI a
   été trouvé. Sans les liens, impossible de vérifier une affirmation du
   modèle — or c'est précisément l'enjeu d'un outil externe. */
.web-sources { display:flex; flex-direction:column; gap:.15rem; margin:.3rem 0 .5rem; }
.web-source {
  display:flex; align-items:baseline; gap:.45rem; text-decoration:none;
  font-size:.74rem; color:#374151; padding:.2rem .45rem; border-radius:.35rem;
  border:1px solid transparent; max-width:56rem;
}
.web-source:hover { background:#f5f3ff; border-color:#e0e7ff; color:#111827; }
.web-source-n {
  flex-shrink:0; width:1.05rem; height:1.05rem; border-radius:50%;
  background:#eef2f6; color:#475467; font-size:.62rem; font-weight:600;
  display:inline-flex; align-items:center; justify-content:center;
}
.web-source-title { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.web-source-host { flex-shrink:0; color:#9ca3af; font-size:.68rem; }

/* ── État vide ──────────────────────────────────────────────────────── */
.empty-ui { background:#fff; border:1px dashed #d1d5db; border-radius:.6rem; padding:2.5rem; text-align:center; color:#6b7280; }

/* ── Champs de formulaire ───────────────────────────────────────────── */
.field-label { font-size:.7rem; text-transform:uppercase; letter-spacing:.04em; color:#6b7280; font-weight:600; display:block; margin-bottom:.3rem; }
.field-ui {
  border:1px solid #d0d5dd; border-radius:.4rem; padding:.45rem .6rem; font-size:.8rem;
  color:#111827; background:#fff; width:100%; box-sizing:border-box; font-family:inherit;
}
.field-ui:focus { outline:2px solid #ddd6fe; outline-offset:-1px; border-color:#a78bfa; }
/* Hauteur harmonisée avec les boutons (--control-h) — mono-ligne
   seulement : un <textarea> (.field-grow, contenu d'une source...) doit
   rester libre de grandir avec son texte, jamais figé à cette hauteur.
   Le padding vertical devient nul, comme pour les boutons : c'est la
   hauteur fixe qui centre le texte, plus un calcul padding+ligne propre
   à chaque champ. */
input.field-ui, select.field-ui { height:var(--control-h); padding:0 .6rem; }
/* max-width : dans un panneau désormais large (jusqu'à 1200px), une ligne
   d'aide courait sur ~150 caractères — l'œil perd la ligne suivante. On
   borne la mesure, pas la largeur du panneau. */
.field-hint { font-size:.72rem; color:#9ca3af; margin-top:.25rem; line-height:1.5; max-width:72ch; }

/* Champ qui prend la hauteur restante du panneau — pour les zones de
   texte longues (consigne d'agent, contenu d'une source), où quelques
   lignes fixes obligeaient à défiler dans un champ étroit alors que le
   panneau, lui, avait de la place. Exception assumée au
   `.panel-body > * { flex-shrink:0 }` posé juste au-dessus : c'est le
   seul enfant autorisé à prendre le reste, et il garde un plancher pour
   que le corps défile plutôt que de l'écraser sur un panneau court. */
.panel-body > .field-grow { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
/* Plafond par défaut : sur un onglet qui porte AUSSI d'autres champs (la
   consigne d'un agent), sans lui la zone prenait 40% de la hauteur pour
   une seule ligne de texte et repoussait tout le formulaire en bas. */
.field-grow textarea.field-ui { flex:1; min-height:9rem; max-height:26rem; resize:none; }
/* Sans plafond là où le champ est SEUL sur son onglet (contenu d'une
   source : un tableur converti fait des centaines de lignes). */
.field-grow.field-tall textarea.field-ui { max-height:none; }

/* Bouton d'assistance IA posé DANS le champ, coin supérieur droit — pas
   au-dessus dans une ligne à côté du label, jugé "collé à la zone de
   texte" par l'utilisateur sur le premier jet. Le wrapper porte les
   bornes de hauteur (repris de field-grow ci-dessus) : le textarea, lui,
   n'est plus qu'un enfant en width/height:100%, le bouton flotte par
   dessus en position absolue. */
.field-with-assist { position:relative; }
.field-with-assist textarea.field-ui { width:100%; height:100%; }
.field-grow .field-with-assist { flex:1; min-height:9rem; max-height:26rem; }
.field-grow.field-tall .field-with-assist { max-height:none; }
/* Le groupe (Améliorer + Annuler, ce dernier caché tant qu'il n'y a rien à
   défaire) est positionné en bloc — les boutons eux-mêmes restent en flux
   normal l'un à côté de l'autre à l'intérieur. */
.field-with-assist .assist-btn-group { position:absolute; top:.5rem; right:.5rem; display:flex; gap:.35rem; }
.field-with-assist .assist-btn-group .assist-btn,
.field-with-assist .assist-btn-group .assist-undo-btn {
  box-shadow:0 1px 3px rgba(17,24,39,.1);
}

/* Petit bouton icône-seul dans le coin droit d'un champ MONO-LIGNE
   (contrairement à .field-with-assist, pensé pour un textarea) — motif
   demandé pour générer un mot de passe sans quitter le champ. Centré
   verticalement plutôt que calé en haut : sur un input à --control-h de
   haut, un calage "top" comme .assist-btn déborderait. */
.field-with-inline-btn { position:relative; }
.field-with-inline-btn input.field-ui { padding-right:2.1rem; }
.field-with-inline-btn .field-inline-btn {
  position:absolute; top:50%; right:.3rem; transform:translateY(-50%);
  width:1.6rem; height:1.6rem; padding:0; border:none; border-radius:.35rem;
  display:inline-flex; align-items:center; justify-content:center;
  background:transparent; color:#6b7280; cursor:pointer;
}
.field-with-inline-btn .field-inline-btn:hover { background:#f3f4f6; color:#111827; }

/* ── Toasts ──────────────────────────────────────────────────────────
   Pour tout ce qui s'annonce sans rien demander (« connexion OK »,
   « lancement refusé ») : ça n'a jamais mérité une boîte modale qui
   bloque la page en attendant un clic. */
.toast-host { position:fixed; right:1rem; bottom:1rem; z-index:1090; display:flex; flex-direction:column; gap:.5rem; align-items:flex-end; }
.toast-ui {
  background:#111827; color:#fff; font-size:.78rem; line-height:1.45;
  padding:.55rem .8rem; border-radius:.45rem; max-width:26rem;
  box-shadow:0 10px 25px rgba(16,24,40,.22);
  animation:toast-in .18s ease-out; transition:opacity .25s, transform .25s;
}
.toast-ui.error { background:#b42318; }
.toast-ui.leaving { opacity:0; transform:translateY(.4rem); }
@keyframes toast-in { from { opacity:0; transform:translateY(.5rem); } }

/* ── Journal d'exécution relu après coup ─────────────────────────────
   Les événements restent en base (agent_run_events) : les jeter à
   l'affichage une fois le run fini serait perdre une trace déjà payée,
   sur un système dont la traçabilité est un principe. */
.run-journal { border:1px solid #eef0f3; border-radius:.5rem; background:#fbfcfd; }
.run-journal > summary { cursor:pointer; font-size:.75rem; color:#4b5563; padding:.5rem .7rem; list-style:none; }
.run-journal > summary::-webkit-details-marker { display:none; }
.run-journal > summary::before { content:"▸ "; color:#9ca3af; }
.run-journal[open] > summary::before { content:"▾ "; }
.run-journal > summary:hover { color:#111827; }
.run-journal .journal-body { padding:.1rem .7rem .6rem; }

/* ── Garde anti-perte ────────────────────────────────────────────────
   La seule interruption modale qui survit — et elle ne se déclenche
   que si quelque chose est réellement en jeu. */
.guard-backdrop { position:fixed; inset:0; background:rgba(17,24,39,.28); display:flex; align-items:center; justify-content:center; z-index:1080; }
.guard-box { width:420px; max-width:calc(100vw - 2rem); background:#fff; border-radius:.7rem; box-shadow:0 20px 45px rgba(16,24,40,.22); padding:1.15rem 1.25rem; }
.guard-title { font-size:.95rem; font-weight:650; letter-spacing:-.01em; color:#111827; }
.guard-text { font-size:.82rem; color:#4b5563; line-height:1.55; margin-top:.4rem; }
.guard-actions { display:flex; gap:.5rem; justify-content:flex-end; margin-top:1rem; }

/* ── Apparence d'un domaine (icône + couleur) ────────────────────────
   Un domaine réapparaît partout : liste des domaines, colonne d'une
   source, périmètre d'un prisme, citation sous une réponse. Le repère
   sert à le reconnaître d'un coup d'œil, il reste donc DISCRET — la
   couleur porte l'identité, l'icône la nature, ni l'un ni l'autre ne
   doit concurrencer le nom. */
/* `inline-flex` sur le tag déplaçait la ligne de base : le nom du domaine
   remontait par rapport au reste de la ligne de méta (« · restreint · … »).
   Le tag reste donc en flux `inline` normal — c'est l'icône seule qui est
   calée verticalement. */
.domain-mark { display:inline-flex; align-items:center; flex-shrink:0; line-height:0; vertical-align:-.18em; }
.domain-tag { display:inline; min-width:0; }
.domain-tag .domain-mark { margin-right:.3rem; }

/* Sélecteur d'icône : un <option> ne peut pas héberger de markup, donc
   pas de <select> natif ici — un bouton qui montre l'icône courante et
   une grille dépliable. */
.icon-picker { position:relative; display:inline-block; }
.icon-picker-btn { display:inline-flex; align-items:center; gap:.4rem; height:var(--control-h);
  box-sizing:border-box; padding:0 .5rem; background:#fff; border:1px solid #d1d5db; border-radius:.45rem;
  color:#6b7280; cursor:pointer; }
.icon-picker-btn:hover { border-color:#9ca3af; }
.icon-picker-current { display:inline-flex; line-height:0; }
.icon-picker-grid { position:absolute; z-index:40; top:calc(100% + .3rem); left:0;
  display:grid; grid-template-columns:repeat(8, 2rem); gap:.15rem; padding:.35rem;
  background:#fff; border:1px solid #e5e7eb; border-radius:.55rem;
  box-shadow:0 12px 28px rgba(16,24,40,.16); }
.icon-picker-opt { display:flex; align-items:center; justify-content:center;
  width:2rem; height:2rem; background:none; border:1px solid transparent;
  border-radius:.35rem; color:#4b5563; cursor:pointer; }
.icon-picker-opt:hover { background:#f3f4f6; color:#111827; }
.icon-picker-opt.selected { border-color:#6d28d9; color:#6d28d9; background:#f5f3ff; }
/* Plusieurs domaines côte à côte (périmètre d'un prisme) : ils doivent
   pouvoir passer à la ligne sans casser la hauteur de la ligne de liste. */
.domain-tag-list { display:inline-flex; flex-wrap:wrap; align-items:center; gap:.15rem .6rem; }

/* Deux actions dans l'en-tête d'un écran qui porte deux types d'objets
   (utilisateurs + rôles) : le secondaire n'a plus à traîner sous la liste
   sous forme de bouton fantôme. Les écrans à UN type d'objet gardent une
   action unique — la portée s'y choisit dans le formulaire. */
.page-actions { display:flex; align-items:center; gap:.45rem; flex-shrink:0; }
