/* ── Sidebar gauche : chaque rangée est la même grille (monogramme | texte) ; au repli la colonne texte passe à 0. ── */
#nav { position: relative; grid-column: 1; grid-row: 1 / -1; display: flex; flex-direction: column; min-width: 0; min-height: 0; border-right: 1px solid var(--line); }
.nav-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: var(--s2) 0; }
/* Les deux listes vivent cote a cote sur un rail : la bascule Bot / Code fait GLISSER le rail, elle ne
   remplace pas une liste par l'autre (demande de Nicolas, 2026-10-05, retiree le 08 avec la face maison,
   revenue le 2026-10-12). Chaque volet fait 50 % d'un rail large de 200 %, donc exactement la colonne.
   Yumi Code tient la PREMIERE moitie : au repos on regarde la seconde (les bots), et passer cote Code
   ramene le rail vers la droite, dans le meme sens que la pastille de la bascule. */
.nav-rail { display: grid; grid-template-columns: 50% 50%; width: 200%; transform: translateX(-50%); transition: transform var(--hop) var(--hop-ease); }
#app.face-code .nav-rail { transform: translateX(0); }
.nav-pane { min-width: 0; overflow: hidden; } /* une liste ne deborde jamais sur l'autre */
@media (prefers-reduced-motion: reduce) { .nav-rail { transition: none; } }
/* Volet Code : memes lignes que les bots (.ng .nav-item), memes en-tetes que les sections (.nav-sec). */
.nav-item.code-row .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nav-item.code-row.quiet .label { color: var(--ink-2); font-weight: 400; }
#pane-code .nav-sec .text > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.code-row.quiet .mono { background: none; }
.code-row.more .icon { transform: rotate(90deg); } .code-row.up .icon { transform: rotate(180deg); }
.code-dot { flex: none; align-self: center; width: 8px; height: 8px; border-radius: 50%; background: var(--ok); }
.code-dot.ask { background: var(--warn); }
.code-note { margin: 0; padding: var(--s2) var(--s3) var(--s2) var(--nav-pad); color: var(--ink-2); font-size: var(--t-meta); }
.code-note.err { color: var(--err); }
.code-acts { display: flex; gap: var(--s2); padding: var(--s2) var(--s3) var(--s3) var(--nav-pad); }
.code-end { margin-top: var(--s2); padding-top: var(--s2); border-top: 1px solid var(--line); }
#app.l-off .code-note, #app.l-off .code-acts { display: none; }
/* Les Mi de la barre laterale, +20 % (Nicolas, 2026-10-14). Les deux jetons sont redefinis
   sur la LIGNE (.ng), pas sur #nav : tout ce que la ligne contient suit — sa colonne de Mi,
   le repli, la pastille d'attente, sa hauteur (.nav-item est le meme element) et le Mi
   lui-meme (.av prend --mono-w en font-size) — et rien d'autre ne bouge.
   Pose sur #nav, l'override emportait AUSSI la bascule de produit (.prod-seg, deux segments
   en var(--mono-w)), qui n'est pas un Mi : repliee elle demandait 106 px dans un rail de 91,
   et TestCollapseKeepsIconsMeasured rougissait pour toutes les lanes de la page. Les memes
   jetons servent ailleurs la barre de recherche, les boutons-icones et des colonnes de
   tableaux : les monter dans tokens.css aurait gonfle la moitie de l'application. */
.ng { --mono-w: 44px; --row: 48px; display: grid; grid-template-columns: var(--mono-w) 1fr; gap: var(--s3); align-items: center; width: 100%; padding: 0 var(--s3) 0 var(--nav-pad); min-width: 0; transition: grid-template-columns var(--move) ease-out; }
#app.l-off .ng { grid-template-columns: var(--mono-w) 0fr; gap: 0; }
.ng > .text { min-width: 0; overflow: hidden; white-space: nowrap; display: flex; align-items: baseline; gap: var(--s2); }
.nav-item { position: relative; height: var(--row); text-align: left; }
#app.l-off .l1, #app.l-off .pv { visibility: hidden; }
#app.l-off .wa { position: absolute; left: calc(var(--nav-pad) + var(--mono-w) - 10px); top: 2px; z-index: 2; }
.nav-item:hover { background: var(--hover); }
.nav-item[aria-current="true"] { background: var(--current); }
.nav-item[aria-current="true"] .label { color: var(--accent); font-weight: 600; }
.nav-item .label { flex: none; font-size: var(--t-body); font-weight: 500; }
.nav-item.two { height: 52px; }
.nav-item.two > .text { flex-direction: column; align-items: stretch; justify-content: center; gap: 1px; }
.l1 { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }
.l1 .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.l1 .when { flex: none; color: var(--ink-2); font-size: var(--t-label); font-variant-numeric: tabular-nums; }
.l2 { display: flex; align-items: center; gap: 6px; min-width: 0; color: var(--ink-2); font-size: var(--t-meta); }
.l2 .pv { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Non lus, comme WhatsApp : pastille verte à droite de la 2e ligne (ronde pour 1 chiffre, pilule au-delà), heure en vert, nom et aperçu en gras. */
.wa { flex: none; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: var(--wa); color: var(--wa-ink); font-size: 12px; font-weight: 700; line-height: 20px; text-align: center; font-variant-numeric: tabular-nums; }
.wa.one { width: 20px; padding: 0; }
/* Pastille des demandes, dans #nav seulement. La classe wait est aussi celle
   de la bulle en attente : un selecteur nu lui imposait height 20px et
   color: var(--bg), le texte sortait de la pilule et se confondait avec la page. */
#nav .wait { flex: none; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: var(--warn-bg, var(--ink)); color: var(--warn-ink, var(--bg)); font-size: 12px; font-weight: 700; line-height: 20px; text-align: center; font-variant-numeric: tabular-nums; }
#nav .wait.one { width: 20px; padding: 0; }
.nav-item.unread .label, .nav-item.unread .pv { color: var(--ink); font-weight: 600; }
.nav-item.unread .when { color: var(--wa); font-weight: 600; }
.new-sep { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s4); color: var(--ink-2); font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.new-sep::before, .new-sep::after { content: ''; flex: 1; height: 1px; background: var(--line); }
/* Modèle : liste groupée par fournisseur, une ligne = nom + indication discrète (rapide, puissant, local ; coût relatif). */
.model-wrap { position: relative; }
.model-field { display: inline-flex; align-items: center; justify-content: space-between; gap: var(--s2); min-width: 240px; text-align: left; }
.model-field .icon { width: 14px; height: 14px; transform: rotate(90deg); color: var(--ink-2); }
.model-menu { top: calc(100% + 4px); left: 0; width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow-y: auto; }
#model-menu { top: auto; bottom: calc(100% + 6px); left: auto; right: 0; }
.model-opt { gap: 10px; } .model-opt .mo-name { font-weight: 500; } .model-opt .ck { margin-left: auto; }
.model-opt .ck { width: 16px; height: 16px; color: var(--accent); flex: none; }
.sub-model { position: relative; display: flex; align-items: center; min-width: 0; }
#new-btn.hl { background: var(--accent-fill); color: var(--on-accent); } /* aucun bot : le « + » est l'action à faire */
.l2.live { color: var(--accent); }
/* Omnibar (W.7b-6) : dans la barre du haut, largeur bornée (--omni-w), loupe DANS le champ. */
/* L'omnibar est centree sur l'ECRAN, pas sur la colonne du milieu : sa
   place ne depend donc d'AUCUNE colonne. Elle ne bouge plus quand on
   bascule de visage, quand on replie la colonne de gauche ou celle de
   droite (demande de Nicolas, 2026-10-05).
   It leaves the flow, so it can no longer push the tools of the bar; it keeps --omni-edge from the RIGHT
   edge, which is the settings column of the bar (--settings-w). It used to keep 220 px, and measured on
   2026-10-18 with a real build id the right tools take 237 px (245 on touch): the field covered the
   search button at 820 px.
   A gauche ce n'est pas vrai : ≡, « + » et l'avatar ne sont pas a 220 px du
   bord, ils suivent la colonne. Mesure du 2026-10-12, largeur gardee a 220 px
   de CHAQUE bord : fenetre de 1200 px, le champ recouvrait « + » et l'avatar ;
   a 1100 px, le menu ; et avec une colonne elargie, a toutes les tailles.
   Son bord gauche (--omni-x) est donc le plus a droite de trois : sa place
   centree, la marge du bord, la fin des outils de gauche (--l, le retrait de
   la barre --bar-in, leur largeur --bar-tools). Tant que rien ne gene, les
   deux premiers gagnent et elle ne bouge pas. */
.omni { --omni-x: max(calc(50vw - var(--omni-w) / 2), var(--omni-edge), calc(var(--l) + var(--bar-in) + var(--bar-tools) + var(--bar-tools-grow))); position: fixed; z-index: 15; top: calc((var(--bar) - var(--mono-w)) / 2); left: var(--omni-x); flex: none; width: min(var(--omni-w), calc(100vw - var(--omni-x) - var(--omni-edge))); max-width: var(--omni-w); min-width: 0; height: var(--mono-w); display: flex; align-items: center; }
.omni-box { flex: 1; min-width: 0; height: var(--mono-w); display: grid; grid-template-columns: var(--mono-w) minmax(0, 1fr); align-items: center; overflow: hidden; border-radius: var(--r2); background: var(--surface); box-shadow: var(--ring); }
.omni-box:focus-within { box-shadow: var(--ring-focus); }
#search-btn { width: var(--mono-w); height: var(--mono-w); display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r2); color: var(--ink-2); }
#search { width: 100%; min-width: 0; height: 100%; padding: 0 10px 0 0; border: 0; background: none; color: var(--ink); font: var(--t-body) var(--sans); }
#search:focus-visible { outline: none; }
#search::placeholder { color: var(--ink-2); }
/* Liste déroulante de l'omnibar : actions de création puis bots et salles ; pas de fond qui avale le clic. */
.omni-list { top: calc(100% + 4px); left: 0; min-width: 100%; }
.omni-list .pop-item { cursor: pointer; }
.omni-list .pop-item[aria-selected="true"] { background: var(--hover); font-weight: 400; }
.omni-list .icon { color: var(--ink-2); }
.omni-none { padding: var(--s2) 10px; color: var(--ink-2); font-size: var(--t-meta); }
.none { margin: 0; min-height: var(--row); }
.none .text { color: var(--ink-2); font-size: var(--t-meta); white-space: normal; }
/* Création inline : même grille que les entrées ; la salle ajoute ses membres sous le champ, un seul Valider.
   W.7b-5 : le champ reprend l'anneau de l'omnibar (box-shadow --ring-focus, pas outline).
   L'outline:focus-visible globale + overflow:hidden de .ng > .text rognaient l'anneau
   en points bleus aux coins (gauche et droite du champ). */
.new-row { height: var(--row); }
.new-row .text { align-items: center; gap: var(--s1); overflow: visible; }
.new-row .field { border: 0; height: var(--mono-w); appearance: none; -webkit-appearance: none; box-shadow: var(--ring); }
.new-row:focus-within .field { outline: none; box-shadow: var(--ring-focus); }
.new-room { padding: var(--s1) var(--s3) var(--s3) calc(var(--nav-pad) + var(--mono-w) + var(--s3)); }
.new-room legend { padding: 0; margin-bottom: var(--s1); color: var(--ink-2); font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.new-room fieldset { border: 0; margin: 0; padding: 0; }
.member { display: flex; align-items: center; gap: var(--s2); height: 28px; font-size: var(--t-body); cursor: pointer; }
.member input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent-fill); }
.new-room .btn { margin-top: var(--s2); }
/* Avatar de bot (ANALYSE §8) : forme + volume (3 dégradés : linéaire 140°, reflet haut-gauche, ombre bas-droite) + deux
   yeux. Forme et couleur DÉRIVÉES de l'identifiant. Yeux STATIQUES qui disent l'état : ouverts = en ligne, fermés = hors
   ligne, regard de côté = au travail. Aucune animation. Salle : icône de groupe. Humain : initiale. */
.av { --eye: #1d1d1f; position: relative; font-size: var(--mono-w); width: 1em; height: 1em; display: inline-flex; flex: none; }
/* Sans WebGL, un Mi n'est pas dessine (avatars.js, initiale) : un disque a sa couleur, l'initiale de son nom a l'encre de ses yeux. */
.av-initiale { display: grid; place-items: center; width: 100%; height: 100%; border-radius: 50%; background: var(--av); color: var(--eye); font-size: .5em; font-weight: 600; }
.av.sm { font-size: 20px; } .av.md { font-size: 28px; } .av.lg { font-size: 56px; } .av.xl { font-size: 72px; }
.av.photo img { width: 100%; height: 100%; display: block; object-fit: cover; border-radius: 50%; }
.av.photo[data-mask="rounded"] img { border-radius: 28%; } .av.photo[data-mask="square"] img { border-radius: 8%; }
/* Icône de groupe : avatars des membres, au plus 3, et « +N » en 4e case. */
.av.grp .cell { position: absolute; font-size: .5em; width: 1em; height: 1em; }
.g1 .c0 { font-size: 1em; left: 0; top: 0; }
.g2 .cell { font-size: .56em; top: 22%; } .g2 .c0 { left: 0; } .g2 .c1 { left: 44%; }
.g3 .c0 { left: 25%; top: 0; } .g3 .c1 { left: 0; top: 50%; } .g3 .c2 { left: 50%; top: 50%; }
.g4 .c0 { left: 0; top: 0; } .g4 .c1 { left: 50%; top: 0; } .g4 .c2 { left: 0; top: 50%; } .g4 .c3 { left: 50%; top: 50%; }
.av.grp .cell.more { width: 50%; height: 50%; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--mono-bg); color: var(--ink); font-size: .3em; font-weight: 600; font-variant-numeric: tabular-nums; }
/* États des yeux : repos = clignement rare et irrégulier ; hors ligne = fermés, immobiles ; au travail = animations
   d'état (demandées par Nicolas : elles disent ce que fait le bot). transform uniquement ; reduced-motion = pose fixe. */
.av-all, .av-eyes, .av-eye { transform-box: fill-box; transform-origin: center; }
/* Regard (--gx, --gy : position du regard, repère 40×40) et paupière (--bk) sont des variables ANIMÉES. Chaque œil prend
   la largeur d'un point sur une sphère, LA MÊME pour les deux yeux : (1 − ((centre de la paire − centre) / course)²)^0,75 — pleine au milieu, de plus en plus fine
   vers le bord, quel que soit ce qui le pousse (regard, pointeur, profil). --ex = x de l'œil, --cx/--rr = la forme.
   SONDES : le regard n'est PAS un transform animé de .av-eyes — lire --gx/--gy (getComputedStyle) ou la largeur réelle
   de .av-eye ; et le contour du corps est .av-line > *, celui d'un accessoire .acc-line > * (pas un [stroke] au hasard). */
@property --gx { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --gy { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --bk { syntax: '<number>'; inherits: false; initial-value: 1; }
.av-eyes { transform: translate(calc(var(--gx) * 1px), calc(var(--gy) * 1px)); }
.acc-face .acc-anim { translate: calc(var(--gx) * 1px) calc(var(--gy) * 1px); } /* moustaches : suivent le regard comme la bouche (translate indépendant de leur frétillement) */
.av-eye { transform: scale(pow(max(.01, 1 - pow((var(--pc, 20) + var(--gx) - var(--cx, 20)) / var(--rp, 8), 2)), .75), var(--bk)); } /* les DEUX yeux pareil, d'après la place de la paire ; ^.75 : quasi pleins au milieu, presque un trait au bord */
/* Trois états lisibles sur l'avatar seul : DORT (yeux fermés, respiration lente), AU TRAVAIL (yeux ouverts animés + effets
   autour), HORS LIGNE (gris, yeux fermés, immobile). Plus de pastille. */
.av { isolation: isolate; }
.av > svg, .av > img, .av > canvas.av3d { position: relative; z-index: 1; }
.av > canvas.av3d { width: 100%; height: 100%; display: block; } /* avatar 3D (avatar3d.js) */
.av[data-state="off"] .av-eye, .av[data-state="sleep"] .av-eye, .av[data-state]:not([data-state="off"]):not([data-state="sleep"]):not([data-state="on"]) .av-lid { display: none; } /* [data-state] : le conteneur d'un groupe n'a pas d'état */
.av[data-state="off"] > svg, .av.photo[data-state="off"] img { filter: grayscale(1) opacity(.5); }
.av[data-state="sleep"] .av-all { transform-origin: 50% 90%; animation: av-breathe calc(4s * var(--k, 1)) ease-in-out infinite; }
.av.photo[data-state="sleep"] img { animation: av-breathe calc(4s * var(--k, 1)) ease-in-out infinite; }
.av[data-state="sleep"] .av-lid { animation: av-fade .45s ease-out 1; }
.av[data-state]:not([data-state="off"]):not([data-state="sleep"]):not([data-state="on"]) .av-eye { animation: av-blink var(--bl, 5s) linear var(--bd, 0s) infinite, av-open .35s ease-out 1; } /* se réveille, puis cligne */
.av[data-state="long"] .av-eyes, .av[data-state="long"] .acc-face { --gy: 1.4; }
@keyframes av-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.03); } }
@keyframes av-open { from { --bk: .08; } to { --bk: 1; } }
@keyframes av-fade { from { opacity: 0; } to { opacity: 1; } }
/* Effets autour de l'avatar (ANALYSE §8, « Agent morphs ») : orbite (travaille), radar (cherche), anneau (tâche longue).
   L'orbite existe en deux couches identiques : derrière l'avatar (atténuée, moitié haute) et devant (moitié basse) = 3D. */
.fx { position: absolute; inset: -32%; pointer-events: none; display: none; }
.fx-back { z-index: 0; opacity: .5; clip-path: inset(0 0 50% 0); } .fx-front { z-index: 2; clip-path: inset(50% 0 0 0); }
.av[data-state="working"][data-fx="orbit"] .fx-back, .av[data-state="working"][data-fx="orbit"] .fx-front, .av[data-state="working"] .fx-work { display: block; }
.orb { position: absolute; left: 50%; top: 50%; width: 0; height: 0; transform: rotate(var(--tilt)); }
.ox { position: absolute; transform: translateX(calc(var(--r) * -1)); animation: av-ox calc(var(--t) / 2) ease-in-out infinite alternate; animation-delay: var(--d0, 0s); }
.oy { position: absolute; width: var(--sz); height: var(--sz); margin: calc(var(--sz) / -2); border-radius: 50%; opacity: var(--op);
  background: color-mix(in oklab, var(--av, var(--accent)) 55%, var(--ink)); animation: av-oy calc(var(--t) / 2) ease-in-out infinite alternate; animation-delay: calc(var(--d0, 0s) - var(--t) / 4); }
/* Orbite connectée : mêmes billes, reliées par des segments dessinés en direct (SVG mis à jour à chaque image). */
.av[data-state="working"][data-fx="link"] .fx-link { display: block; }
.fx-link.lk-back { z-index: 0; opacity: .55; } .fx-link.lk-front { z-index: 2; }
.fx-link circle { fill: color-mix(in oklab, var(--av, var(--accent)) 55%, var(--ink)); }
.fx-link line { stroke: color-mix(in oklab, var(--av, var(--accent)) 55%, var(--ink)); stroke-width: .018; stroke-linecap: round; }
@keyframes av-ox { from { transform: translateX(calc(var(--r) * -1)); } to { transform: translateX(var(--r)); } }
@keyframes av-oy { from { transform: translateY(calc(var(--ry) * -1)) scale(.6); } to { transform: translateY(var(--ry)) scale(1.25); } }
.fx.radar { inset: -20%; z-index: 0; border-radius: 50%; } .av[data-state="searching"] .radar, .av[data-state="working"][data-fx="radar"] .radar { display: block; }
.radar::before, .radar::after, .prog::before, .prog::after { content: ''; position: absolute; inset: 0; border-radius: 50%; }
.radar::before { box-shadow: inset 0 0 0 .025em color-mix(in oklab, var(--accent) 45%, transparent); }
.radar::after { background: conic-gradient(from 0deg, transparent 0turn .7turn, color-mix(in oklab, var(--accent) 55%, transparent) 1turn);
  -webkit-mask: radial-gradient(closest-side, transparent 66%, #000 68%); mask: radial-gradient(closest-side, transparent 66%, #000 68%); animation: av-spin 1.6s linear infinite; }
.fx.prog { inset: -14%; z-index: 2; } .av[data-state="long"] .prog, .av[data-state="working"][data-fx="prog"] .prog { display: block; }
.prog::before { box-shadow: inset 0 0 0 .05em var(--line); }
.prog::after { border: .05em solid transparent; border-top-color: var(--accent); border-right-color: var(--accent); animation: av-spin 1.3s linear infinite; }
.zz { position: absolute; right: -6%; top: -12%; z-index: 3; font: 600 .22em/1 var(--sans); color: var(--ink-2); opacity: 0; pointer-events: none; display: none; }
.zz::after { content: 'z'; }
.av.lg[data-state="sleep"] .zz, .av.xl[data-state="sleep"] .zz { display: block; animation: av-z 5.5s ease-out infinite; }
/* Galerie d'effets (éditeur d'icône) : halo, aura, étincelles, bulles, comète, pixels, ondes, constellation. Tailles en em :
   proportionnés à l'avatar, jamais plus de .72 em du centre (la moitié d'une ligne de liste). transform/opacity seulement. */
.fx-work { z-index: 2; } .fx-work.aura, .fx-work.const { z-index: 0; }
.fx-work > span, .halo::before, .aura::before, .comet::before { position: absolute; }
.halo::before { content: ''; inset: 15%; border-radius: 50%; box-shadow: 0 0 0 .035em color-mix(in oklab, var(--av) 70%, transparent), 0 0 .2em .05em color-mix(in oklab, var(--av) 50%, transparent); animation: av-halo 2.4s ease-in-out infinite; }
.aura::before { content: ''; inset: 8%; border-radius: 50%; background: radial-gradient(closest-side, color-mix(in oklab, var(--av) 60%, transparent) 55%, transparent); animation: av-aura 3s ease-in-out infinite; }
.spk { left: 50%; top: 50%; width: .045em; height: .13em; margin: -.065em -.0225em; border-radius: .03em; background: color-mix(in oklab, var(--av) 50%, var(--ink)); transform: rotate(var(--a)) translateY(-.6em); opacity: .9; animation: av-spark 1.6s ease-out infinite; animation-delay: var(--dl); }
.s0 { --a: 20deg; --dl: 0s; } .s1 { --a: 80deg; --dl: -.3s; } .s2 { --a: 140deg; --dl: -.9s; } .s3 { --a: 200deg; --dl: -.5s; } .s4 { --a: 260deg; --dl: -1.2s; } .s5 { --a: 320deg; --dl: -.7s; }
.bub { left: calc(50% + var(--x)); top: 50%; width: var(--s); height: var(--s); margin: calc(var(--s) / -2); border-radius: 50%; box-shadow: inset 0 0 0 .025em color-mix(in oklab, var(--av) 60%, var(--ink)); transform: translateY(.2em); opacity: .8; animation: av-bub var(--t) ease-in infinite; animation-delay: var(--dl); }
.b0 { --x: -.55em; --s: .12em; --t: 2.2s; --dl: 0s; } .b1 { --x: .58em; --s: .09em; --t: 2.8s; --dl: -.9s; } .b2 { --x: -.62em; --s: .07em; --t: 2.5s; --dl: -1.6s; } .b3 { --x: .5em; --s: .1em; --t: 3.1s; --dl: -2.3s; }
/* Comète : traînée et tête sur le MÊME rayon (--cr) autour du MÊME centre. La traînée est un anneau d'épaisseur --cw dont le
   milieu tombe exactement sur --cr ; la tête est centrée sur ce rayon. Tout tourne ensemble (un seul élément en rotation). */
.comet { --cr: 35%; --cw: .05em; animation: av-spin 1.8s linear infinite; }
.comet::before { content: ''; inset: calc(50% - var(--cr) - var(--cw) / 2); border-radius: 50%; background: conic-gradient(from 0deg, transparent 0turn .6turn, color-mix(in oklab, var(--av) 75%, transparent) 1turn);
  -webkit-mask: radial-gradient(closest-side, transparent calc(100% - var(--cw)), #000 calc(100% - var(--cw) + .5px), #000 calc(100% - .5px), transparent 100%);
  mask: radial-gradient(closest-side, transparent calc(100% - var(--cw)), #000 calc(100% - var(--cw) + .5px), #000 calc(100% - .5px), transparent 100%); }
.comet .head { left: 50%; top: calc(50% - var(--cr)); width: .12em; height: .12em; margin: -.06em; border-radius: 50%; background: color-mix(in oklab, var(--av) 45%, var(--ink)); }
.pxl { left: 50%; top: 50%; width: .09em; height: .09em; margin: -.045em; border-radius: .015em; background: color-mix(in oklab, var(--av) 55%, var(--ink)); transform: translate(var(--tx), var(--ty)); animation: av-pix 1.4s ease-in infinite alternate; animation-delay: var(--dl); } /* aller vers le centre, retour : ils rentrent DANS l'avatar puis repartent */
.av[data-state="working"][data-fx="pxlink"] .fx-link { display: block; }
.fx-link rect { fill: color-mix(in oklab, var(--av, var(--accent)) 55%, var(--ink)); }
.wv { inset: 20%; border-radius: 50%; box-shadow: inset 0 0 0 .03em color-mix(in oklab, var(--av) 65%, transparent); transform: scale(1.1); opacity: .6; animation: av-wave 2.4s ease-out infinite; animation-delay: var(--dl); }
.w0 { --dl: 0s; } .w1 { --dl: -.8s; } .w2 { --dl: -1.6s; }
/* Sonar : une impulsion part du centre, une seconde plus faible la suit, puis un silence avant le ping suivant. */
.snr { left: 50%; top: 50%; width: 1.2em; height: 1.2em; margin: -.6em; border-radius: 50%; box-shadow: inset 0 0 0 .035em color-mix(in oklab, var(--av) 60%, var(--ink)); transform: scale(.8); opacity: calc(var(--op) * .7); animation: av-sonar 2.8s ease-out infinite; animation-delay: var(--dl); }
.n0 { --op: .95; --dl: 0s; } .n1 { --op: .4; --dl: .32s; }
@keyframes av-sonar { 0% { transform: scale(.06); opacity: var(--op); } 42% { transform: scale(1.3); opacity: 0; } 100% { transform: scale(1.3); opacity: 0; } }
.const svg { width: 100%; height: 100%; animation: av-spin 14s linear infinite; }
.const polyline { fill: none; stroke: color-mix(in oklab, var(--av) 55%, var(--ink)); stroke-width: .018; stroke-linejoin: round; opacity: .7; }
.const circle { fill: color-mix(in oklab, var(--av) 45%, var(--ink)); }
@keyframes av-halo { 0%, 100% { transform: scale(.97); opacity: .55; } 50% { transform: scale(1.05); opacity: 1; } }
@keyframes av-aura { 0%, 100% { transform: scale(.94); opacity: .5; } 50% { transform: scale(1.04); opacity: 1; } }
@keyframes av-spark { 0% { transform: rotate(var(--a)) translateY(-.46em) scale(.4); opacity: 0; } 30% { opacity: 1; } 100% { transform: rotate(var(--a)) translateY(-.7em) scale(1); opacity: 0; } }
@keyframes av-bub { 0% { transform: translateY(.45em) scale(.5); opacity: 0; } 20% { opacity: .9; } 100% { transform: translateY(-.66em) scale(1); opacity: 0; } }
@keyframes av-pix { 0% { transform: translate(calc(var(--tx) * 1.15), calc(var(--ty) * 1.15)) rotate(90deg) scale(.8); opacity: 0; } 15% { opacity: 1; } 65% { opacity: .95; } 100% { transform: translate(0, 0) rotate(0deg) scale(.3); opacity: 0; } }
@keyframes av-wave { 0% { transform: scale(.9); opacity: .9; } 100% { transform: scale(1.45); opacity: 0; } }
/* Élément des particules (data-elem) : l'effet de travail garde son mouvement, ses particules prennent la forme de l'élément. */
.av[data-elem="fire"] { --el-fill: radial-gradient(circle at 50% 62%, #fff4a8 0 16%, #ffb21f 38%, #ff5a17 64%, #c91804 92%); --el-glow: 0 0 .05em .02em rgba(255, 108, 20, .75); --el-r: 50% 50% 46% 46% / 62% 62% 38% 38%; --el-solid: #ff6a1a; }
.av[data-elem="water"] { --el-fill: radial-gradient(circle at 34% 30%, #fff 0 12%, #a8dcff 30%, #3e93e6 78%); --el-glow: inset 0 0 0 .012em rgba(255, 255, 255, .7); --el-r: 50%; --el-solid: #4aa3ff; }
.av[data-elem="earth"] { --el-fill: radial-gradient(circle at 34% 30%, #c2a98a, #7c5c3e 58%, #4c3826); --el-glow: none; --el-r: 42% 58% 52% 48% / 56% 44% 56% 44%; --el-solid: #84654a; }
.av[data-elem] .oy, .av[data-elem] .spk, .av[data-elem] .bub, .av[data-elem] .comet .head, .av[data-elem] .pxl { background: var(--el-fill); box-shadow: var(--el-glow); border-radius: var(--el-r); }
.av[data-elem] .fx-link circle, .av[data-elem] .fx-link rect, .av[data-elem] .const circle { fill: var(--el-solid); }
.av[data-elem] .fx { --av: var(--el-solid); } /* les effets sans particules (halo, aura, ondes, sonar, traînée de la comète) prennent la couleur de l'élément */
.av[data-state="working"][data-fx="orbit"][data-aura] .fx-back, .av[data-state="working"][data-fx="orbit"][data-aura] .fx-front { display: none; } /* Mi en 3D : l'élément tourne en 3D à la place des billes */
.fx-grid { display: grid; grid-template-columns: repeat(4, 80px); gap: 4px; }
.fx-btn { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 12px 2px 6px; border-radius: var(--r2); font-size: var(--t-label); color: var(--ink-2); }
.fx-btn .av { font-size: 30px; }
.fx-btn:hover { background: var(--hover); }
.fx-btn[aria-pressed="true"] { background: var(--current); box-shadow: inset 0 0 0 2px var(--accent); color: var(--ink); }
@keyframes av-spin { to { transform: rotate(1turn); } }
@keyframes av-z { 0%, 62% { opacity: 0; transform: translate(0, 0); } 72% { opacity: .9; } 100% { opacity: 0; transform: translate(.1em, -.5em); } }
.av[data-state="thinking"] .av-eyes, .av[data-state="thinking"] .acc-face { --gy: -2.4; animation: av-think calc(2.6s * var(--k, 1)) ease-in-out infinite; }
.av[data-state="working"] .av-eyes, .av[data-state="working"] .acc-face { --gx: 2; animation: var(--lk, av-look) calc(1.7s * var(--k, 1)) ease-in-out infinite; }
.av[data-state="working"] > svg { transform-origin: 50% 70%; animation: var(--tl, av-tilt1) calc(3.2s * var(--k, 1)) ease-in-out infinite; } /* tête qui penche au hasard */
.av[data-state="working"] .av-all, .av.photo[data-state="working"] img { animation: av-bob calc(.85s * var(--k, 1)) ease-in-out infinite; }
.av[data-state="searching"] .av-eyes, .av[data-state="searching"] .acc-face { --gx: 2.6; animation: av-scan calc(1.9s * var(--k, 1)) linear infinite; }
.av[data-state="writing"] .av-eyes, .av[data-state="writing"] .acc-face { --gy: 2; }
.av[data-state="writing"] .av-all, .av.photo[data-state="writing"] img { animation: av-hop .5s ease-in-out infinite; }
.av[data-state="sending"] .av-all, .av.photo[data-state="sending"] img { animation: av-bounce .9s ease-out 1; }
.av[data-state="sending"] .av-eye + .av-eye { animation: av-wink .9s ease-out 1; }
@keyframes av-think { 0%, 100% { --gx: calc(-1.6 * var(--g, 1)); --gy: calc(-2.2 * var(--g, 1)); } 35% { --gx: calc(1.8 * var(--g, 1)); --gy: calc(-2.8 * var(--g, 1)); } 65% { --gx: calc(.4 * var(--g, 1)); --gy: calc(-1.6 * var(--g, 1)); } }
@keyframes av-look { 0%, 100% { --gx: calc(-2.2 * var(--g, 1)); --gy: calc(.3 * var(--g, 1)); } 45%, 55% { --gx: calc(2.2 * var(--g, 1)); --gy: calc(.3 * var(--g, 1)); } }
@keyframes av-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-1px); } }
@keyframes av-scan { 0% { --gx: calc(-2.8 * var(--g, 1)); --gy: 0; } 38%, 50% { --gx: calc(2.8 * var(--g, 1)); --gy: 0; } 88%, 100% { --gx: calc(-2.8 * var(--g, 1)); --gy: 0; } }
@keyframes av-hop { 0%, 100% { transform: translateY(0); } 35% { transform: translateY(-1.6px); } 70% { transform: translateY(0); } }
@keyframes av-bounce { 0% { transform: translateY(0); } 30% { transform: translateY(-3.2px); } 55% { transform: translateY(0); } 72% { transform: translateY(-1px); } 100% { transform: translateY(0); } }
@keyframes av-wink { 0%, 62% { --bk: 1; } 76% { --bk: .08; } 100% { --bk: 1; } }
[data-c="black"] { --av: var(--av-black); --eye: #ffffff; } [data-c="brown"] { --av: var(--av-brown); } [data-c="red"] { --av: var(--av-red); }
[data-c="orange"] { --av: var(--av-orange); } [data-c="yellow"] { --av: var(--av-yellow); } [data-c="green"] { --av: var(--av-green); }
[data-c="cyan"] { --av: var(--av-cyan); } [data-c="blue"] { --av: var(--av-blue); } [data-c="violet"] { --av: var(--av-violet); }
[data-c="magenta"] { --av: var(--av-magenta); } [data-c="gray"] { --av: var(--av-gray); }
/* Couleur libre : --avc = la couleur choisie ; sa variante sombre s'en dérive (color-mix en oklab) ;
   les yeux (--eye-l / --eye-d) sont choisis en JS pour garder un contraste d'au moins 4,5:1 dans chaque thème. */
[data-c="custom"] { --av: var(--avc); --eye: var(--eye-l); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) [data-c="custom"] { --av: color-mix(in oklab, var(--avc) 86%, #ffffff); --eye: var(--eye-d); } }
:root[data-theme="dark"] [data-c="custom"] { --av: color-mix(in oklab, var(--avc) 86%, #ffffff); --eye: var(--eye-d); }
.mono .icon { color: var(--ink-2); }
.mono { position: relative; width: var(--mono-w); height: var(--mono-w); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: var(--mono-bg); color: var(--ink); font: 600 var(--t-label)/1 var(--sans); letter-spacing: .02em; flex: none; }
.mono.room, .mono.tool { border-radius: var(--r2); }
.mono.draft { background: none; box-shadow: inset 0 0 0 1.5px var(--ink-2); color: var(--ink-2); }
.nav-foot { flex: none; border-top: 1px solid var(--line); padding-top: 6px; }
.account { position: relative; padding-bottom: 6px; }
#account-menu { bottom: calc(100% - 2px); left: var(--s3); min-width: 220px; }

/* Vie des yeux au repos (data-state on) : le regard erre (haut, bas, côté, en diagonale), clignement, et de temps en temps
   un sourire en deux demi-lunes (^^ : la paupière retournée). Motif, rythme et décalage par bot : lifeVars (avatars.js). */
.av[data-state="on"] .av-eyes, .av[data-state="on"] .acc-face { animation: var(--gz, av-gaze1) var(--gt, 9s) ease-in-out var(--gd, 0s) infinite; }
.av[data-state="on"] .av-eye { animation: av-blink var(--bl, 5s) linear var(--bd, 0s) infinite, av-joy var(--jy, 20s) linear var(--jd, 0s) infinite; }
.av[data-state="on"] .av-lid { opacity: 0; transform: scaleY(-1); transform-box: fill-box; transform-origin: center; animation: av-joy-lid var(--jy, 20s) linear var(--jd, 0s) infinite; }
@keyframes av-blink { 0%, 94% { --bk: 1; } 96% { --bk: .1; } 98%, 100% { --bk: 1; } }
@keyframes av-joy { 0%, 80% { opacity: 1; } 81%, 91% { opacity: 0; } 92%, 100% { opacity: 1; } }
@keyframes av-joy-lid { 0%, 80% { opacity: 0; } 81%, 91% { opacity: 1; } 92%, 100% { opacity: 0; } }
@keyframes av-gaze1 { 0%, 22% { --gx: 0; --gy: 0; } 26%, 40% { --gx: calc(2 * var(--g, 1)); --gy: calc(-2 * var(--g, 1)); } 44%, 62% { --gx: 0; --gy: 0; } 66%, 78% { --gx: calc(-2.4 * var(--g, 1)); --gy: 0; } 82%, 100% { --gx: 0; --gy: 0; } }
@keyframes av-gaze2 { 0%, 30% { --gx: 0; --gy: 0; } 34%, 46% { --gx: 0; --gy: calc(-2.4 * var(--g, 1)); } 50%, 58% { --gx: 0; --gy: 0; } 62%, 74% { --gx: 0; --gy: calc(2 * var(--g, 1)); } 78%, 100% { --gx: 0; --gy: 0; } }
@keyframes av-gaze3 { 0%, 18% { --gx: 0; --gy: 0; } 22%, 34% { --gx: calc(-2 * var(--g, 1)); --gy: calc(-1.8 * var(--g, 1)); } 38%, 52% { --gx: calc(2.4 * var(--g, 1)); --gy: 0; } 56%, 100% { --gx: 0; --gy: 0; } }
@keyframes av-gaze4 { 0%, 40% { --gx: 0; --gy: 0; } 44%, 56% { --gx: calc(1.6 * var(--g, 1)); --gy: calc(1.8 * var(--g, 1)); } 60%, 70% { --gx: calc(-1.6 * var(--g, 1)); --gy: calc(-2 * var(--g, 1)); } 74%, 100% { --gx: 0; --gy: 0; } }
@keyframes av-look2 { 0%, 100% { --gx: calc(-2 * var(--g, 1)); --gy: calc(-1.6 * var(--g, 1)); } 45%, 55% { --gx: calc(2 * var(--g, 1)); --gy: calc(1.2 * var(--g, 1)); } }
@keyframes av-look3 { 0%, 100% { --gx: calc(-1.6 * var(--g, 1)); --gy: calc(1.6 * var(--g, 1)); } 30% { --gx: 0; --gy: calc(1.8 * var(--g, 1)); } 60% { --gx: calc(1.6 * var(--g, 1)); --gy: calc(1.6 * var(--g, 1)); } }
@keyframes av-tilt1 { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-7deg); } 60% { transform: rotate(5deg); } }
@keyframes av-tilt2 { 0%, 100% { transform: rotate(3deg); } 50% { transform: rotate(-5deg); } }
@keyframes av-tilt3 { 0%, 40%, 100% { transform: rotate(0); } 20% { transform: rotate(8deg); } 70% { transform: rotate(-4deg); } }
.acc-anim { transform-box: fill-box; transform-origin: 50% 100%; }
.av[data-state="sending"] .acc-head .acc-anim { animation: acc-tip .9s ease-out 1; } /* il soulève son chapeau */
.av[data-state="working"] .acc-head .acc-anim { animation: acc-bob calc(.85s * var(--k, 1)) ease-in-out -.12s infinite; } /* le chapeau suit avec un temps de retard */
.av[data-state="writing"] .acc-neck .acc-anim { transform-origin: 50% 50%; animation: acc-wiggle .5s ease-in-out infinite; }
@keyframes acc-tip { 0%, 100% { transform: none; } 35%, 55% { transform: translate(0, -3px) rotate(-10deg); } }
@keyframes acc-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-.9px); } }
@keyframes acc-wiggle { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(7deg); } }
/* Vie propre des accessoires au repos, en plus du corps, au rythme du bot (lifeVars : --jy/--jd = son sourire,
   --gt/--gd = son regard) : le nœud penche de temps en temps, la cravate se balance,
   les lunettes grossissent et rebondissent au sourire, le chapeau se soulève, la moustache frétille. */
.av[data-state="on"] .acc-neck .acc-anim { transform-origin: 50% 50%; animation: acc-bow-live var(--jy, 20s) ease-in-out var(--jd, 0s) infinite; }
.av[data-state="on"] .av-acc[data-k="tie"] .acc-anim { transform-origin: 50% 0; animation: acc-tie-live var(--gt, 9s) ease-in-out var(--gd, 0s) infinite; }
.av[data-state="on"] .acc-eyes .acc-anim { transform-origin: 50% 50%; animation: acc-pop var(--jy, 20s) ease-in-out var(--jd, 0s) infinite; }
.av[data-state="on"] .acc-head .acc-anim { animation: acc-hat-live var(--gt, 9s) ease-in-out var(--gd, 0s) infinite; }
.av[data-state="on"] .acc-face .acc-anim { transform-origin: 50% 50%; animation: acc-stache var(--jy, 20s) ease-in-out var(--jd, 0s) infinite; }
@keyframes acc-bow-live { 0%, 20%, 34%, 100% { transform: rotate(0); } 24% { transform: rotate(-14deg); } 29% { transform: rotate(10deg); } } /* au repos : penche, ne tourne PAS (Nicolas, 02/10) */
/* Le tour de ventilateur, c'est au TRAVAIL seulement ; la cravate, elle, se balance plus vite. */
.av[data-state="working"] .acc-neck .acc-anim { transform-origin: 50% 50%; animation: acc-fan calc(7s * var(--k, 1)) ease-in-out infinite; } /* de temps en temps seulement */
.av[data-state="working"] .av-acc[data-k="tie"] .acc-anim { transform-origin: 50% 0; animation: acc-tie-live 1.1s ease-in-out infinite; }
@keyframes acc-fan { 0%, 82% { transform: rotate(0); } 96%, 100% { transform: rotate(720deg); } }
@keyframes acc-tie-live { 0%, 55%, 100% { transform: rotate(0); } 30% { transform: rotate(7deg); } 40% { transform: rotate(-5deg); } 50% { transform: rotate(2deg); } }
@keyframes acc-pop { 0%, 80%, 92%, 100% { transform: scale(1); } 83% { transform: scale(1.28); } 86% { transform: scale(.92); } 89% { transform: scale(1.08); } }
@keyframes acc-hat-live { 0%, 58%, 72%, 100% { transform: none; } 62% { transform: translateY(-1.6px) rotate(-7deg); } 67% { transform: translateY(0) rotate(3deg); } }
@keyframes acc-stache { 0%, 80%, 92%, 100% { transform: scaleX(1); } 83% { transform: scaleX(1.25); } 86% { transform: scaleX(.9); } 89% { transform: scaleX(1.1); } }
/* Mains (emplacement hands) : chaque main (.hl / .hr, dans le calque contour ET le calque couleur) bouge selon l'état ;
   trajets vers le menton (--h-chin, --h-mid) et la visière (--h-eye) calculés d'après la forme (accSVG). */
.acc-hands .hl, .acc-hands .hr { transform-box: fill-box; transform-origin: center; }
.av[data-state="on"] .acc-hands .hl { animation: h-idle 3.2s ease-in-out infinite; } .av[data-state="on"] .acc-hands .hr { animation: h-idle 3.2s ease-in-out -1.6s infinite; }
.av[data-state="sleep"] .acc-hands .hl, .av[data-state="sleep"] .acc-hands .hr { transform: translateY(2.5px); }
.av[data-state="thinking"] .acc-hands .hr { animation: h-chin 2.6s ease-in-out infinite; }
.av[data-state="searching"] .acc-hands .hr { animation: h-visor 1.9s ease-in-out infinite; }
.av[data-state="working"] .acc-hands .hl { animation: h-work .45s ease-in-out infinite; } .av[data-state="working"] .acc-hands .hr { animation: h-work .45s ease-in-out -.22s infinite; }
.av[data-state="writing"] .acc-hands .hl { animation: h-type .26s linear infinite; } .av[data-state="writing"] .acc-hands .hr { animation: h-type .26s linear -.13s infinite; }
.av[data-state="sending"] .acc-hands .hr { animation: h-wave 1s ease-in-out infinite; }
@keyframes h-idle { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-.8px); } }
@keyframes h-chin { 0%, 100% { transform: translate(var(--h-chin), var(--h-mid)); } 50% { transform: translate(var(--h-chin), calc(var(--h-mid) - 1px)); } }
@keyframes h-visor { 0%, 100% { transform: translate(calc(var(--h-chin) + 1px), var(--h-eye)); } 50% { transform: translate(calc(var(--h-chin) + 3px), var(--h-eye)); } }
@keyframes h-work { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.4px); } }
@keyframes h-type { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(1.6px); } }
@keyframes h-wave { 0%, 100% { transform: translate(1px, -9px) rotate(0); } 25% { transform: translate(2.5px, -10px) rotate(14deg); } 75% { transform: translate(-.5px, -10px) rotate(-14deg); } }
/* Pieds (emplacement feet) : avec des pieds (data-feet), écrire = MARCHER, chercher = COURIR (de profil : les yeux
   partent sur le côté, l'œil du fond s'efface presque), travailler = SAUTER façon Kirby (tassement, étirement, les pieds
   se décollent un peu, réception écrasée). Décision du 25/09 (planche capybara) : le saut est le travail actif. */
.acc-feet .fl, .acc-feet .fr { transform-box: fill-box; transform-origin: center; }
.av[data-feet][data-state="writing"] .av-all { animation: ft-bob .5s ease-in-out infinite; }
.av[data-feet][data-state="searching"] .av-all { transform-origin: 50% 90%; animation: ft-run-body .3s ease-in-out infinite; }
.av[data-feet][data-state="writing"] .acc-feet .fl, .av[data-feet][data-state="searching"] .acc-feet .fl { animation: ft-step var(--ft, 1s) linear infinite; }
.av[data-feet][data-state="writing"] .acc-feet .fr, .av[data-feet][data-state="searching"] .acc-feet .fr { animation: ft-step var(--ft, 1s) linear calc(var(--ft, 1s) / -2) infinite; }
.av[data-feet][data-state="writing"] { --ft: 1s; } .av[data-feet][data-state="searching"] { --ft: .6s; }
.av[data-feet][data-state="writing"] .av-all .av-eyes, .av[data-feet][data-state="searching"] .av-all .av-eyes, .av[data-feet][data-state="writing"] .av-all .acc-face, .av[data-feet][data-state="searching"] .av-all .acc-face { animation: none; --gx: var(--prof, 4); --gy: 0; } /* de profil : au bord (l'œil s'y amincit tout seul) */
.av[data-feet][data-state="writing"] .acc-hands .hl, .av[data-feet][data-state="searching"] .acc-hands .hl { animation: h-swing var(--ft, 1s) ease-in-out infinite; }
.av[data-feet][data-state="writing"] .acc-hands .hr, .av[data-feet][data-state="searching"] .acc-hands .hr { animation: h-swing var(--ft, 1s) ease-in-out calc(var(--ft, 1s) / -2) infinite; }
.av[data-feet][data-state="working"] > svg { animation: none; }
.av[data-feet][data-state="working"] .av-all { transform-origin: 50% 100%; animation: ft-jump .9s cubic-bezier(.3, 0, .5, 1) infinite; }
.av[data-feet][data-state="working"] .acc-feet .fl, .av[data-feet][data-state="working"] .acc-feet .fr { animation: ft-dangle .9s ease-in-out infinite; }
@keyframes ft-bob { 0%, 50%, 100% { transform: translateY(0); } 25%, 75% { transform: translateY(-1px); } }
@keyframes ft-run-body { 0%, 100% { transform: rotate(7deg) translateY(0); } 50% { transform: rotate(7deg) translateY(-1.4px); } }
/* Foulée : le pied posé glisse loin vers l'arrière AU SOL (0 → 55 %), puis revient loin vers l'avant en se levant à peine.
   --fo rapproche les deux pieds sous le corps (vue de profil). */
.acc-feet .fl { --fo: 3.5px; } .acc-feet .fr { --fo: -3.5px; }
@keyframes ft-step { 0%, 100% { transform: translate(calc(var(--fo, 0px) + 4.5px), 0); } 55% { transform: translate(calc(var(--fo, 0px) - 4.5px), 0); } 78% { transform: translate(var(--fo, 0px), -1.3px); } }
@keyframes h-swing { 0%, 100% { transform: translateX(1.4px); } 50% { transform: translateX(-1.4px); } }
@keyframes ft-jump { 0% { transform: scale(1.14, .86); } 18% { transform: scale(.9, 1.12) translateY(-3px); } 45% { transform: scale(1) translateY(-8px); } 72% { transform: scale(.96, 1.05) translateY(-1px); } 82% { transform: scale(1.14, .86) translateY(0); } 100% { transform: scale(1.14, .86); } }
@keyframes ft-dangle { 0%, 18%, 82%, 100% { transform: translateY(0); } 45% { transform: translateY(1.6px); } }
/* Regard plus ample (--g, d'après la forme) et deux parcours de plus : les yeux qui roulent en arc, le double regard. */
@keyframes av-gaze5 { 0%, 30%, 100% { --gx: 0; --gy: 0; } 36% { --gx: calc(-2.4 * var(--g, 1)); --gy: calc(-1.4 * var(--g, 1)); } 42% { --gx: 0; --gy: calc(-2.4 * var(--g, 1)); } 48% { --gx: calc(2.4 * var(--g, 1)); --gy: calc(-1.4 * var(--g, 1)); } 56% { --gx: 0; --gy: 0; } }
@keyframes av-gaze6 { 0%, 50%, 100% { --gx: 0; --gy: 0; } 53%, 58% { --gx: calc(2.4 * var(--g, 1)); --gy: 0; } 61%, 66% { --gx: calc(-2.4 * var(--g, 1)); --gy: 0; } 70% { --gx: 0; --gy: 0; } }
/* Spin : le visage (yeux, traits, lunettes, moustache) part en haut à gauche en s'amincissant, passe DERRIÈRE (masqué),
   revient par l'autre bord et se recentre — une boule qui tourne sur elle-même. --sr = rayon d'après la forme.
   Au repos : de temps en temps (--sp, --sd tirés par bot) ; au travail sans pieds : plus souvent. */
.av-face { transform-box: view-box; transform-origin: 50% 50%; }
.av[data-state] .av-face.still { animation: none; } /* forme à oreilles / antennes : ne tourne pas */
.av[data-state="on"] .av-face { animation: av-facespin var(--sp, 30s) ease-in-out var(--sd, 0s) infinite; }
.av[data-state="working"]:not([data-feet]) .av-face { animation: av-facespin calc(7s * var(--k, 1)) ease-in-out infinite; }
@keyframes av-facespin { /* visage : sortie vers -(sx, sy)·sr en aplatissant l'axe (qx, qy), masqué derrière, retour par +(sx, sy)·sr */
  0%, 86%, 100% { transform: none; opacity: 1; }
  89% { transform: translate(calc(var(--sr, 12px) * var(--sx, 1) * -.72), calc(var(--sr, 12px) * var(--sy, .42) * -.72)) scale(calc(1 - var(--qx, 1) * .38), calc(1 - var(--qy, 0) * .38)); opacity: 1; }
  90.8% { transform: translate(calc(var(--sr, 12px) * var(--sx, 1) * -1), calc(var(--sr, 12px) * var(--sy, .42) * -1)) scale(calc(1 - var(--qx, 1) * .92), calc(1 - var(--qy, 0) * .92)); opacity: 1; }
  90.9% { transform: translate(calc(var(--sr, 12px) * var(--sx, 1) * -1), calc(var(--sr, 12px) * var(--sy, .42) * -1)) scale(calc(1 - var(--qx, 1) * .92), calc(1 - var(--qy, 0) * .92)); opacity: 0; }
  93.4% { transform: translate(calc(var(--sr, 12px) * var(--sx, 1)), calc(var(--sr, 12px) * var(--sy, .42))) scale(calc(1 - var(--qx, 1) * .92), calc(1 - var(--qy, 0) * .92)); opacity: 0; }
  93.5% { transform: translate(calc(var(--sr, 12px) * var(--sx, 1)), calc(var(--sr, 12px) * var(--sy, .42))) scale(calc(1 - var(--qx, 1) * .92), calc(1 - var(--qy, 0) * .92)); opacity: 1; }
  95.5% { transform: translate(calc(var(--sr, 12px) * var(--sx, 1) * .72), calc(var(--sr, 12px) * var(--sy, .42) * .72)) scale(calc(1 - var(--qx, 1) * .38), calc(1 - var(--qy, 0) * .38)); opacity: 1; }
}
/* Les yeux suivent le pointeur (data-look, --mx/--my posés par avatars.js) : remplace le parcours de regard, suit en douceur. */
.av[data-look][data-state] .av-all .av-eyes, .av[data-look][data-state] .av-all .acc-face { animation: none; --gx: var(--mx, 0); --gy: var(--my, 0); transition: --gx .12s ease-out, --gy .12s ease-out; }
.av[data-look][data-state] .av-face { animation: none; } /* yeux accrochés au pointeur : pas de spin (Nicolas, 03/10) */
.av[data-skin="mochi"] { --edge: var(--av); --lw: 2; }
/* Onglet qu'on tient (wireTear) : un fantome de la ligne suit la souris dans la colonne ; des qu'il en sort, la ligne
   decroche et c'est sa fenetre qui suit (desktop/main.js). */
.tear-ghost { position: fixed; left: 0; top: 0; z-index: 50; pointer-events: none; background: var(--surface); border-radius: var(--r2); box-shadow: var(--shadow); opacity: .92; }
#app.tearing { cursor: grabbing; user-select: none; -webkit-user-select: none; }
.nav-item.dragging { opacity: .35; } /* la place que prendra le Mi tenu ; les autres s'ecartent (glide) */
/* Une ligne qui part (glide) : sa copie se replie sur place pendant que les autres comblent. */
#list, #code-tree { position: relative; }
.row-gone { position: absolute; left: 0; right: 0; pointer-events: none; overflow: hidden; transform-origin: top; transition: transform 220ms ease-out, opacity 150ms ease-out; }
.row-gone.out { transform: scaleY(0); opacity: 0; }
/* Un onglet qui rentre glisse depuis la droite jusqu'a sa place ; les autres lui font de la place (glide). */
.landing { animation: land 260ms ease-out; }
@keyframes land { from { opacity: 0; transform: translateX(32px); } }
@media (prefers-reduced-motion: reduce) { .landing { animation: none; } }
