/* ── Centre : le fil. Seule zone de largeur de lecture limitée (--read), centrée dans une colonne pleine largeur. ── */
#center { position: relative; grid-column: 2; grid-row: 2; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; background: var(--surface); }
.scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--s5); }
.scroll:focus-visible { outline: none; box-shadow: inset 2px 0 0 var(--accent); } /* focus clavier du fil : un trait, pas un cadre autour de tout */
.thread { max-width: var(--read); margin: 0 auto; padding: var(--s2) 0 var(--s5); }
.compose-wrap { padding: 0 var(--s5) var(--s4); }
.compose-hint { max-width: var(--read); margin: 0 auto 6px; color: var(--ink-2); font-size: var(--t-meta); text-align: center; }
/* Une question ne se pose pas a cote du composeur : le composeur S'OUVRE
   dessus et se referme quand il n'y en a plus. Un seul objet, pas deux.
   La saisie reste vivante — c'est elle qui porte la reponse libre, donc
   aussi la dictee ; la carte n'a pas besoin de son propre champ. */
/* Un appel d'outil TERMINE : la meme ligne, en retrait. Pas de texte en
   plus — « Read » puis « Read terminé » faisaient deux lignes pour un
   seul appel, et vingt appels donnaient quarante lignes illisibles. */
.act.done > .act-sum { color: var(--ink-2); }
/* L'appel se lit DANS son bloc : une ligne par argument, la cle a gauche,
   la valeur telle qu'elle s'execute a droite. Le JSON brut donnait
   l'impression de plusieurs commandes pour un seul appel. */
/* Les jetons du tour : deux nombres discrets, cote a cote. Ils BOUGENT
   pendant que le bot travaille — c'est leur seul interet, donc ils ne
   changent pas de largeur a chaque mise a jour (chiffres tabulaires). */
.sub-tok { display: inline-flex; gap: 6px; font-variant-numeric: tabular-nums; }
.act-io { display: grid; grid-template-columns: auto 1fr; gap: 2px var(--s2); margin: 0; }
.act-io dt { color: var(--ink-2); font-size: var(--t-meta); }
.act-io dd { margin: 0; font-family: var(--mono, ui-monospace, monospace); font-size: var(--t-meta); overflow-wrap: anywhere; white-space: pre-wrap; }
/* La sortie ferme le bloc : un filet la separe de l'entree, pour qu'on
   voie d'un coup d'oeil ce qui est parti et ce qui est revenu. */
.act-io dt.out, .act-io dd.out { border-top: 1px solid var(--line); padding-top: 4px; margin-top: 2px; }
/* Un appel qui n'a PAS reussi se voit sur la ligne. Le mot est dans le
   libelle (act.st_*) — la couleur ne fait que doubler, elle ne porte
   jamais l'information seule. */
.act.done[data-status="error"] > .act-sum, .act.done[data-status="denied"] > .act-sum,
.act.done[data-status="blocked"] > .act-sum { color: var(--ink); }
.req-gate { max-width: var(--read); margin: 0 auto; }
/* La barre de saisie passe AVANT la carte : une demande, meme enorme, ne
   la pousse jamais hors de l'ecran. La carte se borne et defile en dedans
   — c'est la barre de defilement qui manquait. */
.req-gate .req { max-height: 42vh; overflow-y: auto; }
/* Oui / Non a DROITE : la souris arrive du bouton envoyer, en bas a
   droite du composeur. Les mettre a gauche fait traverser la fenetre a
   chaque validation, et il y en a souvent plusieurs d'affilee. */
.req-gate .q-answers { justify-content: flex-end; }
/* Image generee. Les actions vivent SUR l'image, la ou on la regarde ;
   elles se montrent au survol et restent atteignables au clavier (focus).
   Sur un ecran tactile il n'y a pas de survol : elles y sont permanentes. */
.image-result { margin: var(--s2) 0; max-width: 420px; }
/* La boite de l'image est un carre fixe a tous ses etats (en lecture, lue,
   en echec), legende comprise dans la carte : son arrivee ne change aucune
   hauteur, donc ne decale pas la lecture (gate P3, 2026-10-14). */
.img-wrap { position: relative; display: block; width: min(100%, var(--img-max)); aspect-ratio: 1; line-height: 0; }
.img-wrap img { width: 100%; height: 100%; object-fit: contain; border-radius: var(--r3); display: block; }
.img-wrap.img-empty { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: var(--s2); line-height: normal;
  border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); padding: var(--s4); text-align: center; }
.img-acts { position: absolute; right: 8px; bottom: 8px; display: flex; gap: 6px;
  opacity: 0; transition: opacity .12s ease; }
.img-wrap:hover .img-acts, .img-acts:focus-within { opacity: 1; }
@media (hover: none) { .img-acts { opacity: 1; } }
.img-act { display: grid; place-items: center; width: 30px; height: 30px; padding: 0;
  border: none; border-radius: var(--r2); background: rgba(0, 0, 0, .55); color: #fff; cursor: pointer; }
.img-act svg { width: 16px; height: 16px; }
.img-act.ok { background: rgba(22, 130, 70, .85); }
.img-act.ko { background: rgba(160, 30, 30, .85); }
/* Le prompt se replie tout seul : <details> est le pliage du navigateur. */
.img-foot { margin: 6px 0 0; display: flex; flex-direction: column; gap: 4px; }
.img-prompt summary { color: var(--ink-2); font-size: var(--t-meta); cursor: pointer; }
.img-prompt-text { margin: 4px 0 0; color: var(--ink-2); font-size: var(--t-meta); overflow-wrap: anywhere; }
.img-again { display: inline-flex; align-items: center; gap: 5px; align-self: flex-start; padding: 2px 8px;
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface);
  color: var(--ink-2); font-size: var(--t-meta); cursor: pointer; }
.img-again svg { width: 13px; height: 13px; }
.img-seed { color: var(--ink-2); font-size: var(--t-meta); font-variant-numeric: tabular-nums; }

.req-gate .req { margin-top: 0; border-bottom: none; border-radius: var(--r3) var(--r3) 0 0; }
.req-gate .q-other { display: none; }
.composer.asking { border-top-left-radius: 0; border-top-right-radius: 0; }
.composer { position: relative; max-width: var(--read); margin: 0 auto; display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s1); padding: 6px; border: 1px solid var(--line); border-radius: var(--r4); background: var(--surface); }
.composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
#input { flex: 1 1 140px; width: auto; min-width: 0; border: 0; outline: none; background: none; resize: none; color: var(--ink); font: var(--t-read)/1.45 var(--sans); padding: 5px 4px; height: 32px; max-height: 200px; overflow-y: auto; display: block; }
.cbody { position: relative; flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; }
.cbody > .listen { flex: 1 1 100%; }
#toks { display: contents; }
.plus-wrap { position: relative; flex: none; }
#plus-menu { bottom: calc(100% + 8px); left: 0; min-width: 230px; }
/* Fenetre des reglages du modele : un seul motif de ligne (nom a gauche,
   choix a droite), bornee en largeur et en hauteur — la liste de modeles
   peut etre longue, elle defile, la fenetre ne pousse pas la page. */
/* Defilement : PAS de rail de fond, juste le curseur. Le reste de la page
   dessine le sien (scroll.js) ; dans un volet qui se reconstruit a chaque
   rendu, un curseur dessine fuirait a chaque fois — alors on demande le
   curseur natif sans son fond. */
#run-menu, .run-models-in, .pop { scrollbar-width: thin; scrollbar-color: var(--switch-off) transparent; }
#run-menu::-webkit-scrollbar, .run-models-in::-webkit-scrollbar, .pop::-webkit-scrollbar { width: 6px; height: 6px; }
#run-menu::-webkit-scrollbar-track, .run-models-in::-webkit-scrollbar-track, .pop::-webkit-scrollbar-track { background: transparent; }
#run-menu::-webkit-scrollbar-thumb, .run-models-in::-webkit-scrollbar-thumb, .pop::-webkit-scrollbar-thumb { background: var(--switch-off); border-radius: 3px; }
#run-menu { bottom: calc(100% + 8px); left: 0; width: min(420px, calc(100vw - 32px)); max-height: min(60vh, 460px); overflow-y: auto; display: flex; flex-direction: column; gap: var(--s2); padding: var(--s3); }
/* UNE ligne par reglage : le nom a gauche, la commande collee a DROITE.
   Rien ne flotte au milieu. */
.run-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); min-height: 28px; width: 100%; }
.run-k { color: var(--ink-2); font-size: var(--t-meta); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.run-v { flex: none; margin-left: auto; color: var(--ink); font-size: var(--t-meta); font-weight: 600; max-width: 55%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.run-open { padding: 0 2px; border-radius: var(--r2); }
.run-open:hover { background: var(--hover); }
.run-chev { flex: none; width: 14px; height: 14px; color: var(--ink-2); transition: transform 180ms ease-out; }
.run-open[aria-expanded="true"] .run-chev { transform: rotate(90deg); }
/* Le repli est une rangee de grille qui passe de 0fr a 1fr : le MEME
   motif que la colonne de gauche, et le seul qui anime une hauteur
   inconnue d'avance sans la mesurer en JavaScript. */
.run-models { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 240ms cubic-bezier(.32, .72, 0, 1); }
.run-models.on { grid-template-rows: 1fr; }
.run-models-in { min-height: 0; max-height: 150px; overflow-y: auto; overflow-x: hidden; }
@media (prefers-reduced-motion: reduce) { .run-models { transition: none; } }
/* La liste de modeles est haute : son intitule se cale en HAUT, sinon il
   flotte au milieu d'une colonne qui defile et ne designe plus rien. */
.run-models .model-opt { min-width: 0; }
.run-models .model-opt .mo-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* L'effort : UNE piste a crans. La traine derriere le curseur dit « quatre
   sur cinq » d'un coup d'oeil. Le glissement est FONCTIONNEL — il montre
   de combien on vient de bouger — et c'est la seule raison pour laquelle
   il existe. */
/* Une PETITE fenetre veut de PETITS interrupteurs : la piste du theme fait
   48x28, celle d'ici 34x18. Le motif ne change pas, seules ses trois
   mesures, qui sont deja des variables. */
#run-menu .toggle { --track-w: 34px; --track-h: 18px; --knob: 14px; height: 18px; }
/* L'effort : UNE piste a crans, de la MEME hauteur que les interrupteurs
   d'a cote, sinon la ligne se voit de travers. La traine derriere le
   curseur dit « quatre sur cinq » d'un coup d'oeil. */
/* Le cran k est au MILIEU de sa case : (k + .5) / n. La traine s'arrete
   donc au CENTRE du curseur, elle ne le depasse pas de l'autre cote. */
.run-slide { position: relative; flex: none; width: 92px; display: grid; grid-template-columns: repeat(var(--n), 1fr); height: 18px; border-radius: 9px; background: var(--switch-off); overflow: hidden; touch-action: none; cursor: pointer; }
.run-trail { position: absolute; top: 0; bottom: 0; left: 0; width: calc((var(--i) + .5) / var(--n) * 100%); background: var(--accent-fill); transition: width 260ms cubic-bezier(.32, .72, 0, 1); }
.run-knob { position: absolute; top: 2px; width: 14px; height: 14px; border-radius: 50%; background: #ffffff; box-shadow: 0 1px 2px rgba(0, 0, 0, .35); left: calc((var(--i) + .5) / var(--n) * 100% - 7px); transition: left 260ms cubic-bezier(.32, .72, 0, 1); }
.run-step { position: relative; z-index: 1; height: 100%; display: flex; align-items: center; justify-content: center; }
/* Un point par cran d'arret : assez present pour qu'on voie ou le
   curseur peut se poser, assez efface pour ne pas faire du bruit. */
.run-dot { width: 3px; height: 3px; border-radius: 50%; background: #ffffff; opacity: .38; }
.run-step[aria-checked="true"] .run-dot { opacity: 0; } /* le curseur occupe deja ce cran */
.run-step:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 9px; }
@media (prefers-reduced-motion: reduce) { .run-trail, .run-knob { transition: none; } }
.run-note { margin: 0; color: var(--ink-2); font-size: var(--t-label); }
.cbtn:disabled { opacity: .4; }
.cbtn { width: var(--field); height: var(--field); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex: none; color: var(--ink-2); }
.cbtn:hover { background: var(--hover); color: var(--ink); }
.cbtn .icon { width: 22px; height: 22px; } /* la croix et le carre barre partagent la meme empreinte (14/24) et le meme cran de zoom */
#mic[aria-pressed="true"] { background: var(--accent-fill); color: var(--on-accent); }
#mic:disabled { opacity: .45; cursor: default; }
/* Micro seul par défaut ; dès qu'il y a du contenu, la flèche occupe la place de droite et le micro glisse d'un cran. */
.cacts { display: grid; grid-template-columns: var(--field) 0px; column-gap: 0; align-items: end; transition: grid-template-columns var(--move) ease-out, column-gap var(--move) ease-out; }
.composer.has-text .cacts { grid-template-columns: var(--field) var(--field); column-gap: var(--s1); }
.send-slot { display: flex; overflow: hidden; }
#send { opacity: 0; transition: opacity var(--move) ease-out; }
.composer.has-text #send { opacity: 1; }
/* Attachments, ported from the code harness's composer (Yumi Code's media rail): a row of 72px image tiles across the
   top of the box, numbered in order, then the files as a clip and a name. A touch screen shows each tile's remove
   button in its corner; a pointer veils the hovered tile and centres it there. Corners stay concentric with the box:
   its radius less its 6px padding, the rail's 2px and the tile's 1px border. */
.atts { flex: 1 1 100%; min-width: 0; --att-r: calc(var(--r4) - 9px); }
.att-rail { display: flex; gap: var(--s2); padding: 2px 2px var(--s1); overflow-x: auto; scrollbar-width: none; }
.att-thumb { position: relative; flex: none; border: 1px solid var(--line); border-radius: var(--att-r); background: var(--hover); overflow: hidden; }
.att-thumb img { display: block; width: 72px; height: 72px; object-fit: cover; }
.att-n, .att-rm { position: absolute; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, .6); color: #fff; transition: opacity var(--move) ease-out; }
.att-n { right: var(--s1); bottom: var(--s1); min-width: 20px; height: 20px; padding: 0 6px; box-sizing: border-box; border-radius: calc(var(--att-r) - 5px); font-size: var(--t-label); font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }
.att-rm { top: var(--s1); right: var(--s1); width: 26px; height: 26px; border-radius: 50%; }
.att-rm .icon { width: 14px; height: 14px; }
@media (hover: hover) {
  .att-thumb::before { content: ''; position: absolute; inset: 0; background: rgba(0, 0, 0, .4); opacity: 0; transition: opacity var(--move) ease-out; }
  .att-rm { top: 50%; left: 50%; right: auto; width: 28px; height: 28px; transform: translate(-50%, -50%); border-radius: calc(var(--att-r) - 5px); opacity: 0; }
  .att-thumb:hover::before, .att-thumb:focus-within::before, .att-thumb:hover .att-rm, .att-thumb:focus-within .att-rm { opacity: 1; }
  .att-thumb:hover .att-n, .att-thumb:focus-within .att-n { opacity: 0; }
  .att-file .mini { opacity: 0; transition: opacity var(--move) ease-out; }
  .att-file:hover .mini, .att-file:focus-within .mini { opacity: 1; }
}
.att-files { display: flex; flex-wrap: wrap; gap: 0 var(--s2); padding: 0 4px; }
.att-file { display: inline-flex; align-items: center; gap: 2px; min-width: 0; max-width: 24em; color: var(--ink-2); font-size: var(--t-read); }
.att-file:hover, .att-file:focus-within { color: var(--ink); }
.att-file > .icon { width: 14px; height: 14px; flex: none; }
.att-file .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listen { display: flex; align-items: center; gap: var(--s2); min-height: 32px; padding: 0 4px; color: var(--ink-2); font-size: var(--t-read); }
/* Le micro ne RETIRE plus la zone de texte : la faire disparaitre pour
   mettre un autre bloc a sa place faisait sauter tout le composeur a
   chaque clic. L'ecoute est un ETAT du bouton, pas un changement de
   structure — la bande d'ecoute s'ajoute sous la saisie, qui reste. */
.composer.listening .atts { display: none; }
.composer.listening #mic, .composer.mic-busy #mic { background: var(--ink); color: var(--surface); }
.composer.listening #mic svg, .composer.mic-busy #mic svg { animation: mic-pulse 1.4s ease-in-out infinite; }
@keyframes mic-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .composer.listening #mic svg, .composer.mic-busy #mic svg { animation: none; } }
.bfiles { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 4px; font-size: var(--t-meta); white-space: normal; }
.bfiles .icon { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
.bfiles .bthumb { width: 72px; height: 72px; object-fit: cover; border-radius: var(--r2); display: block; }
/* Enseigner une tâche : panneau inline au-dessus de la saisie, aperçu de ce que le bot en retient. */
.teach { max-width: var(--read); margin: 0 auto var(--s2); padding: var(--s3) var(--s4); border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); container-type: inline-size; }
.steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.step { display: grid; grid-template-columns: 18px minmax(0, 1fr) var(--mini); align-items: center; gap: var(--s2); }
.step .n { color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
.teach .link { margin-top: 6px; }
.recall { margin-top: var(--s3); padding: var(--s3); border-radius: var(--r2); background: var(--bg); font-size: var(--t-body); }
.recall h4 { margin: 0 0 6px; color: var(--ink-2); font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.recall-name { font-weight: 600; } .recall .dim { color: var(--ink-2); font-size: var(--t-meta); } .recall ol { margin: 6px 0; padding-left: 20px; }
.recall p { display: flex; gap: 6px; align-items: flex-start; margin: 6px 0 0; font-size: var(--t-meta); } .recall p .icon { width: 14px; height: 14px; flex: none; margin-top: 1px; }
.recall .sens-line { color: var(--warn); } .recall .safe-line { color: var(--ink-2); }
.teach .form-actions { justify-content: flex-end; }
#input::placeholder { color: var(--ink-2); }
#send { width: var(--field); height: var(--field); border-radius: 50%; background: var(--accent-fill); color: var(--on-accent); display: inline-flex; align-items: center; justify-content: center; flex: none; }
#send:disabled { background: var(--line); color: var(--ink-2); cursor: default; }
.m { display: grid; grid-template-columns: var(--mono-w) minmax(0, 1fr); gap: var(--s3); margin-top: var(--s4); }
/* Chat d'un seul bot : pas d'avatar, donc pas de colonne d'avatar. */
.m.seul { grid-template-columns: minmax(0, 1fr); }
.m.cont { margin-top: var(--s1); }
/* Message retrouvé par la recherche : surligné puis effacé en fondu (animation fonctionnelle). */
.m, .me { border-radius: var(--r2); transition: background-color 700ms ease-out, box-shadow 700ms ease-out; }
.m.flash, .me.flash { background-color: var(--mark); box-shadow: 0 0 0 6px var(--mark); transition-duration: 0s; }
.m .who { font-weight: 600; font-size: var(--t-body); line-height: 20px; }
.m .body { font-size: var(--t-read); line-height: 1.55; overflow-wrap: anywhere; min-width: 0; }
.m .body.typing { color: var(--ink-2); font-size: var(--t-body); }
/* Bulle d'un bot : EXACTEMENT la couleur de son avatar (--av, posee par paintColor), sans melange avec le
   fond (Nicolas, 2026-10-09). Son texte est BLANC, et bascule en NOIR quand la couleur est trop claire pour
   lui (clarte OKLCH > 0,72 : jaune, cyan, vert, orange, gris, une couleur libre claire), dans les deux themes.
   Le code y est a peine plus clair que la bulle (voile blanc de 8 %) et garde son encre : au pire 2,6:1,
   blanc sur marron (Nicolas, 2026-10-10 : « un tres leger contraste »). Une mention @Nom y prend le meme cadre. */
.m .body.tint { width: fit-content; max-width: 100%; padding: 8px 14px; border-radius: var(--r4); background: var(--av); color: oklch(from var(--av) clamp(0, (0.72 - l) * 1000, 1) 0 0); }
.m .body.tint a { color: inherit; text-decoration: underline; }
.m .body.tint :is(code, pre, .tok) { background: color-mix(in oklab, var(--av) 92%, #ffffff); border-color: transparent; color: inherit; }
.body p { margin: 0 0 6px; }
.body p:last-child { margin-bottom: 0; }
.body ul, .body ol { margin: 4px 0 6px; padding-left: 22px; }
.body li { margin: 2px 0; }
.body code { font: var(--t-code) var(--mono); background: var(--bg); border: 1px solid var(--line); border-radius: var(--r1); padding: 1px 5px; }
.body pre { margin: 6px 0; padding: 10px 12px; background: var(--bg); border: 1px solid var(--line); border-radius: var(--r2); white-space: pre-wrap; overflow-wrap: anywhere; } /* retour à la ligne : aucune zone qui défile en plus */
.body pre code { border: 0; padding: 0; background: none; line-height: 1.5; }
/* Bloc de code : un petit bouton copier discret en haut a droite (Nicolas, 2026-10-10), le texte ne passe pas dessous. */
.codeblock { position: relative; }
.codeblock > pre { padding-right: 34px; }
.codeblock > .copy { position: absolute; top: 7px; right: 7px; width: 22px; height: 22px; color: inherit; opacity: .6; }
.codeblock > .copy:hover, .codeblock > .copy:focus-visible { opacity: 1; }
.codeblock > .copy .icon { width: 14px; height: 14px; }
/* Le nom d'une routine dans un texte : un lien precede de son icone ; dans une bulle de bot, l'encre de la bulle. */
.ent { display: inline; color: var(--accent); text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }
.ent .icon { width: 1em; height: 1em; margin-right: 3px; vertical-align: -.15em; }
.m .body.tint .ent { color: inherit; }
.body a { color: var(--accent); text-underline-offset: 2px; }
.me { display: flex; justify-content: flex-end; align-items: center; gap: var(--s2); margin-top: var(--s4); }
/* Les reactions d'un message : une pastille en surimpression, a cheval sur le bas de sa bulle, au coin tourne vers
   le centre du fil. Le fil ne bouge pas quand on en pose une (Nicolas, 2026-10-10). Un clic en retire une. */
.bubble, .m .body.tint { position: relative; }
.reacts { --drop: translateY(55%); position: absolute; bottom: 0; z-index: 1; display: flex; gap: 1px; padding: 1px 3px; border-radius: 12px; background: var(--surface); box-shadow: 0 1px 3px rgb(0 0 0 / .2); transform: var(--drop); user-select: none; white-space: nowrap; }
/* Du cote de celui qui parle, comme chez WhatsApp : a gauche sous la bulle du Mi, a droite sous la tienne. */
.m .body.tint > .reacts { left: 10px; } .bubble > .reacts { right: 10px; }
/* Une reaction qui vient d arriver fait un petit zoom (demande, 2026-10-12). La pastille garde son decalage pendant l effet. */
.reacts.pop { animation: reacts-pop 300ms ease-out both; } .react.pop { animation: react-pop 300ms ease-out both; }
@keyframes reacts-pop { from { transform: var(--drop) scale(0); } 60% { transform: var(--drop) scale(1.2); } to { transform: var(--drop) scale(1); } }
@keyframes react-pop { from { transform: scale(0); } 60% { transform: scale(1.2); } to { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .reacts.pop, .react.pop { animation: none; } }
.react { height: 20px; min-width: 20px; padding: 0 2px; border-radius: 10px; font-size: 14px; line-height: 1; }
.react:hover, .react[aria-pressed="true"] { background: var(--hover); }
.react .n { margin-left: 2px; font-size: var(--t-meta); color: var(--ink-2); }
/* La citation d'une reponse, en encart au-dessus du texte : trois lignes au plus. */
.bubble .quote { margin: 2px 0 6px; padding-left: 8px; border-left: 2px solid currentColor; opacity: .7; white-space: pre-wrap; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.bubble { max-width: 80%; padding: 8px 14px; border-radius: var(--r4); background: var(--bubble); color: var(--on-bubble); font-size: var(--t-read); line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.bubble.wait { opacity: var(--bubble-wait); }
.bubble .mark { float: right; display: inline-flex; align-items: center; gap: 3px; margin: 6px 0 0 var(--s2); font-size: var(--t-label); line-height: 1; color: color-mix(in srgb, var(--on-bubble) 60%, transparent); white-space: nowrap; }
.bubble .mark .icon { width: 14px; height: 14px; }
/* L'heure en contenu genere : hors du texte du message (copier un fil, ordre lu par textContent). */
.bubble .mark::before { content: attr(data-t); }
.bubble .mark.failed { color: var(--err); }
/* ponytail: ratio carre suppose — seul format rendu par le fournisseur
   (1024x1024, mesure du 2026-09-26) ; s'il en rend un autre, la carte
   sautera d'un cran a l'echange. UNE seule borne --img-max : la hauteur
   reservee n'est JAMAIS un pourcentage (un % de height resolve contre la
   hauteur du bloc contenant, pas la largeur — boite fausse, D.3 off) ;
   le carre des images et de la carte vient d'aspect-ratio. */
.image-result { --img-max: 36rem; margin-top: var(--s4); }
.image-result a { display: inline-block; max-width: min(100%, var(--img-max)); line-height: 0; border-radius: var(--r3); overflow: hidden; box-shadow: 0 0 0 1px var(--line); }
.image-result img { display: block; max-width: 100%; max-height: var(--img-max); object-fit: contain; background: var(--bg); cursor: zoom-in; }
.image-result a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.image-error { color: var(--err); font-size: var(--t-body); }
/* IMAGE-EN-COURS : seul le serpent (plus bas, demande de Nicolas du 2026-10-16)
   dit que la carte travaille. Elle reserve la boite de l'image a venir (D.3) :
   UNE seule borne --img-max est servie a la fois a .image-result a/img et a
   .image-pending-box — la carte et l'image ont donc exactement la meme
   boite, l'arrivee ne fait pas sauter le fil. */
.image-pending-box { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: var(--s2); width: min(100%, var(--img-max)); aspect-ratio: 1; border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); color: var(--ink-2); padding: var(--s4); }
.image-pending-box .icon { width: 20px; height: 20px; }
.image-pending-label { font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.image-pending-prompt { font-size: var(--t-body); color: var(--ink); overflow-wrap: anywhere; text-align: center; max-height: 24rem; overflow: hidden; }
/* Le serpent : le throbber d'une carte en cours (image, video), repris du tableau des cles fournisseur
   (docs/rapports/tableau-cles-fournisseur/maquette.html). Un arc multicolore de 42 % du tour fait le tour de la
   carte, son halo flou le suit. Il anime SA propriete, --snake-a : la page anime deja --a comme un angle (les
   etincelles des avatars), la declarer en nombre les casserait. */
@property --snake-a { syntax: '<number>'; inherits: false; initial-value: 0; }
@keyframes snake { to { --snake-a: 1; } }
.image-pending { isolation: isolate; }
.image-pending-box { --p: .42; position: relative; border: 2px solid transparent;
  background: linear-gradient(var(--surface), var(--surface)) padding-box,
    conic-gradient(from calc(var(--snake-a) * 1turn - 90deg), var(--snake-ciel) 0turn, var(--snake-violet) calc(var(--p) * .4turn),
      var(--snake-rose) calc(var(--p) * .75turn), var(--snake-jaune) calc(var(--p) * 1turn), var(--line) 0) border-box;
  animation: snake 2.4s linear infinite; }
.image-pending-box::after { --p: .42; content: ''; position: absolute; inset: -10px; z-index: -1; border-radius: inherit; filter: blur(18px); opacity: .9;
  background: conic-gradient(from calc(var(--snake-a) * 1turn - 90deg), var(--snake-ciel), var(--snake-violet), var(--snake-rose), var(--snake-jaune), var(--snake-ciel));
  /* Extremites fondues : un masque tranche dessinerait des triangles. */
  mask: conic-gradient(from calc(var(--snake-a) * 1turn - 90deg), transparent 0, #000 calc(var(--p) * .25turn), #000 calc(var(--p) * .8turn), transparent calc(var(--p) * 1turn));
  animation: snake 2.4s linear infinite; }
@media (prefers-reduced-motion: reduce) { .image-pending-box, .image-pending-box::after { animation: none; } }
/* Video generee : la meme carte que l'image, en 16:9 (le cadre par defaut du
   service ; un clip vertical s'y loge entier). Ses actions passent en haut :
   en bas, elles couvriraient les commandes du lecteur. */
.video-result .img-wrap, .video-result .image-pending-box { aspect-ratio: 16 / 9; }
.img-wrap video { width: 100%; height: 100%; object-fit: contain; border-radius: var(--r3); display: block; background: var(--bg); }
.video-result .img-acts { top: 8px; bottom: auto; }
.ann { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: var(--s4); color: var(--ink-2); font-size: var(--t-meta); text-align: center; }
.ann .icon { width: 14px; height: 14px; }
.ann.warn .icon { color: var(--warn); }
.me-parked .ann.parked { justify-content: flex-end; margin-top: var(--s1); }
.me-parked .del-confirm { margin-top: var(--s2); margin-left: auto; max-width: 80%; }
.ann.tasks { display: block; }
.ann.tasks summary { display: flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer; list-style: none; }
.ann.tasks summary::-webkit-details-marker, .ann.tasks summary::marker { display: none; }
.ann.tasks ul { margin: var(--s2) auto 0; padding: 0; max-width: var(--read); list-style: none; text-align: left; }
.ann.tasks li { display: flex; gap: var(--s2); justify-content: space-between; padding: 2px 0; overflow-wrap: anywhere; }
.ann.tasks li .why { flex: none; color: var(--warn); }
.task-txt { text-align: left; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; white-space: pre-wrap; }
.task-txt[aria-expanded="true"] { -webkit-line-clamp: none; line-clamp: none; }
.ann.day { margin-top: var(--s5); font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.placeholder { color: var(--ink-2); padding-top: var(--s5); }
/* Carte de confirmation : un seul motif, dans le fil ET dans la colonne de droite (même fonction de rendu). */
.req { border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); padding: var(--s3) var(--s4); margin-top: var(--s2); }
.req-head { display: flex; align-items: center; gap: 6px; color: var(--ink-2); font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.req-head .icon { width: 14px; height: 14px; color: var(--warn); }
.req-head .av { margin-right: 2px; }
.req-text { margin: 6px 0 4px; font-size: var(--t-body); font-weight: 500; }
.req ul { margin: 0; padding-left: 18px; font-size: var(--t-body); }
.req li { margin: 2px 0; overflow-wrap: anywhere; }
.req .from { color: var(--ink-2); font-size: var(--t-meta); margin-left: 6px; }
.req-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }
.req-actions .note { margin-right: auto; font-size: var(--t-body); }
.req-done { display: flex; align-items: center; gap: 6px; margin-top: var(--s3); color: var(--ink-2); font-size: var(--t-body); }
.req-done.ok .icon, .ok-i { color: var(--ok); }
.req-done.no .icon, .no-i { color: var(--err); } .dim-i { color: var(--ink-2); }
.plan-steps { margin: 4px 0 0; padding-left: 20px; font-size: var(--t-body); } .plan-steps li { margin: 3px 0; }
.run-do { height: 24px; padding: 0 10px; font-size: var(--t-meta); }
.run-note.err { color: var(--err); }

/* AskUserQuestion : une question du modele. La mise en page passe a DEUX
   COLONNES — options a gauche, apercu a droite — des qu'une option porte
   un apercu, et seulement en selection simple : comparer deux apercus
   cote a cote n'a de sens que si l'on choisit UN element. */
.ask { display: flex; flex-direction: column; gap: var(--s2); }
.ask-chip { align-self: flex-start; padding: 2px 8px; border-radius: var(--r2); background: var(--mono-bg); color: var(--ink-2); font-size: var(--t-label); }
.ask-q { margin: 0; font-size: var(--t-body); font-weight: 600; }
.ask-d { margin: 0; color: var(--ink-2); font-size: var(--t-meta); }
.ask-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s3); min-width: 0; }
.ask-two .ask-body { grid-template-columns: minmax(0, 260px) minmax(0, 1fr); }
.ask-opts { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ask-opt { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface); text-align: left; min-width: 0; }
.ask-opt:hover { background: var(--hover); }
.ask-opt[aria-checked="true"] { border-color: var(--accent-fill); background: var(--current); }
.ask-lb { font-size: var(--t-body); font-weight: 500; }
.ask-ds { color: var(--ink-2); font-size: var(--t-meta); }
.ask-prev { min-width: 0; border: 1px solid var(--line); border-radius: var(--r2); background: var(--mono-bg); padding: var(--s2); overflow: auto; max-height: 40vh; }
.ask-prev-box { margin: 0; font: var(--t-meta)/1.45 var(--mono); white-space: pre-wrap; word-break: break-word; }
.ask-prev-none { margin: 0; color: var(--ink-2); font-size: var(--t-meta); }
/* Etroit : les deux colonnes se remettent l'une sous l'autre. */
@media (max-width: 820px) { .ask-two .ask-body { grid-template-columns: minmax(0, 1fr); } }
