:root {
  --txt: #e9eaf3;
  --muted: #7b7f97;
  --dim: #565b73;
  --violet: var(--tinta-media);
  --violet-soft: #b8a4ff;
  --cyan: #45d6ff;
  --green: #3fe0a0;
  --no: #ff6b7a;
  --line: rgba(150,130,255,.10);
  --panel: rgba(255,255,255,.022);
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }

body {
  font-family: "Inter", "SF Pro Display", system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--txt);
  background:
    radial-gradient(1100px 720px at 50% 46%, rgba(46,30,96,.40), rgba(5,5,12,0) 60%),
    radial-gradient(800px 600px at 50% 120%, rgba(70,40,150,.18), rgba(5,5,12,0) 60%),
    #06060e;
  overflow: hidden;
}
.stars { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; }

.app {
  position: relative; z-index: 1;
  height: 100vh;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 64px 1fr;
  grid-template-areas: "top" "stage";
}

/* Diamantes (facetas base) */
   

/* ===== Header ===== */
.top { grid-area: top; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; }

/* ===== Painéis laterais ===== */

/* ===== Palco ===== */
.stage { grid-area: stage; position: relative; }
.links { position: absolute; inset: 0; width: 100%; height: 100%; }

/* traços de ambiente (AI factory): curvas neurais sutis ao fundo */

/* Raios de conexão hub→agente: visíveis, mostrando a rede */
.link {
  fill: none; stroke: rgba(160,140,255,.42); stroke-width: 2.4;
  stroke-linecap: round; vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 3px rgba(var(--tinta-media-rgb),.45));
  transition: stroke .3s, stroke-width .3s, filter .3s;
}
.link.faint { stroke: rgba(160,140,255,.26); stroke-width: 1.8; }
/* Delegação ativa: o RAIO de luz da linha se conecta e VIBRA (sem pacotes) */
.link.ativo {
  stroke: var(--tinta-clara); stroke-width: 5; stroke-dasharray: none;
  animation: vibrar .3s ease-in-out infinite;
}
@keyframes vibrar {
  0%, 100% {
    stroke-width: 4;
    filter: drop-shadow(0 0 5px var(--tinta-media)) drop-shadow(0 0 14px rgba(var(--tinta-media-rgb),.85)) drop-shadow(0 0 26px rgba(var(--tinta-media-rgb),.5));
  }
  50% {
    stroke-width: 6.5;
    filter: drop-shadow(0 0 10px var(--tinta-clara)) drop-shadow(0 0 24px rgba(var(--tinta-media-rgb),1)) drop-shadow(0 0 44px rgba(var(--tinta-media-rgb),.85));
  }
}

/* ===== Semantix AI Layer (faixa de governança) ===== */

/* Núcleo glossy */
.orb-core {
  position: absolute; left: 50%; top: 48%; transform: translate(-50%, -50%);
  width: 300px; height: 300px; border-radius: 50%;
  background:
    radial-gradient(circle at 42% 30%, rgba(170,132,255,.30), rgba(78,44,150,.16) 38%, rgba(24,16,50,.22) 66%, rgba(8,6,20,.10) 100%);
  box-shadow: 0 0 90px rgba(122,80,220,.30), inset 0 0 70px rgba(120,80,210,.12), inset 12px 14px 60px rgba(160,130,255,.10);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: float 7s ease-in-out infinite;
}
@keyframes float { 0%,100% { margin-top: 0; } 50% { margin-top: -8px; } }

/* Nós de agentes */
.node { position: absolute; transform: translate(-50%, -50%); width: 150px; text-align: center; }
.node-orb {
  position: relative; width: 60px; height: 60px; border-radius: 50%; margin: 0 auto 10px;
  background: radial-gradient(circle at 40% 30%, rgba(170,140,255,.32), rgba(72,44,140,.16) 42%, rgba(20,14,44,.18) 70%, rgba(8,6,20,.06) 100%);
  box-shadow: 0 0 22px rgba(122,80,220,.32), inset 0 0 18px rgba(var(--tinta-media-rgb),.10);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.node-orb svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 28px; height: 28px; }
  
/* CRO — Clientes/Receita (azul) */
  
/* COO — Operações (âmbar) */
  
/* Research — Mercado (ciano/teal) */
  
/* CPO — Produto (rosa/magenta) */
  
/* Anel externo (enablers): menor e mais sutil */
.node.outer { width: 130px; opacity: .82; }
.node.outer .node-orb { width: 50px; height: 50px; }
.node.outer .node-orb svg { width: 23px; height: 23px; }
.badge { position: absolute; top: -3px; right: -3px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 10px; font-size: 10px; font-weight: 700; display: grid; place-items: center; background: #16122e; color: var(--violet-soft); box-shadow: inset 0 0 0 1px rgba(var(--tinta-media-rgb),.4); }
.node-name { font-size: 13px; font-weight: 600; letter-spacing: 2px; }
.node-sub { font-size: 11px; color: var(--muted); margin: 3px 0 8px; }
.node { transition: opacity .35s; }
.node.ativo .node-orb {
  animation: pulseNode .9s ease-in-out infinite !important;
  box-shadow: 0 0 30px rgba(var(--tinta-media-rgb),.95), 0 0 60px rgba(var(--tinta-media-rgb),.6), inset 0 0 22px rgba(var(--tinta-media-rgb),.25) !important;
}
.node.ativo .node-name { color: var(--tinta-media); text-shadow: 0 0 14px rgba(var(--tinta-media-rgb),.5); }
@keyframes pulseNode { 0%,100% { transform: scale(1); } 50% { transform: scale(1.08); } }

/* Pílulas */
.pill { border: 1px solid rgba(var(--tinta-media-rgb),.35); background: rgba(var(--tinta-media-rgb),.06); color: var(--violet-soft); border-radius: 999px; padding: 5px 14px; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; cursor: pointer; transition: all .15s; }
.pill:hover { background: rgba(var(--tinta-media-rgb),.16); color: #fff; }

.pill.ok { border-color: var(--green); color: var(--green); }
.pill.no { border-color: var(--no); color: var(--no); }

/* Dock inferior (mic) */
.dock { position: absolute; left: 50%; bottom: 2%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 8px; }
.narracao { min-height: 18px; font-size: 13px; color: var(--cyan); text-shadow: 0 0 12px rgba(69,214,255,.4); }
.estado-label { text-transform: uppercase; letter-spacing: 5px; font-size: 10px; color: var(--muted); }
.mic-orb { width: 58px; height: 58px; border-radius: 50%; cursor: pointer; border: 1px solid rgba(var(--tinta-media-rgb),.4); color: var(--violet-soft); background: radial-gradient(circle at 38% 32%, rgba(var(--tinta-media-rgb),.3), rgba(10,8,24,.7)); box-shadow: 0 0 26px rgba(var(--tinta-media-rgb),.35), inset 0 0 14px rgba(var(--tinta-media-rgb),.2); display: grid; place-items: center; transition: all .15s; }
.mic-orb svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.mic-orb:hover { transform: scale(1.06); color: #fff; }
.mic-orb.gravando { background: radial-gradient(circle at 38% 32%, rgba(255,107,122,.5), rgba(40,8,16,.7)); border-color: var(--no); color: #fff; animation: pulseNode .9s ease-in-out infinite; }

.gate-mini { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 999px; border: 1px solid rgba(var(--tinta-media-rgb),.4); background: rgba(16,14,30,.8); margin-top: 4px; }
.gate-mini.oculto { display: none; }
.gate-msg { font-size: 12px; color: var(--violet-soft); }

/* ===== Footer ===== */

 

/* Aqui vivia um resto de um layout de três colunas que não existe mais:

     @media (max-width: 1024px) {
       .app { grid-template-columns: 0 1fr 0; }
       .left, .right { display: none; }
     }

   `.left` e `.right` não existem no `index.html` — a segunda regra não fazia
   nada. A primeira fazia estrago: `.app` declara
   `grid-template-areas: "top" "stage"`, ou seja UMA coluna nomeada, e ela é a
   PRIMEIRA trilha. Pôr `0` na primeira trilha dá `.stage` com largura ZERO;
   palco, console e dock passavam a transbordar de uma faixa de 0px e
   encostavam na esquerda da tela.

   Medido em 1024×813: `.stage` w=0, console em x=0 com centro em 216 num
   viewport de 1024 — a coluna centrada não ficava centrada. O defeito é
   anterior ao FRONT-17 (valia para as duas colunas também); só ficou visível
   agora porque centralizar é o ponto da tarefa. */

/* ===================== LAYOUT HERDADO (cores vêm da seção final) =====================
   As regras abaixo definem POSIÇÃO e FORMA. A cor de cada uma delas é decidida
   no fim do arquivo, na seção "IDENTIDADE SEMANTIX", que é a autoridade
   cromática. Se estiver mexendo em cor, é lá — não aqui. */
.stars { opacity: 1; }   /* grafismo da marca (ver presenca.js) */
/* Fios CEO→agentes (ondas suaves, desenhados em px no app.js) */
.wires { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }

/* Feixes/traços mais escuros p/ contraste no branco */
.link { stroke: rgba(var(--tinta-media-rgb),.45); filter: drop-shadow(0 0 2px rgba(var(--tinta-media-rgb),.22)); }
.link.faint { stroke: rgba(var(--tinta-media-rgb),.22); }

/* Núcleo minimalista: só o glow roxo radial apagando pra transparente */
.orb-core {
  top: 36%;                      /* sobe mais pra abrir banda alta do gráfico embaixo */
  width: 372px; height: 372px;  /* um pouco menor */
  background:
    radial-gradient(circle at 46% 34%, rgba(205,195,255,.30), rgba(205,195,255,0) 34%),
    radial-gradient(circle at 50% 46%, rgba(var(--tinta-media-rgb),.26), rgba(var(--tinta-media-rgb),.12) 28%, rgba(var(--tinta-media-rgb),.04) 52%, rgba(var(--tinta-media-rgb),0) 72%);
  box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}

/* Nós = MESMO diamante do centro (imagem real), disco recolorido + borda apagando */
.node-orb {
  width: 76px; height: 76px; border-radius: 50%; position: relative;
  background: none; box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
.node.outer .node-orb { width: 66px; height: 66px; }
.node { width: 134px; }
.node.outer { width: 118px; }
.node-name { font-size: 12px; }
.node-sub { font-size: 10px; }
.node-logo {
  position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 50%;
  object-fit: cover;
  /* degradê do disco apagando no FINAL embaixo (igual ao núcleo) */
  -webkit-mask-image: linear-gradient(180deg, #000 50%, rgba(0,0,0,.45) 80%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 50%, rgba(0,0,0,.45) 80%, transparent 100%);
}
.node-orb svg { width: 44px; height: 44px; }  /* legado (não usado) */
.badge { background: #ffffff; color: var(--tinta-media); box-shadow: inset 0 0 0 1px rgba(var(--tinta-media-rgb),.35), 0 1px 4px rgba(0,0,0,.06); }
/* Flutuação suave em todos os nós (como o Agentix CEO), em tempos diferentes */
@keyframes floatNode { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
.node-orb { animation: floatNode var(--flut, 6s) ease-in-out infinite; animation-delay: var(--flut-d, 0s); }
.node.ativo .node-name { color: var(--tinta-media); text-shadow: none; }

/* Camada Semantix / títulos */

/* Pílulas */
.pill { border-color: rgba(var(--tinta-media-rgb),.4); background: rgba(var(--tinta-media-rgb),.06); color: var(--tinta-media); }
.pill:hover { background: rgba(var(--tinta-media-rgb),.16); color: #4c1d95; }

/* Dock / mic / estados — discreto, soft, só o microfone */
.dock { gap: 6px; bottom: 1.5%; }
.mic-orb {
  width: 46px; height: 46px;   /* menor, discreto */
  background: radial-gradient(circle at 38% 32%, rgba(var(--tinta-media-rgb),.16), rgba(var(--superficie-rgb),.6));
  border-color: rgba(var(--tinta-media-rgb),.28); color: var(--tinta-media);
  box-shadow: 0 4px 16px rgba(var(--tinta-media-rgb),.12), inset 0 0 10px rgba(var(--tinta-media-rgb),.08);
}
.mic-orb svg { width: 20px; height: 20px; }
.mic-orb:hover { color: #4c1d95; transform: scale(1.05); }
.estado-label { font-size: 9px; letter-spacing: 4px; opacity: .7; }
.mic-orb.gravando { color: var(--sobre-preenchimento); }
.narracao { color: #0e8fd0; text-shadow: none; }
.gate-mini { background: rgba(var(--superficie-rgb),.94); border-color: rgba(var(--tinta-media-rgb),.32); box-shadow: 0 6px 20px rgba(0,0,0,.08); }

/* ===================== v2 — Viz "bolinhas" (insight do Agentix) ===================== */
.viz {
  display: flex; flex-direction: column;
  gap: 6px; pointer-events: none;
}
.viz-title {
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted);
  opacity: 0; transition: opacity .4s; min-height: 12px; text-align: center;
}
.viz.insight .viz-title { opacity: 1; }
/* ---- A RÉGUA DO CARTÃO (20/09/2026) --------------------------------------
   "veículos · carteira de 15/09": a unidade e o corte de tempo do que está na
   tela, numa linha discreta embaixo do título — a pergunta que o board faz
   antes de olhar o número ("isto é em reais ou em caminhões? de quando?").

   Campo OPCIONAL da spec, e por isso VAZIA ELA NÃO EXISTE PARA O LAYOUT:
   `display: none`, nunca `visibility: hidden` com altura reservada. A legenda
   e as pastilhas de série reservam espaço de propósito (a altura do cartão tem
   de ser a mesma antes e depois do desenho); a régua não pode, porque o
   `renderVizEm` SOMA a altura dos filhos do slot para saber quanto sobra ao
   gráfico — um elemento vazio com altura tiraria pixels do plot de todo
   cartão que não tem régua, que é a maioria deles. */
.viz-regua {
  font: 500 9px/1.3 var(--mono); letter-spacing: .2px; text-transform: lowercase;
  color: var(--muted); text-align: center; flex: 0 0 auto;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.viz-regua:empty { display: none; }
/* área de plotagem: ocupa a faixa inferior; o SVG desenha o gráfico de linha-de-dados */
.viz-plot { position: relative; width: 100%; height: 205px; }
.viz-plot svg { width: 100%; height: 100%; overflow: visible; display: block; }
.viz .v-val { fill: var(--tinta-forte); font: 600 11px var(--fonte-texto, "Geist", system-ui, sans-serif); font-variant-numeric: tabular-nums; }   /* 20/09: negrito só no herói (`_HL`) */
.viz .v-cat, .viz .v-nota { fill: var(--dim); font: 500 10px var(--fonte-texto, "Geist", system-ui, sans-serif); letter-spacing: .3px; }
/* animação de entrada dos marcadores */
@keyframes ballIn { 0% { transform: translateY(12px) scale(.2); opacity: 0; } 100% { transform: translateY(0) scale(1); opacity: 1; } }

/* ============================================================================
   v1.1 — UX iterativo por requisição: HUD · roster · sub-nós · partículas
   ============================================================================ */

/* ---- HUD de métricas: canto INFERIOR DIREITO, sofisticado e minimalista ---- */
.top { justify-content: flex-end; }                 /* HUD saiu do header (fixed) */
.hud {
  position: fixed; right: 18px; bottom: 16px; z-index: 7;
  display: flex; align-items: stretch;
  background: rgba(var(--superficie-rgb),.70); border: 1px solid #ececf3; border-radius: 14px;
  box-shadow: 0 12px 34px rgba(80,40,160,.12); backdrop-filter: blur(10px);
  padding: 9px 4px;
}
.hud-item { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; padding: 0 15px; line-height: 1; }
.hud-item + .hud-item { border-left: 1px solid #efeaf6; }
.hud-val { font: 600 14px ui-monospace, "SF Mono", Menlo, monospace; color: var(--tinta-forte); letter-spacing: .02em; }
.hud-lbl { font-size: 8px; text-transform: uppercase; letter-spacing: .14em; color: var(--dim); margin-top: 5px; }

/* ---- Roster: canto INFERIOR ESQUERDO, compacto e soft ---- */
.roster {
  position: fixed; left: 18px; bottom: 16px; z-index: 7;
  width: 198px; background: rgba(var(--superficie-rgb),.70); border: 1px solid #ececf3; border-radius: 14px;
  box-shadow: 0 12px 34px rgba(80,40,160,.12); backdrop-filter: blur(10px);
  padding: 9px 10px 7px; transition: transform .32s ease;
}
/* Recolhido (padrão): vira uma pílula minúscula "Teammates", abre no clique */
.roster.recolhido { width: auto; padding: 7px 10px; }
.roster.recolhido .roster-list { display: none; }
.roster.recolhido .roster-head { padding-bottom: 0; gap: 10px; }
.roster-head { display: flex; align-items: center; justify-content: space-between;
  font: 700 9.5px ui-sans-serif; text-transform: uppercase; letter-spacing: .16em; color: var(--dim); padding: 0 2px 8px; }
.roster-head button { border: 0; background: var(--lavanda); color: var(--tinta-media); width: 19px; height: 19px; border-radius: 6px; cursor: pointer; font-weight: 700; line-height: 1; transform: rotate(90deg); }
.roster.recolhido .roster-head button { transform: rotate(0deg); }
.roster-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.roster-list li { display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 9px; }
.roster-list li > div { flex: 1; line-height: 1.1; min-width: 0; }
.roster-list li b { display: block; font-size: 11px; color: #2a2150; font-weight: 600; }
.roster-list li i { font-style: normal; font-size: 9px; color: var(--dim); }
.roster-list .st { font-size: 8px; text-transform: uppercase; letter-spacing: .08em; color: #b9b5c8; }
.roster-list .dot { width: 8px; height: 8px; border-radius: 50%; background: #d3cee2; flex: 0 0 auto; transition: all .25s; }
.roster-list li[data-st="working"] { background: rgba(var(--tinta-media-rgb),.07); }
.roster-list li[data-st="working"] .dot { background: var(--tinta-media); box-shadow: 0 0 0 3px rgba(var(--tinta-media-rgb),.18); animation: dotPulse 1s ease-in-out infinite; }
.roster-list li[data-st="working"] .st { color: var(--tinta-media); }
.roster-list li[data-st="done"] .dot { background: #34d399; }
.roster-list li[data-st="done"] .st { color: #10b981; }
@keyframes dotPulse { 0%,100% { box-shadow: 0 0 0 2px rgba(var(--tinta-media-rgb),.20); } 50% { box-shadow: 0 0 0 6px rgba(var(--tinta-media-rgb),.05); } }

/* ---- Partículas viajando nos fios (animação por requisição) ---- */
.flux { fill: var(--tinta-clara); filter: drop-shadow(0 0 5px rgba(167,139,250,.95)); pointer-events: none; }

/* ---- Agentes mais SOFT: halo gentil unificado; o realce vem na atuação ---- */
.node .node-orb { box-shadow: 0 8px 24px rgba(80,40,160,.16) !important; }
.node.ativo .node-orb { box-shadow: 0 0 0 3px rgba(var(--tinta-media-rgb),.20), 0 0 30px rgba(var(--tinta-media-rgb),.45) !important; }

/* ---- Enxame de repouso (viz) ---- */
@keyframes swarm { 0%,100% { opacity: .45; } 50% { opacity: .95; } }

/* ---- Usuário logado: pelo login registrado, soft, sem avatar (v1.1) ---- */
.usuario { display: flex; align-items: center; }
.usuario-nome {
  font: 600 12px ui-sans-serif; color: var(--tinta-media); letter-spacing: .03em;
  background: rgba(var(--tinta-media-rgb),.06); border: 1px solid rgba(var(--tinta-media-rgb),.14);
  padding: 5px 14px; border-radius: 999px; white-space: nowrap;
}
.usuario-nome:empty { display: none; }

/* ---- Taxímetro de tokens por Agentix — chip horizontal, compacto e legível ---- */
.badge {
  position: absolute;
  display: inline-flex; align-items: center; gap: 5px;
  height: auto; min-width: 0; width: max-content; padding: 4px 9px;
  border-radius: 999px; white-space: nowrap; line-height: 1;
  background: rgba(var(--superficie-rgb),.96); color: var(--tinta-forte);
  border: 1px solid rgba(var(--tinta-media-rgb),.18); box-shadow: 0 3px 10px rgba(var(--tinta-rgb),.14);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  z-index: 6; pointer-events: none;
}
.badge .tx-n {
  display: inline-flex; align-items: center; gap: 5px;
  font: 700 11px ui-monospace, "SF Mono", Menlo, monospace; letter-spacing: .01em;
}
.badge .tx-n::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--tinta-media); flex: 0 0 auto;
  box-shadow: 0 0 0 2px rgba(168,85,247,.16);
}
.badge::after {
  content: "tokens"; font: 600 8.5px ui-sans-serif;
  letter-spacing: .03em; text-transform: uppercase; color: #9a93b3;
}
.node.ativo .badge { border-color: var(--tinta-clara); box-shadow: 0 0 0 2px rgba(var(--tinta-media-rgb),.12), 0 4px 12px rgba(var(--tinta-media-rgb),.22); }
.node.ativo .badge .tx-n::before { animation: dotPulse 1s ease-in-out infinite; }

/* Taxímetro do CEO (orbe central): centralizado ABAIXO do orbe e do rótulo, sem sobrepor */
.ceo-badge { position: absolute; left: 50%; top: 88%; right: auto; bottom: auto; transform: translateX(-50%); z-index: 6; }
.estado-thinking .ceo-badge { border-color: var(--tinta-clara); box-shadow: 0 0 0 2px rgba(var(--tinta-media-rgb),.12), 0 4px 12px rgba(var(--tinta-media-rgb),.24); }
.estado-thinking .ceo-badge .tx-n::before { animation: dotPulse 1s ease-in-out infinite; }

/* ---- Nome (1 linha) + domínios (1 linha) = 2 linhas, mais soft, mais espaço ---- */
.node { width: 178px; }
.node-name { white-space: nowrap; letter-spacing: .5px; font-weight: 600; color: #46415f; }
.node-sub { white-space: nowrap; font-size: 9px; letter-spacing: .1px; color: #9c98ae; margin: 4px 0 8px; }
.node.ativo .node-name { color: var(--tinta-media); }

/* ---- Taxímetro no canto superior EXTERNO (ao lado do orbe, p/ fora) — nunca sobre o orbe ---- */
.node .badge { top: auto; bottom: 50%; }                              /* metade superior do orbe, subindo */
.node[data-lado="1"] .badge { left: calc(100% + 4px); right: auto; }      /* direita → canto superior direito */
.node[data-lado="-1"] .badge { right: calc(100% + 4px); left: auto; }     /* esquerda → canto superior esquerdo */
.node[data-lado="0"] .badge { left: calc(100% + 4px); right: auto; }       /* centro (modo de um especialista) */

/* ===================== Memória visual — grafo de conhecimento ===================== */
/* Botão no header */
.mem-btn {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font: 600 12px ui-sans-serif; color: var(--tinta-media); letter-spacing: .02em;
  background: rgba(var(--tinta-media-rgb),.06); border: 1px solid rgba(var(--tinta-media-rgb),.16);
  padding: 5px 12px 5px 10px; border-radius: 999px; transition: all .18s; margin-right: 10px;
}
.mem-btn svg { width: 16px; height: 16px; stroke-linecap: round; stroke-linejoin: round; }
.mem-btn:hover { background: rgba(var(--tinta-media-rgb),.12); border-color: rgba(var(--tinta-media-rgb),.32); }
.mem-btn.pulsa { animation: memPulsa 1.2s ease-in-out 2; box-shadow: 0 0 0 0 rgba(var(--tinta-media-rgb),.5); }
@keyframes memPulsa {
  0%,100% { box-shadow: 0 0 0 0 rgba(var(--tinta-media-rgb),.0); border-color: rgba(var(--tinta-media-rgb),.16); }
  50% { box-shadow: 0 0 0 7px rgba(var(--tinta-media-rgb),.10); border-color: rgba(var(--tinta-media-rgb),.6); }
}

/* Overlay tela-cheia */
.mem-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: radial-gradient(120% 90% at 50% 35%, rgba(247,245,255,.96), rgba(238,234,250,.98));
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: memFade .28s ease;
}
.mem-overlay.oculto { display: none; }
@keyframes memFade { from { opacity: 0; } to { opacity: 1; } }
#memCanvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.mem-top {
  position: absolute; top: 18px; left: 24px; right: 24px;
  display: flex; align-items: center; justify-content: space-between; pointer-events: none;
}
.mem-title { font: 700 16px ui-sans-serif; color: #46415f; letter-spacing: .02em; }
.mem-title span { font-weight: 500; color: #9c98ae; font-size: 13px; }
.mem-x {
  pointer-events: auto; cursor: pointer; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid rgba(var(--tinta-media-rgb),.18); background: rgba(var(--superficie-rgb),.9); color: var(--tinta-media);
  font-size: 15px; line-height: 1; transition: all .15s;
}
.mem-x:hover { background: var(--superficie); border-color: rgba(var(--tinta-media-rgb),.4); transform: rotate(90deg); }

/* Legenda */
.mem-legenda {
  position: absolute; left: 24px; bottom: 20px; display: flex; flex-wrap: wrap; gap: 6px 14px;
  background: rgba(var(--superficie-rgb),.78); border: 1px solid rgba(var(--tinta-media-rgb),.12);
  padding: 9px 14px; border-radius: 12px; pointer-events: none; max-width: 360px;
}
.mem-legenda span { font: 600 11px ui-sans-serif; color: #6b6680; display: inline-flex; align-items: center; gap: 6px; }
.mem-legenda span::before { content: ""; width: 9px; height: 9px; border-radius: 50%; background: var(--c); }

/* Painel de explicabilidade (fatos usados na resposta) */
.mem-explica {
  position: absolute; top: 64px; right: 24px; width: 320px; max-height: 70vh; overflow: auto;
  background: rgba(var(--superficie-rgb),.92); border: 1px solid rgba(var(--tinta-media-rgb),.14);
  border-radius: 16px; padding: 14px 16px; box-shadow: 0 12px 36px rgba(var(--tinta-rgb),.12);
}
.mem-ex-tit { font: 700 12px ui-sans-serif; color: var(--tinta-forte); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 10px; }
.mem-ex-vazio { font: 500 12px ui-sans-serif; color: #9c98ae; line-height: 1.5; }
.mem-explica ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
.mem-explica li { display: flex; gap: 9px; align-items: flex-start; }
.mem-explica li .dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 4px; flex: 0 0 auto; }
.mem-explica .ex-txt { font: 500 12px ui-sans-serif; color: #46415f; line-height: 1.4; }
.mem-explica .ex-meta { font: 600 10px ui-sans-serif; color: #a09bb2; margin-top: 3px; }

/* Detalhe do nó clicado */
.mem-detalhe {
  position: absolute; left: 24px; top: 64px; width: 300px;
  background: rgba(var(--superficie-rgb),.95); border: 1px solid rgba(var(--tinta-media-rgb),.16);
  border-radius: 14px; padding: 13px 15px; box-shadow: 0 12px 36px rgba(var(--tinta-rgb),.14);
}
.mem-detalhe.oculto { display: none; }
.mem-det-orig { font: 700 11px ui-sans-serif; letter-spacing: .04em; margin-bottom: 6px; }
.mem-det-txt { font: 500 13px ui-sans-serif; color: #46415f; line-height: 1.5; }

/* Tooltip de hover */
.mem-dica {
  position: absolute; max-width: 280px; pointer-events: none; z-index: 2;
  background: rgba(40,30,70,.95); color: #f3f0ff; font: 500 11.5px ui-sans-serif; line-height: 1.4;
  padding: 7px 10px; border-radius: 9px; box-shadow: 0 6px 20px rgba(0,0,0,.22);
}
.mem-dica.oculto { display: none; }

/* ---- Memória na página principal: constelação de nós por agente ---- */
.mem-main { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.mem-main-dica {
  position: fixed; max-width: 280px; pointer-events: none; z-index: 50;
  background: rgba(40,30,70,.95); color: #f3f0ff; font: 500 11.5px ui-sans-serif; line-height: 1.4;
  padding: 7px 10px; border-radius: 9px; box-shadow: 0 6px 20px rgba(0,0,0,.22);
}
.mem-main-dica.oculto { display: none; }

/* ---- Bloom: foco de memória no PRÓPRIO palco (constelação floresce, cromo esmaece) ---- */
.layer, .hud, .roster, .wires, .stars, .orb-core, .node-orb, #memMain { transition: opacity .45s ease, background .45s ease; }
body.bloom .layer, body.bloom .hud, body.bloom .roster, body.bloom .wires,
body.bloom .badge, body.bloom .ceo-badge, body.bloom .node-name, body.bloom .node-sub, body.bloom .pill.acao {
  opacity: .08 !important; pointer-events: none;
}
body.bloom .stars { opacity: .3; }
body.bloom .orb-core, body.bloom .node-orb { opacity: .5; }
body.bloom #memMain { background: var(--fundo-bloom); }

.bloom-bar {
  position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 61;
  display: flex; align-items: center; gap: 12px; padding: 8px 10px 8px 16px;
  background: rgba(var(--superficie-rgb),.92); border: 1px solid rgba(var(--tinta-media-rgb),.16); border-radius: 999px;
  box-shadow: 0 8px 28px rgba(var(--tinta-rgb),.14); animation: memFade .3s ease;
}
.bloom-bar.oculto { display: none; }
.bloom-tit { font: 700 12px ui-sans-serif; color: var(--tinta-forte); letter-spacing: .02em; }
.bloom-act {
  cursor: pointer; font: 600 11px ui-sans-serif; color: var(--tinta-media);
  background: rgba(var(--tinta-media-rgb),.06); border: 1px solid rgba(var(--tinta-media-rgb),.16);
  padding: 5px 11px; border-radius: 999px; transition: all .15s;
}
.bloom-act:hover { background: rgba(var(--tinta-media-rgb),.14); border-color: rgba(var(--tinta-media-rgb),.34); }
.bloom-act.x { color: #9c98ae; }

/* ╔══════════════════════════════════════════════════════════════════════════╗
   ║  IDENTIDADE SEMANTIX — a autoridade cromática desta interface.           ║
   ║  Branding book "LO01 Guia Rápido", julho/2026.                           ║
   ║                                                                          ║
   ║  Fundo CLARO com campos de roxo — como as páginas do próprio guia.       ║
   ║  1. O ROXO #5A179A é a cor principal e PREDOMINA: sobre o claro ele é a  ║
   ║     cor das linhas, da tipografia e dos campos de lavanda.               ║
   ║  2. Branco é a superfície.                                               ║
   ║  3. Magenta, ciano e laranja são SECUNDÁRIAS e entram de forma pontual.  ║
   ║  4. Grafismo = linhas luminosas que se cruzam. Sobre o claro elas são    ║
   ║     ROXAS, e APENAS UMA recebe a cor secundária de cada vez. É por isso  ║
   ║     que os cinco Agentix são roxos em repouso e só quem está trabalhando ║
   ║     ganha cor — a marca e a legibilidade concordam.                      ║
   ║  5. Tipografia: Geist (vendorizada em /static/fonts, sem CDN).           ║
   ╚══════════════════════════════════════════════════════════════════════════╝ */

/* ---- Geist, servida localmente (a página roda offline num EC2) ---- */
@font-face {
  font-family: "Geist"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("/static/fonts/geist-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("/static/fonts/geist-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Geist Mono"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("/static/fonts/geistmono-latin.woff2") format("woff2");
}

/* ╔══════════════════════════════════════════════════════════════════════════╗
   ║  TOKENS SEMÂNTICOS — o tema é uma tabela, não uma cor.                   ║
   ║                                                                          ║
   ║  Cada valor abaixo tem um PAPEL (tinta, superfície, preenchimento…), e o ║
   ║  resto do arquivo só fala por papel. Os valores aqui são o tema de       ║
   ║  ORIGEM (o roxo sobre claro do branding book). Uma instância troca o     ║
   ║  tema sem tocar neste arquivo: `tema.js` deriva os tokens de             ║
   ║  `produto.modo`/`cor_marca`/`cor_nucleo` (GET /api/agentes) e aplica     ║
   ║  `produto.tema` por cima, inline no <html> — inline vence este :root.    ║
   ║                                                                          ║
   ║  Regra: NENHUM hex de cor abaixo desta tabela. Cor nova = token novo.    ║
   ╚══════════════════════════════════════════════════════════════════════════╝ */
:root {
  /* paleta oficial de origem (só a tabela abaixo pode citá-la) */
  --roxo: #5A179A;
  --branco: #FFFFFF;
  --magenta: #C454D8;
  --ciano: #40E6FF;
  --laranja: #FF8A26;

  /* ---- tinta: linhas e tipografia (sobre claro, é a cor da marca) ---- */
  --tinta: #5A179A;            --tinta-rgb: 90,23,154;
  --tinta-forte: #3E0D6E;      /* texto de leitura, título */
  --tinta-media: #7B2ECC;      --tinta-media-rgb: 123,46,204;
  --tinta-clara: #B486E8;      /* placeholder, marcado, "feito" */
  --txt: #2A0B4C;
  --muted: #6E4E96;
  --dim: #9A82B8;

  /* ---- a única secundária, e o alerta ---- */
  --acento: #C454D8;           --acento-rgb: 196,84,216;
  --alerta: #FF8A26;           --alerta-txt: #B85F0E;

  /* ---- superfícies: papel, vidro, cartão ---- */
  --superficie: #FFFFFF;       --superficie-rgb: 255,255,255;
  --papel: #FBF8FE;
  --lavanda: #EFE6FA;
  --vidro: rgba(255,255,255,.80);
  --vidro-borda: rgba(90,23,154,.14);
  --sombra: 0 12px 34px rgba(90,23,154,.13);

  /* ---- preenchimento: botão cheio, disco do nó; e o texto por cima ---- */
  --preenchimento: #5A179A;
  --preenchimento-2: #8B3FD9;
  --sobre-preenchimento: #FFFFFF;

  /* ---- fundos compostos ---- */
  --fundo:
    radial-gradient(1200px 900px at 50% 44%, rgba(123,46,204,.18), rgba(123,46,204,0) 68%),
    radial-gradient(1000px 620px at 50% 112%, rgba(90,23,154,.16), rgba(90,23,154,0) 66%),
    linear-gradient(176deg, #FBF8FE 0%, #F2E9FB 46%, #E9DCF7 100%);
  --fundo-overlay: radial-gradient(120% 90% at 50% 35%, rgba(251,248,254,.97), rgba(233,220,247,.98));
  --fundo-bloom: radial-gradient(120% 95% at 50% 45%, rgba(251,248,254,.72), rgba(233,220,247,.5));
  --dica-fundo: rgba(42,11,76,.96);  --dica-txt: #F6F0FF;  --dica-borda: rgba(255,255,255,.14);

  /* ---- estados do painel mono ---- */
  --estado-ouvindo: #A22FBB;
  --estado-falando: #0E7C93;

  /* ---- a figura (presenca.js lê daqui) e o gráfico (app.js lê daqui) ---- */
  --figura-linha: #5A179A;
  --figura-nucleo: #FF8A26;
  --marca-agua: #5A179A;
  --viz-a: #a78bfa;  --viz-b: #6d28d9;  --viz-claro: #efe9ff;  --viz-hi: #b07bff;  --viz-hi-b: #7c3aed;

  /* ---- spec 019: UM HERÓI por gráfico (a linguagem visual do board) -------
     Quatro papéis, não quatro cores: `heroi` é a série/marca em foco,
     `neutro` é todo o resto, `neutro-txt` é o texto sobre ele e `acento` é o
     ÚNICO destaque (o ponto que está sendo falado). Quem dá o valor de
     verdade é o `tema.js` (`derivarClaro`/`derivarEscuro`), que os deriva da
     marca da instância — na Volks, Deep Blue #001E50 no herói e Accent Blue
     #00B1EB no acento. As três linhas abaixo são só a RESERVA da pele de
     ORIGEM (a instância que não deriva nada), e por isso apontam para tokens
     que já existem, nunca para um hex novo: hex de agente no CSS é a lista de
     agentes voltando ao front (tests/contratos/test_paleta_agentes.py). */
  --viz-heroi: var(--tinta);
  --viz-neutro: var(--lavanda);
  --viz-neutro-txt: var(--muted);
  --viz-acento: var(--acento);

  /* tokens de texto herdados do resto do arquivo */
  --violet-soft: var(--tinta);
  --violet: var(--tinta-media);
  --cyan: var(--tinta-media);
  --green: var(--tinta);
  --no: var(--alerta);
  --line: rgba(var(--tinta-rgb),.12);
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  /* nomes antigos, mantidos como aliases para quem ainda os cita */
  --roxo-forte: var(--tinta-forte);
  --roxo-medio: var(--tinta-media);
  --roxo-claro: var(--tinta-clara);
}

body {
  font-family: "Geist", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--txt);
  /* campos de roxo claro sobre papel — o roxo domina sem escurecer a tela */
  background: var(--fundo);
}

/* ===== Grafismo e fios: linhas roxas; a secundária é do domínio ativo ===== */

.link {
  stroke: rgba(var(--tinta-rgb),.34); stroke-width: 1.4;
  filter: drop-shadow(0 0 3px rgba(var(--tinta-media-rgb),.30));
}
.link.faint { stroke: rgba(var(--tinta-rgb),.18); stroke-width: 1.1; }
.link.ativo {
  stroke: var(--ag-cor, var(--tinta)); stroke-width: 2.6;
  animation: neon 1.8s ease-in-out infinite;
}
@keyframes neon {
  0%, 100% { filter: drop-shadow(0 0 4px var(--ag-cor, var(--tinta))) drop-shadow(0 0 12px var(--ag-cor, var(--tinta))); }
  50%      { filter: drop-shadow(0 0 8px var(--ag-cor, var(--tinta))) drop-shadow(0 0 24px var(--ag-cor, var(--tinta))); }
}
/* A cor de cada fio chega INLINE (`--ag-cor` no <path>, vinda da pele da
   instância via app.js). Nenhum id de agente é escrito aqui. */
.flux { fill: var(--tinta); filter: drop-shadow(0 0 5px rgba(var(--tinta-media-rgb),.7)); }

/* ===== A presença ocupa o centro; o orbe vira só a âncora =====
   O `.orb-core` some visualmente mas continua existindo: é dele que `app.js`
   tira a origem dos fios CEO→agentes e `presenca.js` tira o ponto de fuga. */
.orb-core {
  top: 42%; width: 372px; height: 372px;
  background: none; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none;
  animation: none;
}
/* Enquadramento FECHADO: mais alto que largo, para os ombros quase encostarem
   nas laterais e o crânio ficar perto do topo. A figura é a composição. */
.presenca {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  /* O tamanho vem de `--fig`, e ele NÃO é medido em `vw`/`vh` — ver
     `container-type: size` no `.palco`.

     Duas correções encadeadas mora aí. O "quebrando acima" (FRONT-20) era a
     figura dimensionada pela LARGURA: em tela larga e baixa ela crescia até
     caber na horizontal e a cabeça saía por cima. Pus um termo em `vh` e
     resolveu — até o FRONT-24 acrescentar a faixa do gráfico, quando a janela
     continuou do mesmo tamanho e o espaço da figura encolheu. `vh` passou a
     medir a coisa errada.

     Agora ela se mede pela linha do palco, que é quem de fato a limita. A
     regra que sobrevive às duas: a figura ENCOLHE para caber; nada é empurrado
     e nada salta — que é o "não alterasse o deslocamento de tela para cima". */
  width: var(--fig); aspect-ratio: 144 / 157; height: auto;
  pointer-events: none;
}
/* A identidade do CEO vive no painel mono, não sobre o peito da figura. */

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║  A PALETA DOS AGENTES — FONTE ÚNICA. Não copie estes valores.         ║
   ╚═══════════════════════════════════════════════════════════════════════╝

   Havia SEIS lugares definindo a cor de agente: aqui, nos `#link*` logo
   acima, no `COR_AGENTE` do presenca.js, em DUAS tabelas dentro do
   memoria_grafo.js, e à mão na legenda do index.html. As cinco cores
   divergiam entre o palco e o grafo — o mesmo CFO era ciano numa tela e
   azul-petróleo na outra. Agora só existe aqui; o JS lê daqui por
   `getComputedStyle`, e a legenda do HTML deriva por `var()`.

   POR QUE CINCO CORES E NÃO TRÊS. A regra da marca reserva magenta, ciano e
   laranja como secundárias — três para cinco agentes. A consequência, medida
   com quatro agentes acesos (o caso REAL: o prompt manda usar
   `analise_paralela` sempre, e a sugestão "Que decisão eu deveria estar
   tomando esta semana?" aciona cfo+cro+coo+cpo): CFO e CPO ficavam com o
   MESMO ciano, um em cima do outro na mesma coluna, e COO e Research com o
   mesmo magenta. Cor que serve para identificar não pode ser ambígua
   justamente quando mais de um está aceso.

   POR QUE NÃO AS CINCO CORES DO GRAFO. Testei: elas separam bem, mas duas são
   ROXAS (`#7B2ECC`, `#B486E8`) — e roxo é a cor de REPOUSO no palco. O CPO
   "aceso" ficava idêntico ao CPO parado. Trocar ambiguidade entre agentes por
   ambiguidade entre estados é pior.

   O QUE FIZEMOS. Só as TRÊS FAMÍLIAS da marca, com duas variações de
   luminosidade dentro delas — ciano→petróleo e laranja→dourado. Nenhuma
   matiz nova entra na identidade; o que entra é uma segunda parada na mesma
   escala, que é o que a marca já faz com o roxo.

   DUAS DERIVAÇÕES, UMA FONTE. O palco é claro com brilho: pede cor saturada.
   O grafo é fundo lavanda com pontos pequenos: cor clara demais some. Por
   isso cada agente tem `--ag-X` (palco) e `--ag-X-grafo` (grafo) — dois
   valores, uma origem, e a relação entre eles fica visível aqui em vez de
   virar duas tabelas que divergem. */
:root {
  /* A cor de CADA AGENTE não mora mais aqui: vem da pele da instância
     (`produto.cores` em GET /api/agentes) e entra inline em cada nó, fio e
     chip como `--ag-cor`. O CSS só conhece o que é do produto: a marca (que
     presenca.js sobrescreve com `produto.cor_marca`), o usuário e o vazio. */
  --ag-ceo: #5A179A;       --ag-ceo-grafo: #5A179A;   /* o roxo da marca (reserva) */
  --ag-usuario-grafo: var(--tinta-forte);
  --ag-vazio-grafo: var(--dim);
}

/* A sétima tabela — `.node.X .node-orb { box-shadow: rgba(63,224,160…) }` com
   verde, azul, âmbar, turquesa e rosa — SAIU. Ela não pertencia a nenhuma
   paleta e estava MORTA: medi `getComputedStyle` nos cinco nós, em repouso e
   ativos, e a sombra computava `rgba(0,0,0,0)` em todos. Regra morta que
   parece viva é a pior forma de documentação errada. */

/* ===== Nós dos agentes: roxos em repouso, coloridos só ao atuar =====
   `--ag-cor` chega inline em cada nó (app.js, da pele da instância). */
/* O símbolo dos nós é o DIAMANTE OFICIAL (diamante.svg), recortado como
   máscara sobre um disco roxo — vetor, então não serrilha em nenhum tamanho, e
   a cor sai do CSS. O `semantixai_logo.jpg` que estava aqui era outro
   diamante, e o usuário apontou isso. */
.node-orb {
  background: radial-gradient(circle at 38% 30%, var(--preenchimento-2), var(--preenchimento) 78%) !important;
  border-radius: 50%;
}
.node-logo {
  position: absolute; inset: 26%; width: auto; height: auto; display: block;
  background-color: var(--sobre-preenchimento); filter: none; opacity: .92;
  /* A marca do orbi vem da PELE da instância (`--logo-mascara`, posta por
     tema.js a partir de `produto.logo`); sem logo declarado, o diamante. */
  -webkit-mask: var(--logo-mascara, url("/static/diamante.svg")) center / contain no-repeat;
  mask: var(--logo-mascara, url("/static/diamante.svg")) center / contain no-repeat;
  transition: opacity .35s;
}
.node .node-orb {
  box-shadow: 0 0 0 1px rgba(var(--tinta-rgb),.12), 0 10px 26px rgba(var(--tinta-rgb),.16) !important;
  transition: box-shadow .35s;
}
.node.ativo .node-logo {
  opacity: 1;
}
.node.ativo .node-orb {
  box-shadow: 0 0 0 2px var(--ag-cor), 0 0 22px var(--ag-cor), 0 10px 26px rgba(var(--tinta-rgb),.2) !important;
  animation: pulseNode 1.6s ease-in-out infinite !important;
}
/* Parada, a constelação é discreta: ela só ganha protagonismo quando há
   delegação acontecendo. Em repouso quem fala é a figura. */
.node { opacity: .62; transition: opacity .4s ease; }
.node:hover, .node.ativo, .node.foco { opacity: 1; }
.node-name { color: var(--tinta-forte); }
.node.ativo .node-name { color: var(--tinta); text-shadow: 0 0 14px var(--ag-cor); }
.node-sub { color: var(--dim); }

/* ===== Camada Semantix / pílulas ===== */
.pill {
  border-color: rgba(var(--tinta-rgb),.26); background: rgba(var(--superficie-rgb),.72);
  color: var(--tinta); backdrop-filter: blur(6px);
}
.pill:hover { background: var(--tinta); border-color: var(--tinta); color: var(--sobre-preenchimento); }
.pill.ok { border-color: var(--tinta); color: var(--tinta); }
.pill.no { border-color: var(--alerta); color: var(--alerta-txt); }

/* ===== Painel mono: a leitura de máquina, como nas referências ===== */
.mono-canto {
  position: absolute; top: 18px; left: 26px; z-index: 6; pointer-events: none;
  display: flex; flex-direction: column; gap: 5px; align-items: flex-start;
}
/* D6 — 9,5px era um número de MONITOR (leitura a 60cm). A tela do board é
   vista a ~3m: em 1920 o painel virava textura. O piso continua 9,5px para
   que em 1280 nada mude de tamanho (0,6vw = 7,7px, abaixo do piso) e o teto
   de 13px chega em 2167px; em 1920 dá 11,5px (+21%). `vw` e não `cqw`: o
   painel mora no palco, mas o que muda com a distância é a TELA. */
.mono-l { display: flex; align-items: baseline; gap: 9px; font: 500 clamp(9.5px, 0.6vw, 13px)/1.35 var(--mono); letter-spacing: .13em; }
.mono-l > span { color: var(--dim); min-width: 74px; }
.mono-v { color: var(--tinta); font-weight: 600; }
.mono-v.aceso { color: var(--estado-pensando, var(--alerta-txt)); }
.mono-v.estado-listening { color: var(--estado-ouvindo); }
/* mesmo token do anel do orbi (D3): o olho liga o painel ao centro da tela */
.mono-v.estado-thinking { color: var(--estado-pensando, var(--alerta-txt)); }
.mono-v.estado-speaking { color: var(--estado-falando); }
/* o taxímetro do CEO mora no painel, não sobre o peito da figura */
.mono-canto .ceo-badge { position: static; transform: none; margin-top: 8px; }
/* D6 — no modo orbital o taxímetro do executivo é o `.badge` do rodapé do
   orbi (bloco da 011 v3): este aqui fica órfão no canto, mostrando só a
   palavra "TOKENS" sem número. Some — dois taxímetros na mesma tela é um
   pedindo para divergir do outro. */
body.modo-orbital .mono-canto .ceo-badge { display: none; }

/* ===== Dock: STATUS explícito + narração do evento `acao` ===== */
.dock { gap: 8px; bottom: 1.5%; }
/* O rótulo "STATUS: EM REPOUSO" saiu (spec 012 T13): o orbi e o microfone já
   dizem o estado. O elemento continua no HTML porque o app.js escreve nele. */
.estado-label { display: none !important; }
/* ---- Narração como LOG (spec 012 T13) --------------------------------------
   Referência do Lucas: a cadeia de pensamento escrita, minimalista. Até
   quatro linhas, a mais recente embaixo, as antigas desvanecendo; mono 12px
   apagado, centrado, sem fundo, um `·` à esquerda de cada linha. O app.js
   acrescenta uma linha por evento `acao` (`<span>` ou `<div>` filho de
   `#narracao`) e mantém no máximo quatro. Em repouso (`.stage.estado-idle`)
   a lista some sozinha 6 s depois — CSS puro, sem temporizador. Texto solto
   (o `setNarracao` de hoje) continua legível com o mesmo estilo. */
.narracao {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  font: 500 12px/1.4 var(--mono); letter-spacing: .03em; color: var(--muted);
  text-shadow: none; min-height: 0; width: min(720px, 90%); max-width: none;
  text-align: center; background: none;
  opacity: 1; transition: opacity .6s ease;
}
.narracao:empty { display: none; }
.narracao > * { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.narracao > *::before { content: "· "; color: var(--dim); }
.narracao > :nth-last-child(1) { opacity: 1; }
.narracao > :nth-last-child(2) { opacity: .8; }
.narracao > :nth-last-child(3) { opacity: .55; }
.narracao > :nth-last-child(4) { opacity: .35; }
.narracao > :nth-last-child(n+5) { display: none; }
.stage.estado-idle .narracao { opacity: 0; transition: opacity .8s ease 6s; pointer-events: none; }
.mic-orb {
  width: 52px; height: 52px; color: var(--sobre-preenchimento);
  background: linear-gradient(160deg, var(--tinta-media), var(--tinta));
  border-color: transparent; box-shadow: 0 10px 26px rgba(var(--tinta-rgb),.32);
}
.mic-orb svg { width: 21px; height: 21px; }
.mic-orb:hover { box-shadow: 0 12px 32px rgba(var(--tinta-rgb),.45); }
.mic-orb.gravando {
  background: linear-gradient(160deg, var(--acento), var(--tinta));
  box-shadow: 0 0 0 5px rgba(var(--acento-rgb),.20), 0 12px 30px rgba(var(--tinta-rgb),.35);
}
.gate-mini { background: var(--vidro); border-color: var(--vidro-borda); box-shadow: var(--sombra); backdrop-filter: blur(12px); }
.gate-msg { color: var(--tinta); font-size: 11.5px; }

/* ===== HUD e roster: vidro branco sobre o claro ===== */
.hud, .roster {
  background: var(--vidro); border: 1px solid var(--vidro-borda);
  box-shadow: var(--sombra); backdrop-filter: blur(14px);
}
.hud-item + .hud-item { border-left: 1px solid rgba(var(--tinta-rgb),.10); }
.hud-val { font-family: var(--mono); color: var(--tinta); }
.hud-lbl { color: var(--dim); }
.roster-head { color: var(--dim); }
.roster-head button { background: rgba(var(--tinta-rgb),.08); color: var(--tinta); }
.roster-list li b { color: var(--tinta-forte); }
.roster-list li i { color: var(--dim); }
.roster-list .st { color: var(--dim); font-family: var(--mono); }
.roster-list .dot { background: rgba(var(--tinta-rgb),.22); }
.roster-list li[data-st="working"] { background: rgba(var(--tinta-rgb),.07); }
.roster-list li[data-st="working"] .dot { background: var(--tinta); box-shadow: 0 0 0 3px rgba(var(--tinta-rgb),.16); }
.roster-list li[data-st="working"] .st { color: var(--tinta); }
.roster-list li[data-st="done"] .dot { background: var(--tinta-clara); }
.roster-list li[data-st="done"] .st { color: var(--tinta-media); }
@keyframes dotPulse { 0%,100% { box-shadow: 0 0 0 2px rgba(var(--tinta-rgb),.22); } 50% { box-shadow: 0 0 0 7px rgba(var(--tinta-rgb),.04); } }

/* ===== Taxímetro de tokens ===== */
.badge {
  background: rgba(var(--superficie-rgb),.92); color: var(--tinta);
  border: 1px solid rgba(var(--tinta-rgb),.16); box-shadow: 0 3px 10px rgba(var(--tinta-rgb),.14);
}
.badge .tx-n { font-family: var(--mono); }
.badge .tx-n::before { background: var(--tinta-media); box-shadow: 0 0 0 2px rgba(var(--tinta-media-rgb),.14); }
.badge::after { color: var(--dim); }
.node.ativo .badge { border-color: var(--ag-cor); box-shadow: 0 0 0 1px var(--ag-cor), 0 3px 12px rgba(var(--tinta-rgb),.18); }
.node.ativo .badge .tx-n::before { background: var(--ag-cor); }

/* ===== Usuário / botão de memória ===== */
.usuario-nome, .mem-btn {
  color: var(--tinta); background: rgba(var(--superficie-rgb),.78);
  border-color: rgba(var(--tinta-rgb),.16);
}
.mem-btn:hover { background: var(--superficie); border-color: rgba(var(--tinta-rgb),.4); }

/* ═══════════════════════════════════════════════════════════════════════════
   DECOMPOSIÇÃO — o domínio se abre no leque das suas faculdades.

   Chegou `ramificacao{ativo:true}`: sobe do nó um leque de faculdades ligadas
   por linhas finas, e os outros domínios apagam. Chegou `subagente`: a
   faculdade exata acende com a cor secundária daquele domínio. As faculdades
   COMPARTILHADAS (Memória, Conhecimento) são desenhadas diferente — tracejadas
   e menores — porque não pertencem ao domínio: são de todo mundo.
   ═══════════════════════════════════════════════════════════════════════════ */
.decomp-fios, .decomp-chips {
  position: fixed; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 8;
}
.decomp-fios .fio {
  fill: none; stroke: rgba(var(--tinta-rgb),.35); stroke-width: 1;
  stroke-dasharray: 1000; stroke-dashoffset: 1000;
  transition: stroke-dashoffset .55s cubic-bezier(.22,.9,.3,1), stroke .3s, opacity .3s;
}
.decomp-fios .fio.aberto { stroke-dashoffset: 0; }
.decomp-fios .fio.compart { stroke: rgba(var(--tinta-rgb),.20); stroke-dasharray: 3 5; stroke-dashoffset: 0; opacity: 0; }
.decomp-fios .fio.compart.aberto { opacity: 1; }
.decomp-fios .fio.ativo {
  stroke: var(--cor, var(--tinta)); stroke-width: 1.8;
  filter: drop-shadow(0 0 5px var(--cor, var(--tinta)));
}

.fac {
  position: absolute;
  /* `--anc` decide de que lado o chip cresce: 0% à direita do nó, -100% à
     esquerda. Assim o texto nunca corre por cima do próprio domínio. */
  transform: translate(var(--anc, -50%), -50%) translate(var(--dx), var(--dy)) scale(.6);
  opacity: 0; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px 5px 9px; border-radius: 999px;
  background: rgba(var(--superficie-rgb),.94); border: 1px solid rgba(var(--tinta-rgb),.22);
  box-shadow: 0 6px 18px rgba(var(--tinta-rgb),.16); backdrop-filter: blur(8px);
  font: 600 10.5px/1 "Geist", system-ui, sans-serif; letter-spacing: .04em;
  color: var(--tinta-forte);
  transition: transform .52s cubic-bezier(.22,.9,.3,1), opacity .42s ease,
              border-color .28s, box-shadow .28s, color .28s, background .28s;
}
.fac.aberto { transform: translate(var(--anc, -50%), -50%) scale(1); opacity: 1; }
.fac i {
  width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto;
  background: rgba(var(--tinta-rgb),.35); transition: all .25s;
}
/* faculdade compartilhada: menor, tracejada, propositalmente secundária */
.fac.compart {
  font-size: 9.5px; padding: 4px 10px 4px 8px; color: var(--muted);
  background: rgba(var(--superficie-rgb),.80); border-style: dashed; border-color: rgba(var(--tinta-rgb),.24);
}
.fac.compart i { border-radius: 1px; background: rgba(var(--tinta-rgb),.28); }
/* a faculdade que está executando AGORA */
.fac.ativo {
  color: var(--tinta-forte); border-color: var(--cor, var(--tinta)); background: var(--superficie);
  box-shadow: 0 0 0 1.5px var(--cor, var(--tinta)), 0 6px 22px rgba(var(--tinta-rgb),.24);
}
.fac.ativo i { background: var(--cor, var(--tinta)); box-shadow: 0 0 8px var(--cor, var(--tinta)); animation: dotPulse 1s ease-in-out infinite; }
/* já usada neste turno: fica marcada, sem roubar a atenção */
.fac.usado:not(.ativo) { border-color: rgba(var(--tinta-rgb),.4); }
.fac.usado:not(.ativo) i { background: var(--cor, var(--tinta)); opacity: .7; }
.fac.pisca { animation: facPisca .6s ease-out; }
/* Leque FIXO (modo de um especialista): chips em linha, ancorados no centro,
   sempre visíveis. Só o estado `ativo`/`usado` muda com o turno. */
.fac.fixo { --anc: -50%; --dx: 0px; --dy: 0px; }
.fac.fixo.aberto { transform: translate(-50%, -50%) scale(1); opacity: 1; }
.fac.fixo.compart { opacity: .85; }
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.fixo { stroke: rgba(var(--tinta-rgb),.28); stroke-dasharray: none; stroke-dashoffset: 0; }
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.fixo.compart { stroke-dasharray: 3 5; opacity: 1; }
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.fixo.ativo { stroke: var(--cor, var(--tinta)); stroke-width: 1.8; }

/* ---- SATÉLITES das áreas, SISTEMAS e fios (spec 011 v3, T16) --------------
   Com um especialista só, as áreas SÃO a tela, em ARCO POR CIMA do orbi
   grande. O chip é SÓ O DISCO (`<i>`): é o retângulo dele que fica sobre o
   ponto do arco, e o rótulo (`<b>`) sai para fora do arco, em absoluto, pelo
   lado que o presenca.js declara em `data-rotulo` (padrão: cima).
   Estados, com peso crescente:
     repouso  disco de 52px, sem brilho, ponto da cor do domínio no centro
     ativo    disco de 64px com ANEL na cor do agente, brilho, respiração e um
              pulso radial (`pulso`); cresce em torno do próprio centro
     usado    disco de 52px PREENCHIDO com a cor, quieto, até `limpar()`
   As compartilhadas continuam menores e tracejadas: são de todo mundo. */
body:is(.modo-unico, .modo-orbital) .fac.fixo {
  display: block; width: 52px; height: 52px; padding: 0; border: 0; background: none;
  box-shadow: none; backdrop-filter: none; white-space: nowrap;
  font: 600 12px/1.15 "Geist", system-ui, sans-serif; letter-spacing: .02em;
  color: var(--muted);
  transition: transform .52s cubic-bezier(.22,.9,.3,1), opacity .5s ease, color .3s,
              width .35s cubic-bezier(.22,.9,.3,1), height .35s cubic-bezier(.22,.9,.3,1);
}
body:is(.modo-unico, .modo-orbital) .fac.fixo i {
  position: absolute; inset: 0; width: auto; height: auto; border-radius: 50%;
  background: rgba(var(--superficie-rgb),.94);
  border: 1.5px solid rgba(var(--tinta-rgb),.35);
  box-shadow: 0 4px 14px rgba(var(--tinta-rgb),.10);
  transition: box-shadow .3s, background .3s, border-color .3s, transform .35s cubic-bezier(.22,.9,.3,1);
}
/* pista de cor em repouso: um ponto do domínio, sem brilho */
body:is(.modo-unico, .modo-orbital) .fac.fixo i::after {
  content: ""; position: absolute; inset: 30%; border-radius: 50%;
  background: var(--cor, var(--tinta)); opacity: .5; transition: opacity .3s;
}
/* o rótulo, do lado de fora do arco */
body:is(.modo-unico, .modo-orbital) .fac.fixo b {
  position: absolute; font-weight: 600; white-space: nowrap; pointer-events: none;
  transition: color .3s;
}
body:is(.modo-unico, .modo-orbital) .fac.fixo b,
body:is(.modo-unico, .modo-orbital) .fac.fixo[data-rotulo="cima"] b  { left: 50%; bottom: calc(100% + 7px); top: auto; right: auto; transform: translateX(-50%); }
body:is(.modo-unico, .modo-orbital) .fac.fixo[data-rotulo="baixo"] b { left: 50%; top: calc(100% + 7px); bottom: auto; right: auto; transform: translateX(-50%); }
body:is(.modo-unico, .modo-orbital) .fac.fixo[data-rotulo="esq"] b   { right: calc(100% + 9px); top: 50%; left: auto; bottom: auto; transform: translateY(-50%); }
body:is(.modo-unico, .modo-orbital) .fac.fixo[data-rotulo="dir"] b   { left: calc(100% + 9px); top: 50%; right: auto; bottom: auto; transform: translateY(-50%); }
/* ---- D7: com o painel de conversa aberto, o rótulo da esquerda sobe -----
   MEDIDO em 1280×800 (captura p1/r1280/02-d-5s.png): "Faturamento de
   Veículos" ia de x=259 a x=408 e a borda do painel está em x=333 — o
   rótulo atravessava a conversa. O chip fica dentro do palco; quem sai é o
   TEXTO, que num `data-rotulo="esq"` cresce para fora a partir da borda
   esquerda do disco (26px + 9px + a largura inteira do rótulo).
   Virando para cima, o rótulo passa a crescer para os DOIS lados a partir
   do centro do chip: o alcance à esquerda cai de 35 + largura para
   largura/2 (≈110px a menos no caso medido), e o teto de largura garante o
   resto. Vale só com `.com-chat` — sem painel o arco tem lugar de sobra e o
   rótulo radial é o que a spec 011 §2 pede.
   LIMITE HONESTO: a POSIÇÃO do chip continua sendo do presenca.js
   (`ladoDoRotulo`, presenca.js:1003, e `posicoesFixas`, :1014). Se um chip
   algum dia pousar a menos de meia-largura de rótulo da borda do painel,
   só ele pode consertar — o CSS aqui já terá dado o que tinha. */
body:is(.modo-unico, .modo-orbital) .stage.com-chat .fac.fixo[data-rotulo="esq"] b {
  right: auto; left: 50%; top: auto; bottom: calc(100% + 7px); transform: translateX(-50%);
  max-width: min(148px, 16cqw); overflow: hidden; text-overflow: ellipsis;
}
/* compartilhada: menor, tracejada, secundária */
body:is(.modo-unico, .modo-orbital) .fac.fixo.compart { width: 40px; height: 40px; font-size: 10.5px; }
body:is(.modo-unico, .modo-orbital) .fac.fixo.compart i {
  border-style: dashed; border-color: rgba(var(--tinta-rgb),.35);
  background: rgba(var(--superficie-rgb),.85); box-shadow: none;
}
body:is(.modo-unico, .modo-orbital) .fac.fixo.compart i::after { opacity: .3; }
/* já consultada neste turno: preenchida, sem pulso */
body:is(.modo-unico, .modo-orbital) .fac.fixo.usado:not(.ativo) { color: var(--tinta); }
body:is(.modo-unico, .modo-orbital) .fac.fixo.usado:not(.ativo) i {
  border-color: var(--cor, var(--tinta));
  background: radial-gradient(circle at 35% 30%, rgba(var(--superficie-rgb),.55), var(--cor, var(--tinta)) 70%);
  box-shadow: 0 6px 16px rgba(var(--tinta-rgb),.20);
}
body:is(.modo-unico, .modo-orbital) .fac.fixo.usado:not(.ativo) i::after { opacity: 0; }
/* consultando AGORA: anel, brilho, escala e pulso radial */
body:is(.modo-unico, .modo-orbital) .fac.fixo.ativo { width: 64px; height: 64px; color: var(--tinta); background: none; box-shadow: none; border: 0; }
body:is(.modo-unico, .modo-orbital) .fac.fixo.ativo.compart { width: 48px; height: 48px; }
body:is(.modo-unico, .modo-orbital) .fac.fixo.ativo i {
  border-color: var(--cor, var(--tinta));
  background: radial-gradient(circle at 35% 30%, rgba(var(--superficie-rgb),1), var(--cor, var(--tinta)) 78%);
  box-shadow: 0 0 0 3px rgba(var(--superficie-rgb),.95), 0 0 0 5px var(--cor, var(--tinta)),
              0 0 28px var(--cor, var(--tinta)), 0 10px 28px rgba(var(--tinta-rgb),.28);
  animation: orbiRespira 1.1s ease-in-out infinite;
}
body:is(.modo-unico, .modo-orbital) .fac.fixo.ativo i::after { opacity: 0; }
body:is(.modo-unico, .modo-orbital) .fac.fixo.pulso i::before {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid var(--cor, var(--tinta)); opacity: 0;
  animation: orbiPulso .9s ease-out 1;
}
/* Fios das áreas: saem do CENTRO do orbi grande até o satélite (presenca.js
   desenha o caminho). Discretos em repouso; na cor do agente e mais grossos
   quando a área está sendo consultada. */
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.fixo { stroke-width: 1.2; opacity: .8; }
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.fixo.ativo {
  stroke-width: 2.6; opacity: 1; filter: drop-shadow(0 0 6px var(--cor, var(--tinta)));
}

/* ---- SISTEMAS: o 2º anel ---------------------------------------------------
   Para cada área, os sistemas que a API entrega, em pílulas de 10px sobre o
   raio da área (`--sis-raio`), em leque. Apagadas em repouso (.55) e acesas
   (1) quando a área está `ativo`/`usado` — o presenca.js põe `aceso` na
   pílula e no fio fino satélite→pílula. Somem abaixo de 700px de altura:
   primeiro sacrifício da spec. */
body:is(.modo-unico, .modo-orbital) .sis {
  position: absolute; transform: translate(-50%, -50%);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap; pointer-events: none;
  font: 500 10px/1.2 var(--mono); letter-spacing: .04em; color: var(--tinta-forte);
  background: rgba(var(--superficie-rgb),.92); border: 1px solid rgba(var(--tinta-rgb),.22);
  opacity: .55; transition: opacity .5s ease, border-color .3s, color .3s;
}
body:is(.modo-unico, .modo-orbital) .sis.aceso { opacity: 1; border-color: var(--cor, var(--tinta)); color: var(--tinta); }
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.sis {
  stroke: rgba(var(--tinta-rgb),.22); stroke-width: 1; stroke-dasharray: 2 4;
  stroke-dashoffset: 0; opacity: .7; transition: stroke .3s, opacity .3s;
}
body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.sis.aceso { stroke: var(--cor, var(--tinta)); opacity: 1; }
@media (max-height: 700px) {
  body:is(.modo-unico, .modo-orbital) .sis, body:is(.modo-unico, .modo-orbital) .decomp-fios .fio.sis { display: none; }
}
@keyframes orbiRespira { 0%,100% { transform: scale(1); } 50% { transform: scale(1.06); } }
@keyframes orbiPulso { 0% { transform: scale(1); opacity: .9; } 100% { transform: scale(2.4); opacity: 0; } }
@keyframes facPisca { 0% { box-shadow: 0 0 0 0 rgba(var(--tinta-rgb),.30); } 100% { box-shadow: 0 0 0 12px rgba(var(--tinta-rgb),0); } }

/* Enquanto um domínio se decompõe, os outros saem de cena. */
.node, .link { transition: opacity .4s ease; }
body.decompondo .node:not(.foco) { opacity: .16; }
body.decompondo .node.foco .badge { opacity: 0; transition: opacity .3s; }
body.decompondo .link:not(.ativo) { opacity: .14; }

/* ===== Viz: as bolinhas roxas do insight ===== */
.viz .v-val { fill: var(--tinta); font-family: var(--fonte-texto, "Geist", system-ui, sans-serif); font-variant-numeric: tabular-nums; font-size: 10px; }
.viz .v-cat, .viz .v-nota { fill: var(--muted); font-family: var(--fonte-texto, "Geist", system-ui, sans-serif); }
.viz-title { color: var(--muted); font-family: var(--mono); }

/* ===== Memória: overlay e painéis ===== */
.mem-overlay { background: var(--fundo-overlay); }
.mem-title { color: var(--tinta-forte); } .mem-title span { color: var(--dim); }
.mem-x { background: rgba(var(--superficie-rgb),.9); border-color: rgba(var(--tinta-rgb),.2); color: var(--tinta); }
.mem-x:hover { background: var(--superficie); border-color: var(--tinta); }
.mem-legenda, .mem-explica, .mem-detalhe, .bloom-bar {
  background: var(--vidro); border-color: var(--vidro-borda);
  box-shadow: var(--sombra); backdrop-filter: blur(14px);
}
.mem-legenda span { color: var(--muted); }
.mem-ex-tit, .bloom-tit { color: var(--tinta); }
.mem-ex-vazio, .mem-explica .ex-meta { color: var(--dim); }
.mem-explica .ex-txt, .mem-det-txt { color: var(--txt); }
.bloom-act { color: var(--tinta); background: rgba(var(--tinta-rgb),.07); border-color: rgba(var(--tinta-rgb),.18); }
.bloom-act:hover { background: rgba(var(--tinta-rgb),.16); border-color: var(--tinta); }
.bloom-act.x { color: var(--dim); }
.mem-dica, .mem-main-dica { background: var(--dica-fundo); color: var(--dica-txt); border: 1px solid var(--dica-borda); }
body.bloom #memMain { background: var(--fundo-bloom); }

/* ===== Movimento reduzido: nada pisca, nada gira ===== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    /* Zerar só a duração não basta: o leque das faculdades é escalonado por
       `transition-delay`, e escalonamento também é movimento. Sem isto os
       chips apareciam um a um mesmo com animação desligada. */
    transition-delay: 0ms !important; animation-delay: 0ms !important;
  }
}

@media (max-width: 1200px) {
  .mono-canto { display: none; }
  .stage .palco > * { --fig: min(620px, 84cqh, 96cqw); }   /* .stage para vencer a regra-base, que vem depois no arquivo */
}

/* ═══════════════════════════════════════════════════════════════════════════
   SUGESTÕES — uma pergunta por vez, logo abaixo da figura.

   Uma grade de cartões obrigava o olho a varrer e escolher; uma linha só
   oferece. Peso visual deliberadamente baixo: isto é apoio, não chamada para
   ação. A figura continua sendo a composição.
   ═══════════════════════════════════════════════════════════════════════════ */
.sugestoes {
  position: absolute; left: 50%; bottom: 13.5%; transform: translateX(-50%);
  max-width: 92vw; z-index: 4;
  opacity: 0; pointer-events: none;
}
/* A transição só entra DEPOIS da primeira aparição: as sugestões chegam com a
   resposta da API, e um fade na estreia só faz a tela piscar. */
.sugestoes.animado { transition: opacity .45s ease; }
body.sugerindo .sugestoes { opacity: 1; pointer-events: auto; }

.sug {
  display: inline-flex; align-items: baseline; gap: 11px; white-space: nowrap;
  max-width: 92vw; padding: 8px 18px; border-radius: 999px; cursor: pointer;
  background: transparent; border: 1px solid transparent; font-family: inherit;
  transition: opacity .26s ease, transform .26s ease, background .2s, border-color .2s;
}
.sug em {
  font: 500 8.5px var(--mono); font-style: normal; letter-spacing: .22em;
  text-transform: uppercase; color: var(--dim); flex: 0 0 auto;
}
.sug b {
  font: 500 14px "Geist", system-ui, sans-serif; color: var(--tinta-forte);
  overflow: hidden; text-overflow: ellipsis;
}
.sug:hover { background: rgba(var(--superficie-rgb),.78); border-color: rgba(var(--tinta-rgb),.18); }
.sug:hover b { color: var(--tinta); }
/* Cruzada = fan-out paralelo: um ponto da única cor secundária, nada mais. */
.sug[data-cruzada="1"] em::after {
  content: ""; display: inline-block; width: 4px; height: 4px; border-radius: 50%;
  background: var(--acento); margin-left: 7px; vertical-align: middle;
}
.sug.trocando { opacity: 0; transform: translateY(5px); }

/* ============================================================================
   Elementos que faltavam para o HITL e a sessão serem operáveis.
   ========================================================================= */

/* ---- Human gate: o plano precisa ser LIDO antes de ser aprovado ----------
   O painel só tinha a pergunta e dois botões; o plano vinha no evento e era
   descartado. Aprovar sem ver o que se aprova é HITL decorativo. */
.gate-cab { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.gate-topico {
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--tinta-media);
}
.gate-plano {
  margin: 10px 0 12px;
  max-height: 34vh; overflow-y: auto;
  padding: 12px 14px;
  background: var(--papel);
  border: 1px solid var(--vidro-borda);
  border-radius: 10px;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 12px; line-height: 1.55; color: var(--tinta-forte);
  white-space: pre-wrap; word-break: break-word;
  text-align: left;
}
.gate-acoes { display: flex; gap: 10px; justify-content: center; }
.gate-acoes .pill[disabled] { opacity: .4; cursor: not-allowed; }

/* ---- Resposta em texto ---------------------------------------------------
   Persistente e independente do áudio: se o TTS falhar, a resposta continua
   na tela em vez de se perder no silêncio. */
.resposta {
  max-width: 62ch; margin: 14px auto 0;
  font-size: 15px; line-height: 1.6;
  color: var(--tinta-forte);
  text-align: left; text-wrap: pretty;
}
.resposta[hidden] { display: none; }

/* ---- Controles discretos de sessão (sair · reiniciar) -------------------- */
.pill-min {
  border: 1px solid var(--vidro-borda);
  background: transparent;
  color: var(--tinta-media);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer; transition: background .15s, color .15s, border-color .15s;
}
.pill-min:hover { background: var(--tinta); border-color: var(--tinta); color: var(--sobre-preenchimento); }
.pill-min:focus-visible { outline: 2px solid var(--tinta); outline-offset: 2px; }

/* ---- Voz contínua (canal realtime) --------------------------------------
   A linha "voz contínua ligada/indisponível" saiu do dock (spec 012, CA-4):
   o estado do microfone já diz. O `#vozEstado` não existe mais no HTML e o
   voz.js só escreve nele se existir. */

/* ============================================================================
   FRONT-16 — separação estrutural entre conversa, gráfico e figura.

   O defeito: `.viz` e `.dock` (que continha a resposta) eram AMBOS
   `position:absolute` no mesmo `.stage`, presos por `bottom:8%` e `bottom:2%`.
   Não colidiam por sorte de altura. Bastava uma resposta mais longa — ou uma
   tela mais estreita — para o texto pousar em cima dos rótulos do gráfico, que
   são dado (nº de consultores, margem, fim de contrato) e não decoração.

   A correção é de LAYOUT, não de z-index: o palco, o console e o dock são
   linhas de grid. Uma linha não invade a outra por construção, então nem
   resposta de vinte linhas nem viewport estreito trazem o problema de volta.
   ========================================================================= */
/* ============================================================================
   FRONT-20 — o humanoide fica com o CENTRO DA TELA e a conversa vai para a
   esquerda como painel opcional.

   TENTEI a calha ESPELHADA primeiro — uma terceira coluna vazia da largura do
   painel, para o palco cair exatamente no meio da viewport e "centro da tela"
   ser literal. Medi e não fecha: em 1512px o painel come 393 de cada lado e
   sobram 726 para o palco, enquanto a figura sozinha tem 635. Não sobra
   largura nenhuma para a constelação, e os nós entravam POR CIMA da figura.

   O anel é largo porque os nós carregam rótulo ("Agentix Research", "mercado ·
   web · tendências"), então quem manda no tamanho é o texto, não o raio. Não
   há como espremer os dois numa faixa de 726px.

   Então a calha espelhada saiu: o palco fica com a largura que sobra e a
   figura centra NELE. O custo é honesto e é este — com o painel aberto o
   centro da figura fica ~196px à direita do centro exato da viewport. O que se
   compra com isso é a constelação continuar existindo, que também foi pedido.
   Com o painel fechado — a primeira tela, e a maior parte do tempo — a figura
   está no centro exato.
   ========================================================================= */
/* ---- GEOMETRIA DOS CANTOS (medida, não chutada) --------------------------
   Três números que mais de um elemento precisa saber, e que estavam só na
   cabeça de quem escreveu a regra. Ficam no `:root` porque o `.roster` e o
   `.hud` são IRMÃOS do `.stage` (ver index.html): nada declarado em `.stage`
   chega até eles.

     --calha-larg  a largura do painel de conversa aberto. Era um `clamp`
                   escrito só em `.stage.com-chat`; o roster precisa do mesmo
                   número para sair de cima do painel (D5) e o dock para
                   descontá-lo da própria largura (D4).
     --hud-larg    a largura do HUD (tokens · custo · turnos · sessão) medida
                   na página real em 1280×800: 346px (x 916→1262). É reserva,
                   não profecia: se o HUD crescer, este número cresce junto.
     --canto-ar    o ar mínimo entre o dock e um canto fixo. */
:root {
  --calha-larg: clamp(300px, 26vw, 400px);
  --hud-larg: 346px;
  --canto-ar: 24px;
}

.stage {
  --calha: 0px;
  display: grid;
  grid-template-columns: var(--calha) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  grid-template-areas:
    "console palco"
    "console dock";
  min-height: 0;
}
.stage.com-chat { --calha: var(--calha-larg); }

/* O palco é quem ancora os elementos absolutos (figura, nós, badges). Antes
   ancoravam no `.stage` inteiro e podiam descer sobre o console. */
/* ============================================================================
   O PALCO É CONTÊINER DE CONSULTA, e isto é o conserto de uma armadilha.

   A figura era dimensionada com `68vh` — altura da JANELA. Funcionou enquanto
   o palco era a única linha alta do stage. No instante em que o FRONT-24
   acrescentou a faixa do gráfico, a janela continuou do mesmo tamanho e a
   ALTURA DISPONÍVEL PARA A FIGURA encolheu ~220px. `vh` deixou de descrever o
   espaço que ela realmente tem, e a cabeça voltaria a sair por cima — o mesmo
   defeito do "quebrando acima", pela mesma porta, com outra roupa.

   `container-type: size` faz o palco ser a referência de `cqh`/`cqw`. O número
   passa a vir de QUEM DE FATO LIMITA: se amanhã outra linha disputar altura, a
   figura encolhe sozinha, sem ninguém lembrar de ajustar constante nenhuma.

   `--fig` é declarado nos FILHOS e não aqui de propósito: unidade de consulta
   usada numa propriedade do próprio contêiner resolve contra o contêiner
   ANCESTRAL, não contra ele mesmo — ficaria medindo a coisa errada em
   silêncio. Custom property herda, então `.orb-core` passa para `.presenca`.
   ========================================================================= */
.palco {
  grid-area: palco; position: relative; min-height: 0;
  container-type: size;
}
/* 84cqh porque a proporção é 144/157: a altura renderizada é a largura × 1,09,
   e 0,84 × 1,09 = 0,92 — sobra ~8% de folga na linha. */
.palco > * { --fig: min(640px, 84cqh, 92cqw); }

/* Os canvases e os fios trabalham em coordenadas de VIEWPORT
   (`innerWidth/innerHeight`, `getBoundingClientRect`), então precisam cobrir a
   viewport — não o palco. Presos ao palco, o sistema de coordenadas deixaria de
   bater e os fios sairiam do lugar. */
.wires, .links { position: fixed; inset: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 1; }
.mem-main { position: fixed; inset: 0; width: 100vw; height: 100vh; pointer-events: none; }

/* ---- O painel de conversa ------------------------------------------------
   Mora na calha esquerda, ocupa as duas linhas (palco e dock) e rola por
   dentro. Fora da figura: não há mais texto por cima do humanoide, então não
   há véu, cartão, nem máscara para inventar — foi a lição das três tentativas
   de véu do FRONT-17, e aqui ela se paga sozinha, porque o problema saiu de
   cena em vez de ser disfarçado. */
.console {
  grid-area: console;
  position: relative;
  z-index: 3;
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  overflow: hidden;
  padding: 14px 16px 12px;
  border-right: 1px solid var(--vidro-borda);
  background: linear-gradient(90deg, rgba(var(--superficie-rgb),.58), rgba(var(--superficie-rgb),.30));
  backdrop-filter: blur(10px);
}
.console[hidden] { display: none; }

.console-cab { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.console-tit {
  font-family: var(--mono); font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--tinta-media);
}
.console-x {
  background: none; border: 0; cursor: pointer; padding: 2px 6px;
  color: var(--dim); font-size: 13px; line-height: 1; border-radius: 6px;
}
.console-x:hover { color: var(--tinta); background: rgba(var(--tinta-rgb),.08); }
.console-x:focus-visible { outline: 2px solid var(--tinta); outline-offset: 1px; }

/* A conversa preenche o painel. Sem teto em vh: quem limita agora é a altura
   da coluna, e a coluna é a tela inteira. */
.conversa {
  display: flex; flex-direction: column; gap: 8px;
  flex: 1 1 auto; min-width: 0; min-height: 0;
  padding: 0;
}

/* Barra de rolagem discreta, na caixa que de fato rola. */
.turnos::-webkit-scrollbar { width: 6px; }
.turnos::-webkit-scrollbar-thumb { background: rgba(var(--tinta-rgb),.16); border-radius: 999px; }
.turnos::-webkit-scrollbar-track { background: transparent; }
.turnos { scrollbar-width: thin; scrollbar-color: rgba(var(--tinta-rgb),.16) transparent; }

.pergunta {
  margin: 0;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 11px; letter-spacing: .04em;
  color: var(--tinta-media);
}
.pergunta::before { content: "› "; opacity: .6; }
.pergunta[hidden] { display: none; }

/* Contraste real sobre o fundo claro: o cinza anterior já estava no limite. */
.resposta {
  max-width: none; margin: 0;
  font-size: 14.5px; line-height: 1.62;
  color: var(--tinta-forte);
  text-align: left; text-wrap: pretty;
  white-space: pre-wrap; word-break: break-word;
}

/* ---- Viz: dentro do turno, na largura inteira da coluna ------------------
   `figure` dentro do `<li>`. Não tem posição própria nem largura própria: é
   um bloco do texto, como uma figura de artigo. */
.turno .viz {
  margin: 4px 0 0; padding: 0;
  width: 100%; min-width: 0;
}
.turno .viz[hidden] { display: none; }   /* turno sem gráfico não deixa buraco */
.turno .viz-plot { height: clamp(132px, 19vh, 186px); overflow: hidden; }
/* Os rótulos de categoria são desenhados a 9px do pé do viewBox. Sem folga
   embaixo eles encostam na borda de recorte da lista e ficam cortados ao meio
   — vi acontecer com o cluster, que tem o rótulo mais baixo dos quatro. */
.turnos { padding-bottom: 6px; }
.turno:last-child .viz { margin-bottom: 2px; }
/* Alinhado à esquerda com a pergunta e a resposta, não centrado: numa coluna
   só, tudo pendura no mesmo eixo. */
.turno .viz-title { text-align: left; margin: 0 0 2px; }
.turno .viz-regua { text-align: left; margin: 0 0 2px; }

/* ---- Dock: linha própria, sem sobrepor nada ------------------------------ */
.dock {
  grid-area: dock;
  position: relative; left: auto; bottom: auto; transform: none;
  z-index: 3;
  margin: 0 auto; padding-bottom: 10px;
  /* D4 — o teto de largura do dock, calculado aqui porque é aqui que
     `--calha` (declarada no `.stage`) chega por herança. Ver a conta no
     bloco "D4" mais abaixo, onde `.narracao` e `.sugestoes` a consomem. */
  --dock-max: max(120px, min(560px, calc(100vw - 36px - 2 * (var(--hud-larg) + var(--canto-ar)) - var(--calha, 0px))));
}


/* ---- Conversa como lista de turnos (preparada para o FRONT-09) ----------- */
.turnos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.turno { display: flex; flex-direction: column; gap: 6px; }
.turno + .turno { padding-top: 12px; border-top: 1px solid var(--vidro-borda); }

/* ---- Fontes externas do turno (ADR-0019) ---------------------------------
   Procedência é RODAPÉ do turno, não manchete: mono, um corpo abaixo da
   resposta e na cor de apoio — presente para quem for conferir, silencioso
   para quem está lendo o texto. Nenhum hex novo: tudo por token da pele (a
   Volks sobrescreve `--muted`/`--tinta`, e um hex copiado a ignoraria). */
.turno-fontes { margin-top: 8px; font: 400 12px/1.45 var(--mono); color: var(--muted); }
.turno-fontes-tit {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: .16em;
  color: var(--dim); margin-bottom: 3px;
}
.turno-fontes ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.turno-fontes li { min-width: 0; overflow-wrap: anywhere; }
.turno-fontes a { color: var(--tinta); text-decoration: underline; text-underline-offset: 2px; }
.turno-fontes a:hover { color: var(--tinta-media); }
/* "parcial": o payload veio cortado no teto de 20 KB. Pílula pequena, nunca
   alerta — é ressalva de leitura, não erro. */
.turno-fonte-parcial {
  display: inline-block; padding: 0 6px; border-radius: 999px;
  background: var(--lavanda); color: var(--tinta-media);
  font-size: 9.5px; letter-spacing: .08em;
}
.conversa-entrada { margin-top: 10px; }
.conversa-entrada[hidden] { display: none; }

/* ---- Entrada de texto e histórico (FRONT-09) ----------------------------- */
.conversa-entrada { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.campo-pergunta {
  flex: 1 1 auto; min-width: 0;
  padding: 9px 13px;
  border: 1px solid var(--vidro-borda);
  border-radius: 999px;
  background: rgba(var(--superficie-rgb),.85);
  color: var(--tinta-forte);
  font-family: inherit; font-size: 13.5px;
}
.campo-pergunta::placeholder { color: var(--tinta-clara); }
.campo-pergunta:focus-visible { outline: 2px solid var(--tinta); outline-offset: 1px; }
.campo-pergunta:disabled { opacity: .55; cursor: not-allowed; }
.enviar { flex: 0 0 auto; }

/* Aviso de poda: histórico que some calado é pior que histórico curto. */
.turnos-podados {
  list-style: none;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--tinta-clara); text-align: center;
  padding-bottom: 6px; border-bottom: 1px dashed var(--vidro-borda);
}

/* ---- Correções vistas só no render (FRONT-09) ---------------------------
   Duas coisas que a medição de colisão não pega, porque não são sobreposição
   de retângulos: */

/* 1. O campo de entrada rolava JUNTO com a lista e saía de vista assim que o
      histórico crescia — a via de entrada desaparecia exatamente quando havia
      mais conversa. Quem rola é a lista; o rodapé fica ancorado. */
.conversa { overflow: hidden; }                 /* o scroll desce um nível */
.turnos {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding-right: 4px;                            /* espaço para a barra */
}
.conversa-entrada { flex: 0 0 auto; }

/* 2. A linha de sugestão passava POR TRÁS do microfone. O dock é uma coluna
      flex, então não era sobreposição de layout: o orbe tem 58 px e a linha
      centralizada cruzava por baixo dele em telas largas. Limitar a largura
      resolve sem tirar a sugestão do centro. */
/* A sugestão era `position:absolute; bottom:13.5%` — ancorada no stage, e com
   o stage virando grid ela passou a cair POR CIMA do microfone. Como agora ela
   vive dentro do dock, o lugar certo é o fluxo: a coluna flex a posiciona, e
   não há mais número de porcentagem para desalinhar. */
.sugestoes {
  position: static; left: auto; bottom: auto; transform: none;
  max-width: min(62ch, 46vw); margin: 0 auto; text-align: center;
}

/* ---- D4: o dock não invade o HUD (nem o roster) --------------------------
   MEDIDO em 1280×800 durante o turno: `.dock` em x 535→1077 e `.hud` em
   x 916→1262 — 161px × 51px de sobreposição. A causa não é o dock em si, é
   o que ele CONTÉM: a narração (até quatro linhas de log em mono) e a linha
   de sugestões são as únicas caixas largas, e o dock é CENTRADO na sua área
   de grid. Cada pixel de texto anda meio pixel para cada lado, até entrar
   embaixo do HUD — que é `fixed` na viewport (`right: 18px`) e não sabe que
   o grid do stage existe.

   A conta, com a origem na VIEWPORT (é onde o HUD vive):
     centro do dock  = --calha + (100vw − --calha)/2 = 50vw + --calha/2
     borda do HUD    = 100vw − 18px − --hud-larg
     meia-largura    = borda − centro − --canto-ar
     largura máxima  = 100vw − 36px − 2·(--hud-larg + --canto-ar) − --calha
   1280×800 sem painel → 504px · com painel (--calha = 333px) → 171px ·
   1920×1080 sem painel → 1144px, e aí quem manda é o teto de 560px.
   Pelo lado esquerdo a folga é sempre maior (o roster é estreito e, com o
   painel aberto, já foi deslocado pela D5 para depois dele), então é o lado
   do HUD que define o teto.

   O texto que não couber some no FIM DA LINHA: cada linha da narração já
   tem `overflow: hidden; text-overflow: ellipsis` (regra `.narracao > *`),
   e a sugestão é `nowrap` com elipse. Nada passa por cima do HUD.

   O PISO de 120px é o limite honesto desta conta: abaixo de ~1100px COM o
   painel aberto ela dá negativo — não existe largura para o painel, a
   narração e o HUD ao mesmo tempo, e um `max-width` negativo seria
   descartado pelo navegador em silêncio, que é pior. Nessa faixa a
   constelação já sai de cena (regra de ≤1100px) e a narração fica com uma
   frase curta; a sobreposição volta a ser possível e é uma escolha
   declarada, não um esquecimento. */
.narracao { max-width: var(--dock-max); }
.sugestoes { max-width: min(62ch, 46vw, var(--dock-max)); }
.sug { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* ---- D8: o dock tem ORÇAMENTO de altura, e o cartão vive do resto -------
   MEDIDO em 1280×800 com o painel aberto e gráfico na tela (captura
   p1/sse1280/01-c-fim.png): quatro linhas de narração (~20px cada) mais a
   linha de sugestão mais o mic levaram o dock a ~170px. O dock é uma LINHA
   do grid do stage (`grid-template-rows: minmax(0,1fr) auto`), então ele
   não cobre o palco — ele o ENCOLHE: com 170px de dock o palco cai para
   566, a faixa C para 204, e o `.viz-slot` passou 28px do cartão, levando o
   eixo x ("9/8", "11/8") e a legenda para fora do `overflow: hidden`.

   O conserto é no que faz o dock crescer, porque ele cresce com CONTEÚDO
   TRANSITÓRIO enquanto o cartão é a resposta:
     · a narração mostra no máximo DUAS linhas no modo orbital — as duas
       ÚLTIMAS, que são a cadeia de pensamento agora; as anteriores saem do
       layout (não só da vista), senão a altura continua lá;
     · a linha de sugestão some enquanto há gráfico no palco e durante o
       turno: ela convida a PERGUNTAR, e não se convida a perguntar quem
       está lendo a resposta.
   Orçamento resultante em 1280×800, modo orbital, com gráfico:
     narração 2×16,8 + 3 de folga = 36,6 · folga 4 · mic 52 · rodapé 6
     ⇒ dock ≈ 99px  ⇒  palco = 800 − 64 (header) − 99 = 637
     faixa A/B 64% = 407,7 ≥ 367,5 da pilha do orbi com o piso da D9
     faixa C   36% = 229,3 ≥ 16 + 60,2 + 136 = 212,2 (piso do plot, D1)
   `nth-last-child` E `max-height`: o app.js escreve cada linha como um
   `<div class="linha">` (`setNarracao`, app.js:26-37) e o seletor dá conta
   disso com precisão; o `max-height` é o cinto para o texto solto, que não
   tem filho nenhum para contar. */
body.modo-orbital .narracao > :nth-last-child(n+3) { display: none; }
body.modo-orbital .narracao {
  justify-content: flex-end;
  max-height: calc(2 * 1.4em + 3px); overflow: hidden;
}
body:is(.modo-unico, .modo-orbital) .stage:has(.palco-viz.insight) .sugestoes,
body:is(.modo-unico, .modo-orbital) .stage.estado-thinking .sugestoes { display: none; }


/* ---- O recuo do palco não voltou, e a razão é medida --------------------
   O FRONT-17 esmaecia `.orb-core` e os fios enquanto houvesse conversa
   (`body:has(.turnos:not(:empty))`). A razão era uma só: o texto ficava POR
   CIMA da figura e os dois disputavam a mesma região.

   Com o chat na calha esquerda não há mais nada por cima da figura — a razão
   deixou de existir, e a regra saiu com ela. Mantê-la seria contradição
   direta: o FRONT-20 subiu a densidade da figura exatamente para ela ser o
   objeto de MAIOR contraste da tela, e esmaecê-la a 50% desfaria isso
   justamente quando há conversa, que é quando ela mais precisa estar presente.

   Regra que fica: recuo resolve DISPUTA POR REGIÃO. Sem disputa, sem recuo. */

/* ---- A constelação, em % do PALCO ---------------------------------------
   As posições viviam no `style=` de cada `<div>`, em `vw`: mediam a VIEWPORT e
   ignoravam a largura real do palco. Com o painel de conversa aberto o palco
   encolhe e o anel continuava do mesmo tamanho — os nós da esquerda passavam
   por baixo da conversa.

   Em porcentagem do palco o anel reequilibra sozinho quando o painel abre e
   fecha: sem classe de estado, sem número novo, sem segundo caminho para
   manter em sincronia. O `min(px, %)` preserva o teto em telas largas, que era
   o motivo de existir do `min` original. */
/* A posição de cada nó é calculada em app.js (`_layoutConstelacao`) e entra
   como custom property: `--lado` (−1 esquerda, 1 direita), `--nx` (fração de
   `--fig`, 0.62 nas pontas a 0.76 no meio do arco) e `--ny` (px em torno de
   36% do palco). Vale para qualquer quantidade de especialistas. */
.node {
  left: calc(50% + var(--lado, 1) * var(--fig) * var(--nx, 0.7) * var(--nx-escala, 1));
  top: calc(36% + var(--ny, 0px));
}
.constelacao { display: contents; }

/* ---- MODO DE UM ESPECIALISTA ---------------------------------------------
   A composição do palco com um especialista só (spec 011 v3: o orbi grande
   da marca, áreas em arco por cima, gráfico embaixo) mora mais abaixo, em
   "SPEC 011 v3 — O PALCO DO ORBI ÚNICO". Fica
   depois das regras de `.com-chat` e da consulta de ≤1100px de propósito:
   elas também declaram `--fig` com a mesma especificidade, e o que vale é
   a ordem no arquivo. */

/* Com o painel aberto o palco cai de 1512 para 1119: é a mesma escassez de
   largura da regra de ≤1200px, então recebe o mesmo tratamento. O painel mono
   sai (ele já sai em tela estreita) e o anel encolhe.

   Medi errado da primeira vez: testei colisão do ORBE e dei tudo por resolvido.
   Quem passa da borda é o `.badge` do taxímetro, que fica FORA do orbe — os da
   esquerda sumiam atrás do painel e os da direita eram cortados pela viewport.
   Nó não é o círculo; é o círculo mais o rótulo mais o chip. */
.stage.com-chat .mono-canto { display: none; }
.stage.com-chat .node { --nx-escala: 0.9; }
/* Com o anel mais apertado, a figura cede um pouco para não encostar. */
.stage.com-chat .palco > * { --fig: min(560px, 80cqh, 88cqw); }

/* ---- D5: o roster sai de cima do painel de conversa ----------------------
   O roster é `fixed` em `left: 18px; bottom: 16px` — coordenadas de
   VIEWPORT, escolhidas quando o canto inferior esquerdo era fundo vazio.
   Com o painel aberto aquele canto é o CAMPO DE TEXTO da conversa, e a
   pílula "TRUCK BOARD AI ASSISTENT · TIME" pousa exatamente em cima dele (captura
   p1/r1280/04-c-fim.png): o usuário clica para escrever e abre o roster.

   O roster é IRMÃO do `.stage` (index.html), então nada que o `.stage`
   declare chega nele — daí o `:has()` no ancestral comum e o
   `--calha-larg` no `:root`, que é a mesma largura que o painel usa.
   Ele anda para a direita do painel, com os mesmos 12px de ar que o painel
   já reserva; a coluna do palco continua livre porque a pílula recolhida
   mede ~190px e o orbi começa bem depois. */
.app:has(.stage.com-chat) .roster { left: calc(var(--calha-larg) + 12px); }

/* Abaixo de ~1100px o painel e a constelação não cabem juntos: medido em
   1024 e em 900, três nós ficavam por baixo da conversa e três saíam da tela.
   Espremer mais os dois entrega os dois ruins. A constelação sai — ela é a
   camada de contexto, e a mesma informação continua no roster e no texto do
   turno, enquanto a conversa é o que o CEO está lendo agora.

   Vale só com o painel ABERTO: fechado, a tela estreita continua mostrando a
   constelação inteira, como sempre mostrou. */
@media (max-width: 1100px) {
  /* `visibility` além de `opacity`: só opacidade deixa os nós invisíveis mas
     ainda presentes na geometria, e foi assim que a minha própria medição
     continuou acusando "2 fora da tela" numa tela onde não há nada para ver. */
  .stage.com-chat .node,
  .stage.com-chat .wires { opacity: 0; visibility: hidden; pointer-events: none; }
  .stage.com-chat.com-chat .palco > * { --fig: min(420px, 78cqh, 92cqw); }
}

/* Trava estrutural: o `0.74` foi escolhido medindo, mas o que de fato passava
   da borda não era o nó — era o `.badge` do taxímetro, que fica FORA do orbe e
   ia a 1530px numa viewport de 1512. Um rótulo mais longo (um domínio novo,
   outro idioma) reabre isso, e barra de rolagem horizontal nesta tela é defeito
   grave: ela desloca a composição inteira, que é justamente o que o Lucas
   pediu para não acontecer.

   `clip` e não `hidden`: `hidden` transformaria o `.app` em contêiner de
   rolagem e passaria a interceptar o scroll da conversa. */
.app { overflow-x: clip; }


/* ---- O gráfico é CAMADA, não linha (FRONT-25) — SÓ NA CONSTELAÇÃO --------
   (`body:not(.modo-unico)` e não `.stage:not(...)`: `setEstado()` reescreve a
   classe do stage a cada estado; a do body é estável.)
   No FRONT-24 ele era linha de grid e tirava altura do palco. Geometricamente
   impecável — e produziu a composição que o Lucas recusou: a figura encolhia
   de 546 para 375px para dar lugar, e o assunto principal virava o menor
   objeto da tela. Ele não quer negociação de espaço; quer PROFUNDIDADE.

   Com VÁRIOS especialistas o gráfico continua flutuando por cima e a figura
   NÃO MUDA de tamanho nem de lugar: os nós da constelação descem até ~77% do
   palco e não há faixa livre embaixo para um cartão em fluxo.

   Com UM especialista (spec 011) esta regra NÃO vale: lá o gráfico é cartão
   em fluxo, na faixa C, e não cobre nada — ver o bloco da 011 logo abaixo.
   Foi a captura de 07/09: o gráfico absoluto no rodapé cobria os orbis das
   áreas e a figura ficava atrás dele. Sobreposição deliberada com hierarquia
   é composição quando há UMA coisa atrás; com orbis, nó e figura disputando o
   mesmo rodapé, era só disputa. */
body:not(.modo-unico):not(.modo-orbital) .stage .palco-viz {
  position: absolute; left: 0; right: 0; bottom: 0;
  z-index: 4;                      /* à frente da figura e dos nós */
  pointer-events: none;
  width: min(1040px, 100%);
  margin: 0 auto;
  padding: 0 clamp(16px, 3vw, 40px) 6px;
}
.palco-viz[hidden] { display: none; }
.palco-viz .viz-title { text-align: center; opacity: 1; margin-bottom: 2px; }
.palco-viz .viz-regua { text-align: center; }
body:not(.modo-unico):not(.modo-orbital) .stage .palco-viz .viz-plot { height: clamp(190px, 26cqh, 260px); }
/* Legenda vazia RESERVA o espaço (invisível, não `display:none`): a altura
   do cartão e do `.viz-plot` tem de ser a mesma antes e depois do desenho,
   porque o app.js mede o `.viz-plot` para montar o viewBox (T22). */
.palco-viz .viz-legenda:empty, .palco-viz .viz-series:empty { visibility: hidden; }
.palco-viz .viz-legenda { min-height: 16px; }
/* A nota das premissas também aparece no gráfico arquivado no turno do painel,
   onde não há os tokens `--viz-fs-*` do palco. */
.viz-nota {
  font: 500 10px/1.3 var(--mono); font-style: italic; color: var(--dim);
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.palco-viz .viz-series { min-height: 14px; }
/* v4: até dois gráficos complementares, um por `.viz-slot`, lado a lado.
   `um` (app.js) ou palco estreito (< 1100px, consulta de contêiner): um só. */
.palco-viz { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 16px; }
.palco-viz .viz-slot { display: flex; flex-direction: column; gap: 4px; min-width: 0; min-height: 0; }
.palco-viz.um { grid-template-columns: minmax(0, 1fr); }
.palco-viz.um .viz-slot:nth-child(2) { display: none; }
/* slot sem SVG: invisível, mas COM layout — `display:none` zerava o
   `clientWidth/Height` que o app.js mede antes do primeiro desenho (T22) */
.palco-viz .viz-slot:has(> .viz-plot:empty) { visibility: hidden; }
.palco-viz .viz-plot { overflow: hidden; }   /* rótulo que sair do viewBox não sai do cartão */
@container (max-width: 1100px) {
  .palco-viz { grid-template-columns: minmax(0, 1fr); }
  .palco-viz .viz-slot:nth-child(2) { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   SPEC 011 v3 — O PALCO DO ORBI ÚNICO (modo de um especialista)

   Do desenho do Lucas (07/09, 13:49): UM orbi grande da marca no centro, as
   áreas do especialista como satélites em ARCO POR CIMA, cada área abrindo
   em leque para os seus sistemas, e o espaço do gráfico reservado embaixo.
   Não há figura separada: o canvas `#presenca` some e o nó É a presença.

   O que vive onde (origem no topo do palco):
     orbi grande   `.node:is(.unico, .executivo) .node-orb`, raio `--orbi-raio`; o rodapé
                   (nome, DECIDIR, tokens) ABAIXO do disco, nunca ao lado
     áreas         `.fac.fixo` sobre o arco de raio `--fac-raio` centrado no
                   orbi (presenca.js posiciona; aqui só o estilo e os estados)
     sistemas      `.sis`, pílulas sobre `--sis-raio` (somem < 700px de altura)
     gráfico       `#vizPalco`, faixa de 64% a 100%, INVISÍVEL sem gráfico

   CONTRATO com presenca.js. As folgas são px puros em `:root`, que é onde
   ele lê (`--fac-folga`, `--sis-folga`). O RAIO do orbi ele não lê do CSS —
   `--orbi-raio` carrega `cqh` e só resolve dentro do palco; ele mede o disco
   (`.node-orb`, `getBoundingClientRect().width / 2`). Daí:
     fac-raio = raio + --fac-folga · sis-raio = fac-raio + --sis-folga
   Rótulo da área do lado de fora do arco: o presenca.js diz o lado por
   `data-rotulo="cima|esq|dir|baixo"` no chip (padrão: cima). Sistema aceso:
   classe `aceso` na pílula e no fio (`.fio.sis`).

   POR QUE O RAIO É UMA CONTA e o centro não é um número fixo. A spec pedia
   centro a 46% e raio `min(110px, 15cqh, 14cqw)`. Em 1280×800 (palco
   ≈620px) isso põe o rodapé em 63–73% — em cima da faixa do gráfico — e os
   sistemas acima do topo. O que fecha é empilhar de cima para baixo:
     4% de ar · sistemas (10px de meia pílula + 64 de folga) · áreas (92 de
     folga) · o disco (2 × raio) · 14px · rodapé (≈44px) · 6px de folga
   até os 64% onde começa o gráfico. Logo o topo do disco fica em
   `4% + 166px` (124px sem sistemas) e o raio é o resto:
     raio = min(110px, 30cqh − 115px, 14cqw)     (30cqh − 94px sem sistemas)
   Resultado: 1280×800 → raio ≈71px (disco 142), centro ≈41% ·
   1440×900 → ≈101px (202), centro ≈41% · 1024×768 (sem sistemas) → ≈83px
   (166), centro ≈40%. Ordem de sacrifício da spec, por construção:
   sistemas somem (< 700px de altura), o gráfico cede até o piso (150/160),
   e só então o raio — porque ele é o resto.
   ═══════════════════════════════════════════════════════════════════════════ */
:root { --fac-folga: 92px; --sis-folga: 64px; }
/* `body.modo-unico` e não `.stage.modo-unico`: `setEstado()` (app.js)
   reescreve a classe do stage inteira a cada estado e a de `modo-unico`
   sumia no primeiro turno. O body ninguém reescreve. */
/* D9 — o `max(56px, …)` é o PISO do orbi, e ele existe porque a ordem de
   sacrifício da spec 011 §2 (sistemas → gráfico até o piso → raio) estava
   sendo executada ao contrário: medido em 1280×800 com o painel aberto e
   cartão na tela, o disco chegou a ~80px de diâmetro (era 310 em repouso)
   enquanto o gráfico ainda tinha 192px, bem acima do piso dele. O raio é o
   RESTO da conta, então ele absorve sozinho qualquer altura que o dock
   tome — a não ser que tenha chão. 56px = 112px de disco, o menor tamanho
   em que a máscara do logo ainda é a marca e não uma mancha.
   A conta continua fechando com o piso: topo do disco em `4% + 166px` e a
   pilha (disco 112 + 14 + rodapé 44 + 6) cabe nos 64% da faixa A/B sempre
   que o palco tiver ≥ 570px — em 1280×800 com o dock contido pela D8 ele
   tem 637. Abaixo disso quem cede é o gráfico, que é o combinado. */
body:is(.modo-unico, .modo-orbital) .stage .palco > * {
  --orbi-raio: max(56px, min(110px, calc(30cqh - 115px), 14cqw));
  --fac-raio: calc(var(--orbi-raio) + var(--fac-folga));
  --sis-raio: calc(var(--fac-raio) + var(--sis-folga));
  --orbi-topo: calc(4% + 166px);
}
@media (max-height: 700px) {
  body:is(.modo-unico, .modo-orbital) .stage .palco > * {
    --orbi-raio: max(56px, min(110px, calc(30cqh - 94px), 14cqw));
    --orbi-topo: calc(4% + 124px);
  }
}

/* O palco: a faixa do gráfico é a segunda linha e fica RESERVADA — vazia
   quando não há gráfico, e nada mais a usa. Percentuais e não `cqh`:
   unidade de consulta no PRÓPRIO contêiner resolve contra o ancestral. */
body:is(.modo-unico, .modo-orbital) .stage .palco {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 64% minmax(0, 1fr);
}
/* a figura sai: o nó é a presença */
body:is(.modo-unico, .modo-orbital) .stage .orb-core { display: none; }

/* ---- O orbi grande -------------------------------------------------------
   Disco na cor do agente (`--ag-cor` chega inline no nó, da pele da
   instância) com a máscara do logo em branco, e um reflexo de superfície
   para não ser um círculo chapado. O `!important` responde aos `!important`
   das regras-base de `.node-orb` (fundo roxo, sombra, pulso). */
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) {
  top: var(--orbi-topo); left: 50%; transform: translate(-50%, 0);
  width: auto; min-width: 220px; opacity: 1;
  transition: top .6s ease, opacity .4s ease;
}
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-orb {
  width: calc(2 * var(--orbi-raio)); height: calc(2 * var(--orbi-raio));
  margin: 0 auto; border-radius: 50%;
  /* O REFLEXO e a FACE do disco são papel da pele. A marca da Volks proíbe
     gradiente atrás do símbolo ("no colour gradients", p. 33), então lá os
     dois viram `none` + Dark Blue chapado; a instância de ORIGEM não manda
     nada e cai na reserva — o orbe roxo com reflexo, como sempre foi. */
  background:
    var(--orbi-reflexo,
      radial-gradient(circle at 36% 28%, rgba(var(--superficie-rgb),.38), rgba(var(--superficie-rgb),0) 56%)),
    var(--orbi-face, var(--ag-cor, var(--preenchimento))) !important;
  box-shadow: 0 0 0 1px rgba(var(--tinta-rgb),.10),
              0 0 var(--orbi-halo-brilho, 40px) rgba(var(--tinta-rgb),.22),
              0 18px 44px rgba(var(--tinta-rgb),.20) !important;
  animation: orbiPresenca 4.2s ease-in-out infinite !important;
  transition: box-shadow .45s ease, width .6s ease, height .6s ease;
}
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-logo { inset: 24%; opacity: 1; }
/* Brilho por estado de presença (`.stage.estado-*`, app.js): a cor do
   estado é a mesma do painel mono, então o olho liga os dois. */
body:is(.modo-unico, .modo-orbital) .stage.estado-listening .node:is(.unico, .executivo) .node-orb {
  box-shadow: 0 0 0 var(--orbi-halo-a, 4px) rgba(var(--superficie-rgb),.9),
              0 0 0 var(--orbi-halo-b, 6px) var(--estado-ouvindo),
              0 0 var(--orbi-halo-brilho, 46px) var(--estado-ouvindo),
              0 18px 44px rgba(var(--tinta-rgb),.18) !important;
}
/* D3 — o anel de "pensando" era `--alerta`: o LARANJA da pele de ORIGEM
   (#FF8A26). A Volks não tem laranja em lugar nenhum da paleta oficial, e
   um anel laranja num orbi Deep Blue é a única cor fora da marca na tela do
   board. O estado ganha token próprio, como ouvindo e falando já tinham;
   `--estado-pensando` é emitido pelo tema.js a partir da pele (claro: a
   tinta média; escuro: o núcleo clareado). A RESERVA é `--alerta`: a
   instância de ORIGEM não deriva no claro (a marca é a do styles.css), não
   recebe o token, e continua exatamente laranja como sempre foi. */
body:is(.modo-unico, .modo-orbital) .stage.estado-thinking .node:is(.unico, .executivo) .node-orb {
  box-shadow: 0 0 0 var(--orbi-halo-a, 4px) rgba(var(--superficie-rgb),.9),
              0 0 0 var(--orbi-halo-b, 6px) var(--estado-pensando, var(--alerta)),
              0 0 var(--orbi-halo-brilho, 46px) var(--estado-pensando, var(--alerta)),
              0 18px 44px rgba(var(--tinta-rgb),.18) !important;
  animation: orbiPresenca 1.4s ease-in-out infinite !important;
}
body:is(.modo-unico, .modo-orbital) .stage.estado-speaking .node:is(.unico, .executivo) .node-orb {
  box-shadow: 0 0 0 var(--orbi-halo-a, 4px) rgba(var(--superficie-rgb),.9),
              0 0 0 var(--orbi-halo-b, 6px) var(--estado-falando),
              0 0 var(--orbi-halo-brilho, 52px) var(--estado-falando),
              0 18px 44px rgba(var(--tinta-rgb),.18) !important;
  animation: orbiPresenca 2.2s ease-in-out infinite !important;
}
@keyframes orbiPresenca { 0%,100% { transform: scale(1); } 50% { transform: scale(1.03); } }

/* ---- O ANEL DE ESTADO, fora da zona de proteção -------------------------
   O brand book reserva ½ diâmetro em volta do símbolo, livre de tudo (p. 27),
   e é dentro dessa faixa que os halos de estado acima moram. A saída não é
   tirar o sinal de vida: é MUDAR ONDE ele acontece. `inset: -1 × raio` põe
   esta circunferência a exatamente 1a do centro — a borda da zona —, e o arco
   (só `border-top-color`) gira ali, do lado de fora.

   A origem não vê nada disto: sem `--orbi-anel` a borda é transparente e o
   pseudo-elemento não desenha. Quem acende é a pele, um token por estado. */
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-orb::after {
  content: ""; position: absolute; inset: calc(-1 * var(--orbi-raio));
  border-radius: 50%; pointer-events: none;
  border: var(--orbi-fio, max(2px, calc(2 * var(--orbi-raio) / 32))) solid transparent;
  border-top-color: var(--orbi-anel, transparent);
  animation: orbiAnel var(--orbi-anel-vel, 9s) linear infinite;
}
body:is(.modo-unico, .modo-orbital) .stage.estado-listening .node:is(.unico, .executivo) .node-orb::after {
  border-top-color: var(--orbi-anel-ouvindo, var(--orbi-anel, transparent)); --orbi-anel-vel: 3.4s;
}
body:is(.modo-unico, .modo-orbital) .stage.estado-thinking .node:is(.unico, .executivo) .node-orb::after {
  border-top-color: var(--orbi-anel-pensando, var(--orbi-anel, transparent)); --orbi-anel-vel: 1.1s;
}
/* Falando: o anel INTEIRO acende e modula — é a única vez em que ele fecha a
   volta, e por isso lê como "a resposta está saindo agora". */
body:is(.modo-unico, .modo-orbital) .stage.estado-speaking .node:is(.unico, .executivo) .node-orb::after {
  border-color: var(--orbi-anel-falando, var(--orbi-anel, transparent));
  animation: orbiAnelFala 1.6s ease-in-out infinite;
}
@keyframes orbiAnel { to { transform: rotate(360deg); } }
@keyframes orbiAnelFala { 0%,100% { opacity: .45; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-orb::after { animation: none; }
}

/* ---- O rodapé do orbi: nome, DECIDIR e tokens, ABAIXO do disco ----------
   Uma linha para o nome e uma para ação + tokens: 14px de ar do disco,
   ≈44px de rodapé — os números que a conta do raio reserva. O badge era
   absoluto dentro do orbe (`.node .badge { bottom: 50% }`); aqui é filho do
   rodapé e volta ao fluxo. */
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-rodape {
  display: grid; grid-template-columns: auto auto; justify-content: center; align-items: center;
  column-gap: 8px; row-gap: 6px; margin-top: 14px;
}
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-rodape .node-name {
  grid-column: 1 / -1; font: 600 12px/1.2 "Geist", system-ui, sans-serif;
  letter-spacing: .14em; text-transform: uppercase; color: var(--tinta-forte);
}
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-rodape .pill.acao { padding: 5px 14px; font-size: 11px; }
body:is(.modo-unico, .modo-orbital) .stage .node:is(.unico, .executivo) .node-rodape .badge {
  position: static; transform: none; top: auto; bottom: auto; left: auto; right: auto;
}

/* A reserva do dock cede 20px neste modo: folgas de 4px e a narração com
   14px de piso. Nada some, e o dock continua linha própria. */
body:is(.modo-unico, .modo-orbital) .stage .dock { gap: 4px; padding-bottom: 6px; }
body:is(.modo-unico, .modo-orbital) .stage .narracao { min-height: 0; }

/* ---- O espaço do gráfico -------------------------------------------------
   Segunda linha do grid, em fluxo. INVISÍVEL em repouso: o cartão branco
   vazio da captura de 13:45 é o que o Lucas chamou de feio. Só existe com um
   gráfico desenhado — `insight` (app.js). Entre revelar e desenhar, e em
   slot sem SVG, o cartão/slot fica INVISÍVEL mas com layout: o app.js mede
   o `.viz-plot` antes de montar o SVG, e `display:none` zerava a medida (T22).
   `max-height` de 100% da linha: o gráfico cede quando a faixa é curta, mas
   só até o PISO de 160px (150 abaixo de 720px de altura); se faltar pixel,
   ele vem do header ou do dock, não daqui. Sem `pointer-events:none`:
   rótulo com elipse ganha `title` (app.js) e precisa do hover. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz {
  grid-row: 2; align-self: start; justify-self: center;
  position: static; z-index: auto; pointer-events: auto;
  width: min(1280px, 100%); max-height: 100%; min-height: 0;
  margin: 0 auto; padding: 8px clamp(14px, 2.4vw, 28px) 8px;
  background: var(--superficie); border: 1px solid var(--vidro-borda);
  border-radius: 16px; box-shadow: var(--sombra); overflow: hidden;

  /* ---- D2: o texto do cartão escala com a LARGURA do palco --------------
     Medido em 1920×1080: `.v-val` 12px, `.v-cat` 11px, título 14px e
     legenda 12px — os mesmos números de 1280. A tela do board é vista a
     ~3m e a régua não pode ser o pixel; é a largura do palco (`cqw`, o
     `.palco` é `container-type: size`). Os pisos são exatamente os
     tamanhos de hoje e os coeficientes foram escolhidos para que em 1280
     NADA mude (o piso vence) e em 1920 tudo suba ~1/3:
       palco 1280px → 1,05cqw = 13,4 (piso 14) · 0,9cqw = 11,5 (piso 12)
       palco 1920px → 18,5 / 16 / 16 / 14,5  (+32%, +33%, +33%, +32%)
     Com o painel de conversa aberto o palco encolhe e tudo volta ao piso,
     que é o que se quer: menos largura, menos corpo.

     ---- D1: e o CARTÃO RESERVA o que não é gráfico ------------------------
     Em 1280×800 a faixa C dá 222px ao cartão e o `.viz-plot` insistia em
     160px de piso: 8+18,2+4+14+4+160+4+16+8 = 236,2 > 222, e o que sobrava
     de fora era justamente a legenda ("destaque: 31/08 · R$ 207,8 mi"),
     cortada pelo `overflow: hidden`. A legenda tem PRIORIDADE sobre o piso
     do gráfico: ela carrega o número que o CFO está falando.
     `--viz-cromo` é tudo o que não é gráfico dentro do cartão, e escala
     junto com as fontes acima:
       título (1,3 × fs) + legenda (máx(16px, 1,3 × fs))
       + 44px = 3 folgas de 4px + `.viz-series` (14px) + 8px+8px de padding
         + 2px para o arredondamento de meio pixel das métricas de fonte
     1280 → 78,2px · 1920 → 88,9px. O plot fica com o resto da faixa. */
  /* 20/09/2026: um tom abaixo (pedido do dono do produto) — o gráfico fica
     mais limpo e o número deixa de competir com o título. Coeficiente = teto ÷
     640px, como sempre, para o teto encostar no piso antigo sem degrau. */
  --viz-fs-titulo: clamp(13px, 0.95cqw, 16.5px);
  --viz-fs-legenda: clamp(11px, 0.82cqw, 14.5px);
  --viz-fs-val: clamp(11px, 0.82cqw, 14.5px);
  --viz-fs-cat: clamp(10px, 0.75cqw, 13px);
  --viz-cromo: calc(1.3 * var(--viz-fs-titulo) + max(16px, 1.3 * var(--viz-fs-legenda)) + 44px);
  /* ---- ADR-0018: a NOTA DE RODAPÉ das premissas, quando ela existe --------
     Uma linha de `0,9 × --viz-fs-legenda` com entrelinha 1,3 (= 1,17 × fs) mais
     a QUARTA folga do `.viz-slot` (4px), que só passa a existir com o quarto
     filho. 1280 → 18,0px · 1920 → 22,7px.
     Ela entra na régua do cartão SÓ quando há nota (`com-nota`, posta pelo
     app.js): sem `premissas` o elemento não existe e o cartão de hoje não muda
     um pixel. Com nota, quem cede é o gráfico — a premissa é o que impede a
     tela de apresentar simulação como medição, e isso vale mais do que 18px
     de altura de plot. */
  --viz-nota: calc(1.17 * var(--viz-fs-legenda) + 4px);
}
body:is(.modo-unico, .modo-orbital) .stage .palco-viz.com-nota {
  --viz-cromo: calc(1.3 * var(--viz-fs-titulo) + max(16px, 1.3 * var(--viz-fs-legenda)) + 44px + var(--viz-nota));
}

/* ---- O PISO DA FONTE É DISTÂNCIA DE LEITURA, não gosto -------------------
   Os `clamp()` acima têm piso de 11–14px porque a premissa do palco é a tela
   do board, lida a três metros. Abaixo de ~640px de container essa premissa
   não vale: ninguém lê um palco de 380px a três metros — lê a um braço. E o
   piso alto cobra caro justamente onde falta espaço, porque o `_padEixo` do
   app.js RESERVA a largura do eixo medindo o texto (`measureText`) no corpo
   que estes tokens mandam: a 380px, "R$ 300 mi" a 12px come quase 40% da
   largura antes de qualquer dado ser desenhado.

   Então o piso cede com o container, e o `--viz-cromo` (calculado a partir
   destes mesmos tokens) cede junto, devolvendo altura ao plot. Acima de 640px
   nada muda — o cartão do board continua exatamente como está hoje. */
@container (max-width: 640px) {
  body:is(.modo-unico, .modo-orbital) .stage .palco-viz {
    /* O TETO aqui é o PISO de lá: em 640px estes valores encontram os de cima
       (14/12/12/11), então não há degrau na fronteira da consulta; daí para
       baixo o corpo encolhe junto com o container, até um piso de leitura a
       um braço de distância. Os coeficientes são o teto dividido por 640px —
       e o `cqw` mede o PALCO, não o cartão, que foi o erro da primeira
       tentativa (2,7cqw a 500px dava 13,5px: aumentava a fonte em vez de
       reduzir, e o eixo passou de 62 para 67px na medição). */
    --viz-fs-titulo: clamp(12px, 2.19cqw, 14px);
    --viz-fs-legenda: clamp(10px, 1.88cqw, 12px);
    --viz-fs-val: clamp(9.5px, 1.88cqw, 12px);
    --viz-fs-cat: clamp(9px, 1.72cqw, 11px);
  }
}
body:is(.modo-unico, .modo-orbital) .stage .palco-viz:not(.insight) { display: none; }
/* `insight` sem SVG algum (entre revelar e desenhar): invisível, com layout */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz:not(:has(.viz-plot:not(:empty))) { visibility: hidden; }
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-title {
  flex: 0 0 auto; margin: 0; min-height: 0; opacity: 1;
  font: 600 var(--viz-fs-titulo)/1.3 "Geist", system-ui, sans-serif; letter-spacing: 0; text-transform: none;
  color: var(--tinta-forte); text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A régua no palco: 0,85 × a legenda — menor que o título e menor que o
   número, porque ela é o CONTEXTO da leitura, não a leitura. Uma linha, com
   elipse: régua que não cabe é cortada, nunca empurra o gráfico. O
   `min-height` só existe quando há texto; vazia, a regra `:empty` acima a tira
   do fluxo e o cartão de hoje não muda um pixel. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-regua {
  flex: 0 0 auto; margin: 0;
  font: 500 calc(0.85 * var(--viz-fs-legenda))/1.3 var(--fonte-texto, "Geist", system-ui, sans-serif);
  letter-spacing: 0; text-transform: lowercase; color: var(--muted); text-align: center;
  min-height: calc(1.3 * 0.85 * var(--viz-fs-legenda));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-regua:empty { display: none; min-height: 0; }
/* A altura do gráfico é O QUE SOBRA da faixa depois do cromo (D1), e não um
   `cqh` solto: `34cqh` pedia 243px numa faixa de 222 e o cartão cortava a
   legenda. A faixa C é 36% do palco (o grid acima), logo
   `36cqh − --viz-cromo`. O app.js encolhe o plot para o que couber e PARA
   no `min-height` daqui (ele lê essa propriedade), então o piso é a decisão
   de quanto gráfico ainda é gráfico. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-plot {
  flex: 0 1 auto; min-height: 160px;
  height: clamp(160px, calc(36cqh - var(--viz-cromo)), 300px);
}
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-legenda {
  flex: 0 0 auto; font: 500 var(--viz-fs-legenda)/1.3 var(--fonte-texto, "Geist", system-ui, sans-serif);
  font-variant-numeric: tabular-nums; color: var(--muted); text-align: center;
  min-height: max(16px, calc(1.3 * var(--viz-fs-legenda)));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A nota das premissas: menor que a legenda (0,9×), em itálico e na tinta
   apagada — ela é o rodapé do cartão, não concorre com o número. Uma linha com
   elipse: premissa que não cabe é cortada, nunca empurra o gráfico. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-nota {
  flex: 0 0 auto; font: 500 calc(0.9 * var(--viz-fs-legenda))/1.3 var(--fonte-texto, "Geist", system-ui, sans-serif);
  font-style: italic; color: var(--dim); text-align: center;
  min-height: calc(1.17 * var(--viz-fs-legenda));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Números legíveis no palco (CA-4): o app.js manda o tamanho nos atributos do
   SVG, e regra de CSS vence atributo — então o tamanho é decidido aqui. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .v-val { font-size: var(--viz-fs-val); }
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .v-cat { font-size: var(--viz-fs-cat); }
body:is(.modo-unico, .modo-orbital) .stage .palco-viz .v-nota { font-size: var(--viz-fs-cat); }   /* nota de unidade (20/09/2026): mesma escala da categoria */
/* ---- D1: faixa curta (≤860px de altura) — o piso do gráfico desce a 136px
   O 860 é medido, não redondo: com o piso de 160px o cartão precisa de
   160 + 78,2 = 238,2px de faixa; a faixa é 36% do palco e o palco é
   100vh − 64 (header) − dock, com o dock entre 99px (orçamento da D8, que
   é o estado em que há gráfico) e 133px (com a linha de sugestão), logo
     238,2 ≤ 0,36·(100vh − 64 − dock)  ⇒  100vh ≥ 825px … 859px.
   Em 1440×900 a conta fecha com 160 (237 de faixa útil contra 222,2), e é
   por isso que a régua fica acima de 860 e não em 900.
   Abaixo disso quem cede é o gráfico, nesta ordem (spec 011 §2): sistemas,
   gráfico, raio do orbi (que agora tem piso — D9). Com 136px a conta fecha
   nas duas telas baixas do contrato:
     1280×800  faixa 229,3 − 16 = 213,3 ≥ 136 + 62,2 = 198,2  (folga 15,1)
     1024×768  faixa 217,8 − 16 = 201,8 ≥ 198,2               (folga  3,6)
   e a legenda — que carrega o número que o CFO está falando — volta para
   dentro do cartão. */
@media (max-height: 860px) {
  body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-plot {
    min-height: 136px;
    height: clamp(136px, calc(36cqh - var(--viz-cromo)), 300px);
  }
  /* Com nota, o piso desce de 136 para 120px — a nota custa 18,0px e a folga
     de 136 em tela curta é de 15,3px (1280×800) e 3,6px (1024×768). A ordem de
     quem cede é a da spec 011 §2 (sistemas, gráfico, raio do orbi), e a nota
     está do lado da legenda: ela diz que o número é simulado. A conta com o
     piso novo, nas duas telas curtas do contrato:
       1280×800  faixa 229,5 ≥ 96,2 (cromo+nota) + 133,2 (plot) = 229,5
       1024×768  faixa 217,9 ≥ 96,2 + 121,7 = 217,9
     — o clamp fica acima do piso nas duas, e o piso é a reserva para a tela
     que ainda não medimos. */
  body:is(.modo-unico, .modo-orbital) .stage .palco-viz.com-nota .viz-plot {
    min-height: 120px;
    height: clamp(120px, calc(36cqh - var(--viz-cromo)), 300px);
  }
}

/* ---- CA-2 (spec 018): o cartão RESERVADO, antes de existir gráfico --------
   O `viz` chegando MOVIA o orbi (T1, página real: o raio caía 3–13px e o nó
   descia ≤3px), porque `com-viz` só nascia junto com o SVG. Agora o app.js põe
   `esqueleto` no cartão no primeiro especialista aceso do turno, e o cartão já
   entra com a ALTURA FINAL. A conta é a mesma dos dois estados:

     altura(cartão) = --viz-cromo + altura(.viz-plot)

   `--viz-cromo` não muda com o esqueleto — o título é uma linha (`nowrap` +
   elipse) nos dois casos, e legenda e `.viz-series` vazias já reservam o seu
   espaço por `min-height` (regras da 011 v4). E o `.viz-plot` tem altura
   DECLARADA, que não depende do conteúdo. Falta só o placeholder nunca
   empurrar o plot: `height: 100%` e `min-height: inherit` — o piso do
   esqueleto É o piso do plot (160px, 136px em faixa ≤860px), herdado em vez
   de copiado, senão a régua vira duas e a de baixo se esquece de descer.
   Com isso a altura com esqueleto é idêntica à altura com um slot desenhado —
   e é essa igualdade que tira o salto do orbi.

   Nenhuma regra nova precisa revelar o cartão: o placeholder já torna o
   `.viz-plot` NÃO vazio, então o `:not(:has(.viz-plot:not(:empty)))` e o
   `.viz-slot:has(> .viz-plot:empty)` passam a valer a favor da reserva. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz.esqueleto .viz-title { color: var(--muted); }
.viz-esqueleto {
  width: 100%; height: 100%; min-height: inherit;
  margin: 0; padding: 0; border: 0;   /* nada aqui pode somar altura ao plot */
  border-radius: 10px;
  background: linear-gradient(90deg, var(--papel), var(--lavanda), var(--papel));
  background-size: 220% 100%;
  animation: esqueleto 1.4s ease-in-out infinite;
}
@keyframes esqueleto {
  0%, 100% { background-position: 0% 0; }
  50%      { background-position: 100% 0; }
}
/* A saída da reserva é mais curta que a do gráfico (≤300ms, CA-2): aposta que
   não se pagou não merece a dispersão de 380ms de um insight. Mais específica
   que `.palco-viz.saindo`, então vence a duração do atalho `animation` de lá. */
body:is(.modo-unico, .modo-orbital) .stage .palco-viz.esqueleto.saindo { animation-duration: .28s; }
/* O brilho é só sinal de espera; sem ele o placeholder diz exatamente o mesmo.
   Explícito, e não só pelo `.001ms` global: aqui é decisão, não efeito. */
@media (prefers-reduced-motion: reduce) {
  .viz-esqueleto { animation: none; background-position: 0 0; }
}

/* ---- DOIS ESTADOS DO PALCO (spec 012 T15; captura 16:10, "orbi muito pequeno")
   SEM gráfico (`.stage:not(.com-viz)`) o orbi é GRANDE e centrado na altura:
   raio `min(190px, 24cqh, 16cqw)`, centro em 50%. COM gráfico (`.stage.com-viz`,
   classe que o app.js põe quando há SVG no cartão) volta às contas acima, em
   600 ms. As duas variáveis são REGISTRADAS (`@property`) para que a transição
   seja delas mesmas: o disco, o rodapé e os anéis seguem juntos; `width`/
   `height`/`top` também têm transição, para o navegador sem `@property`.
   Registrar `--orbi-raio` como comprimento tem um efeito bom: ela passa a
   computar em px nos filhos do palco, e quem herdar lê px. Custo conhecido:
   sem gráfico e com raio de 24cqh, o anel 2 (raio + 210px) NÃO cabe acima do
   centro em 1280×800 — o presenca.js decide onde pôr os satélites nesse
   estado (lados/abaixo), e a decisão de manter os anéis maiores é da 012. */
@property --orbi-raio { syntax: "<length>"; inherits: true; initial-value: 0px; }
@property --orbi-topo { syntax: "<length-percentage>"; inherits: true; initial-value: 0px; }
body:is(.modo-unico, .modo-orbital) .stage .palco > * {
  transition: --orbi-raio .6s ease, --orbi-topo .6s ease;
}
body:is(.modo-unico, .modo-orbital) .stage:not(.com-viz) .palco > * {
  --orbi-raio: min(190px, 24cqh, 16cqw);
  --orbi-topo: calc(50% - var(--orbi-raio));
}
/* Janela alta (≥ 1000px): a faixa do gráfico cresce para 40% e o orbi em
   `com-viz` cede o que a conta manda (60% − pilha em vez de 64%; em 1920×1080
   o raio continua no teto de 110px). Sem gráfico nada muda. */
@media (min-height: 1000px) {
  body:is(.modo-unico, .modo-orbital) .stage .palco { grid-template-rows: 60% minmax(0, 1fr); }
  /* a faixa passa a ser 40% do palco: a conta do plot (D1) acompanha */
  body:is(.modo-unico, .modo-orbital) .stage .palco-viz .viz-plot {
    height: clamp(160px, calc(40cqh - var(--viz-cromo)), 320px);
  }
  body.modo-unico .stage.com-viz .palco > * { --orbi-raio: max(56px, min(110px, calc(28cqh - 115px), 14cqw)); }
  body.modo-orbital .stage.com-viz .palco > * { --orbi-raio: max(56px, min(110px, calc(28cqh - 167px), 14cqw)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SPEC 012 — MODO ORBITAL (`body.modo-orbital`, posto pelo app.js quando a
   instância tem logo). Reusa TODO o bloco da 011 v3 acima (os seletores lá
   são `body:is(.modo-unico, .modo-orbital)`); o que muda:

     centro   o orbi grande é o EXECUTIVO — o nó dele leva a classe
              `executivo` (app.js) e recebe as mesmas regras do `.node.unico`
     anel 1   ESPECIALISTAS (`.sat.esp`, 64px, marca em branco sobre a cor do
              agente) a `--anel1` do centro; invisíveis até `aberto`
     anel 2   as ÁREAS do especialista ativo (`.fac.fixo`, como na 011) a
              `--anel2`; os sistemas seguem a área
     embaixo  o gráfico, faixa de 64% a 100%, como na 011

   CONTRATO com presenca.js — px puros em `:root`: `--anel1-folga` (120px) e
   `--anel2-folga` (90px). O raio do executivo ele mede no disco
   (`.node.executivo .node-orb`); anel1 = raio + 120; anel2 = anel1 + 90.
   Em CSS: `--orbi-raio`, `--anel1`, `--anel2` nos filhos do palco.

   O raio do executivo é o RESTO da altura, como na 011, só que agora com
   dois anéis por cima: do topo (4%) até o topo do disco vão anel2 (164px)
   + meio orbi de área (32px) = 196px; com sistemas, mais 74. A conta:
     raio = min(110px, 30cqh − 130px, 14cqw)     (30cqh − 167px com sistemas)
   1280×800 (palco ≈620) → raio ≈56px (disco 112) · 1440×900 (≈720) → ≈86px ·
   1920×1080 (≈900) → 110px (teto). Sistemas só a partir de 900px de altura
   de janela: abaixo disso sobrariam 19px de raio em 1280×800. */
:root { --anel1-folga: 120px; --anel2-folga: 90px; }
/* D9 — o piso de 56px também aqui, e é AQUI que ele pegava: esta regra vem
   depois da 011 no arquivo, então é ela que vale na Volks. Com o dock em
   170px (antes da D8) o palco caía para 566 e `30cqh − 130` dava 39,8px de
   raio: o disco do executivo com 80px, menor que os satélites das áreas.
   Com o dock contido (98,6px) o palco tem 637 e a conta volta a dar 61px;
   o piso é a garantia de que nenhuma outra linha que cresça no rodapé
   coma o centro da composição de novo. */
body.modo-orbital .stage .palco > * {
  --orbi-raio: max(56px, min(110px, calc(30cqh - 130px), 14cqw));
  --anel1: calc(var(--orbi-raio) + var(--anel1-folga));
  --anel2: calc(var(--anel1) + var(--anel2-folga));
  --orbi-topo: calc(4% + 196px);
}
@media (min-height: 900px) {
  body.modo-orbital .stage .palco > * {
    --orbi-raio: max(56px, min(110px, calc(30cqh - 167px), 14cqw));
    --orbi-topo: calc(4% + 270px);
  }
}
@media (max-height: 899px) {
  body.modo-orbital .sis, body.modo-orbital .decomp-fios .fio.sis { display: none; }
}

/* ---- Anel 1: os especialistas ---------------------------------------------
   Mesma anatomia do satélite de área: o chip é SÓ o disco (64px), e o
   rótulo fica sempre ABAIXO do disco, nunca ao lado (decisão de 16:20). A marca em branco
   sobre a cor do agente (`--cor` inline), como o orbi grande. Invisível em
   repouso: só aparece no `ramificacao` (`aberto`), acende (`ativo`) e fica
   `usado` até o fim do turno. */
body.modo-orbital .sat {
  position: absolute; width: 64px; height: 64px; padding: 0; border: 0; background: none;
  transform: translate(-50%, -50%) scale(.4); opacity: 0; white-space: nowrap;
  font: 600 11px/1.15 "Geist", system-ui, sans-serif; letter-spacing: .04em; color: var(--muted);
  transition: transform .5s cubic-bezier(.22,.9,.3,1), opacity .5s ease, color .3s;
}
body.modo-orbital .sat.aberto { transform: translate(-50%, -50%) scale(1); opacity: 1; }
body.modo-orbital .sat i {
  position: absolute; inset: 0; border-radius: 50%;
  background:
    radial-gradient(circle at 36% 28%, rgba(var(--superficie-rgb),.36), rgba(var(--superficie-rgb),0) 56%),
    var(--cor, var(--tinta));
  box-shadow: 0 0 0 1px rgba(var(--tinta-rgb),.10), 0 8px 22px rgba(var(--tinta-rgb),.20);
  transition: box-shadow .3s, transform .35s cubic-bezier(.22,.9,.3,1);
}
body.modo-orbital .sat i::after {
  content: ""; position: absolute; inset: 24%;
  background: var(--sobre-preenchimento); opacity: .95;
  -webkit-mask: var(--logo-mascara, url("/static/diamante.svg")) center / contain no-repeat;
  mask: var(--logo-mascara, url("/static/diamante.svg")) center / contain no-repeat;
}
body.modo-orbital .sat b { position: absolute; font-weight: 600; white-space: nowrap; pointer-events: none; transition: color .3s; }
/* sempre ABAIXO do disco, nunca ao lado (T15) — `data-rotulo` é ignorado aqui */
body.modo-orbital .sat b { left: 50%; top: calc(100% + 7px); bottom: auto; right: auto; transform: translateX(-50%); }
body.modo-orbital .sat.usado:not(.ativo) { color: var(--tinta); }
body.modo-orbital .sat.usado:not(.ativo) i { box-shadow: 0 0 0 2px var(--cor, var(--tinta)), 0 8px 22px rgba(var(--tinta-rgb),.20); }
body.modo-orbital .sat.ativo { color: var(--tinta); }
body.modo-orbital .sat.ativo i {
  transform: scale(1.08);
  box-shadow: 0 0 0 3px rgba(var(--superficie-rgb),.95), 0 0 0 5px var(--cor, var(--tinta)),
              0 0 30px var(--cor, var(--tinta)), 0 10px 28px rgba(var(--tinta-rgb),.28);
  animation: orbiRespira 1.1s ease-in-out infinite;
}
body.modo-orbital .sat.pulso i::before {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid var(--cor, var(--tinta)); opacity: 0;
  animation: orbiPulso .9s ease-out 1;
}
/* fio executivo → especialista: como o das áreas */
body.modo-orbital .decomp-fios .fio.esp { stroke: rgba(var(--tinta-rgb),.28); stroke-width: 1.2; opacity: .8; stroke-dasharray: none; stroke-dashoffset: 0; }
body.modo-orbital .decomp-fios .fio.esp.ativo { stroke: var(--cor, var(--tinta)); stroke-width: 2.6; opacity: 1; filter: drop-shadow(0 0 6px var(--cor, var(--tinta))); }

/* ---- Cartão: séries e faixas (spec 012, `comparacao` e `distribuicao`) ----
   Até três séries, uma cor por série — tokens que o app.js lê daqui
   (`--viz-s1..3`) e classes para o SVG (`.v-s1..3`). A legenda de séries
   (`.viz-series`, entre o título e o gráfico) fica FORA do gráfico, uma
   pastilha por série (`<span class="viz-serie" style="--cor:…"><i></i>nome</span>`).
   O eixo de faixas do histograma usa `.v-faixa`; grade e eixo, `.v-grade` e
   `.v-eixo`. */
/* As TRÊS séries vêm da pele quando ela manda (`--serie-*`); sem isso, a
   reserva da origem. Declarar aqui no `.viz` (e não só no `:root`) é o que
   permitia sobrescrever — e o que impedia a instância de sobrescrever, já que
   esta regra vence a herança do inline no <html>. A reserva mantém a origem
   idêntica; a Volks manda navy → lake → sky, os três degraus que passaram no
   validador de paleta (pior par ΔE 30,9; o #003A8C anterior dava 13,1). */
.viz {
  --viz-s1: var(--serie-1, var(--acento));
  --viz-s2: var(--serie-2, var(--tinta-media));
  --viz-s3: var(--serie-3, var(--tinta));
}   /* palco E turno */
.viz .v-s1 { fill: var(--viz-s1); } .viz .v-s2 { fill: var(--viz-s2); } .viz .v-s3 { fill: var(--viz-s3); }
.viz .v-faixa { fill: var(--dim); font: 500 10px var(--fonte-texto, "Geist", system-ui, sans-serif); font-variant-numeric: tabular-nums; }
/* `v-eixo` é a LINHA do eixo; as marcas (números) são texto `v-cat v-marca`.
   O `fill:none` só vale fora de texto — em texto apagava os números (C4, T22). */
.viz .v-eixo { stroke: var(--viz-eixo, rgba(var(--tinta-rgb),.18)); stroke-width: 1; }
.viz :not(text).v-eixo { fill: none; }
/* Marca de eixo em MONOESPAÇADA era fonte de código num gráfico de board: o
   que ela precisa é dígito de largura fixa, e isso `tabular-nums` dá na fonte
   de texto — que na Volks será a VW Text quando os .woff2 chegarem. */
.viz .v-marca { fill: var(--dim); font: 500 10px var(--fonte-texto, "Geist", system-ui, sans-serif); font-variant-numeric: tabular-nums; }
.viz .v-grade { stroke: var(--viz-grade, rgba(var(--tinta-rgb),.08)); stroke-width: 1; fill: none; }
.viz-series {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 14px;
  font: 500 11px/1.2 var(--mono); color: var(--muted); flex: 0 0 auto;
}
.viz-series:empty { visibility: hidden; }
.viz-series .viz-serie { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.viz-series .viz-serie i { width: 8px; height: 8px; border-radius: var(--viz-legenda-raio, 3px); background: var(--cor, var(--viz-s1)); flex: 0 0 auto; }


/* ---- Spec 019 · a linguagem visual: um herói, o resto neutro ---------------
   A régua é a sala: a tela é vista a ~3 m e quem olha tem uma pergunta, não
   uma legenda. Daí os quatro papéis (`--viz-heroi`, `--viz-neutro`,
   `--viz-neutro-txt`, `--viz-acento`, derivados no tema.js) e nenhuma cor
   nova: `--viz-s1..3` continuam servindo o gráfico de ATÉ TRÊS SÉRIES, que é
   outro problema; aqui a decisão é de FOCO, não de identidade de série.

   Quem põe as classes é o app.js. Este arquivo só diz o que cada papel
   parece — e faz isso em DUAS propriedades, porque em SVG a mesma classe
   pinta `fill` numa barra e `stroke` numa linha. A ordem dos blocos abaixo É
   a cascata e não pode ser embaralhada:
     1) o PAPEL na forma (âncora, mudo, antes/depois, cascata, faixa…)
     2) a COR do papel (`v-heroi`/`v-neutro`), que vence o bloco 1
     3) `v-aceso`, que vence todos — o ponto falado acende por cima de
        qualquer estado anterior, inclusive de um ponto mudo.
   Trocar a ordem faz um ponto neutro âncora ficar azul-marinho e uma série
   inteira perder o foco, que é exatamente o defeito que a spec ataca. */

/* 1) o papel na forma ------------------------------------------------------ */
/* Âncora: o ponto que a fala CITA numa série (pico, total, primeiro/último).
   Marcador cheio e maior — ele é o que carrega rótulo. A cor vem do bloco 2
   quando o app.js também marca `v-heroi`/`v-neutro`; sozinho, é o herói. */
.viz .v-ancora { r: 4px; fill: var(--viz-heroi); fill-opacity: 1; opacity: 1; }
/* Mudo: os demais pontos da série. Existem (a linha é contínua) mas não
   disputam atenção — e NÃO ganham rótulo: `text.v-mudo` some de vez, que é a
   trava contra a série de 24 pontos virar 24 números ilegíveis a 3 m. */
.viz .v-mudo { r: 2.4px; opacity: .55; }
.viz text.v-mudo { display: none; }
/* Cascata: início e fim são o herói (os dois saldos), o delta positivo é o
   acento e o negativo é neutro COM contorno — sem o contorno, a queda some
   sobre o papel claro. O fio que liga um passo ao seguinte é traço fino
   tracejado: ele é andaime, não dado. */
.viz .v-cascata-inicio, .viz .v-cascata-fim { fill: var(--viz-heroi); }
/* 20/09/2026: o que SOBE é Lake e o que CAI é Tin Grey (tokens da pele). O acento
   (Light Blue) pintava a subida e "acendia" sem lógica; o neutro passou a ser a
   barra de contexto (Sky) e não pode significar queda. */
.viz .v-cascata-delta-pos { fill: var(--viz-sobe, var(--viz-b)); }
.viz .v-cascata-delta-neg { fill: var(--viz-desce, var(--viz-neutro)); stroke: var(--viz-neutro-txt); stroke-width: 1; }
.viz .v-cascata-fio { fill: none; stroke: var(--viz-neutro-txt); stroke-width: 1; stroke-dasharray: 3 3; opacity: .45; }
/* Antes-e-depois: a INCLINAÇÃO é o dado (sobe ou desce), por isso ela é a
   linha e os marcadores são só as pontas. Neutra a 2px; o item em foco ganha
   o herói a 3px — a especificidade (0,3,0) vence o `stroke` do bloco 2. */
.viz .v-inclinacao { fill: none; stroke: var(--viz-neutro); stroke-width: 2; stroke-linecap: round; }
.viz .v-inclinacao.v-heroi { stroke: var(--viz-heroi); stroke-width: 3; }
.viz .v-antes { r: 3.2px; fill: var(--viz-neutro); }
.viz .v-depois { r: 4px; fill: var(--viz-neutro); }
/* Faixa de cenários (simulação): o retângulo do intervalo é neutro e
   TRANSLÚCIDO — ele é território, não valor.
   ATENÇÃO à homonímia: `.v-faixa` já existia acima como o TEXTO das faixas do
   histograma (`fill: var(--dim)`); por isso a regra nova é `rect.v-faixa`,
   mais específica e restrita ao elemento certo. Duas coisas com o mesmo nome
   é dívida — mas renomear a antiga quebraria o app.js de hoje, e o contrato
   da spec 019 pede este nome. */
.viz rect.v-faixa { fill: var(--viz-neutro); fill-opacity: .45; stroke: none; }
.viz .v-realizado { fill: var(--viz-heroi); }
.viz :is(line, polyline, path).v-realizado { fill: none; stroke: var(--viz-heroi); }

/* ---- PROJEÇÃO 30/60/90 (spec 022): banda, bigode e amostra ---------------
   O app.js emite estas marcas com ATRIBUTO DE APRESENTAÇÃO, que perde para
   qualquer regra de folha — então é aqui que a pele manda, e é por isso que
   cada valor abaixo tem a reserva igual ao atributo que o app.js escreve: a
   instância de origem continua pixel a pixel como está.

   O `v-bigode` é a exceção que obriga esta regra a existir: ele chega SEM
   `stroke`, e o padrão do SVG é `none` — sem o que vem abaixo, a haste de
   `minimo`–`maximo` simplesmente não pinta.

   A OPACIDADE é token porque ela é decisão de marca, não de desenho: o Sky
   Blue da paleta VW já é claro (1,39:1 sobre branco), e a .22 do atributo o
   apaga de vez. Na Volks ela sobe; quem lê a borda continua sendo o tracejado
   em Lake, como o brand book pede para preenchimento claro. */
.viz .v-banda {
  fill: var(--viz-banda, var(--viz-claro));
  fill-opacity: var(--viz-banda-opacidade, .22);
  stroke: var(--viz-banda-borda, var(--viz-b));
}
.viz .v-bigode {
  fill: none;
  stroke: var(--viz-bigode, var(--viz-banda-borda, var(--viz-neutro-txt)));
  stroke-width: 1.2; stroke-opacity: .75; stroke-linecap: butt;
}
.viz .v-amostra-projecao { stroke: var(--viz-banda-borda, var(--viz-b)); }
/* Cenário: contorno do herói e miolo de papel — é projeção, não medição. Só
   o cenário ESCOLHIDO (`v-heroi`) fica cheio. */
.viz .v-cenario { fill: var(--superficie); stroke: var(--viz-heroi); stroke-width: 2; }
.viz .v-cenario.v-heroi { fill: var(--viz-heroi); }

/* 2) a cor do papel -------------------------------------------------------- */
.viz .v-heroi { fill: var(--viz-heroi); }
.viz .v-neutro { fill: var(--viz-neutro); }
/* COMPARAÇÃO (20/09/2026): a barra usa a cor da SUA série — a mesma da pastilha
   da legenda —, e a categoria em foco se marca pela opacidade das outras, não
   repintando tudo de herói/neutro. Antes, a legenda mostrava três azuis e as
   barras saíam navy (foco) ou cinza (resto): legenda e gráfico discordavam. */
.viz rect.v-s1 { fill: var(--viz-s1, var(--serie-1, var(--viz-a))); }
.viz rect.v-s2 { fill: var(--viz-s2, var(--serie-2, var(--viz-b))); }
.viz rect.v-s3 { fill: var(--viz-s3, var(--serie-3, var(--viz-hi))); }
.viz rect.v-neutro.v-s1, .viz rect.v-neutro.v-s2, .viz rect.v-neutro.v-s3 { opacity: .38; }
/* linha, fio e inclinação: a MESMA classe, na outra propriedade. O
   `fill: none` é obrigatório aqui — um `polyline` com `fill` pinta o polígono
   fechado sob a curva e come o gráfico inteiro. */
.viz :is(line, polyline, path, polygon).v-heroi { fill: none; stroke: var(--viz-heroi); }
.viz :is(line, polyline, path, polygon).v-neutro { fill: none; stroke: var(--viz-neutro); }
/* …e a ÁREA sob a linha é a exceção da exceção: preenche, não traça. */
.viz :is(path, polygon).v-heroi.v-area { fill: var(--viz-heroi); fill-opacity: .12; stroke: none; }
.viz :is(path, polygon).v-neutro.v-area { fill: var(--viz-neutro); fill-opacity: .12; stroke: none; }
/* Texto sobre o neutro: o cinza de leitura (Graphite na Volks), nunca o
   próprio neutro — cinza claro sobre papel claro não se lê a 3 m. */
.viz .v-neutro-txt { fill: var(--viz-neutro-txt); }

/* 3) o ponto falado ACENDE ------------------------------------------------- */
/* O gráfico se constrói com a fala (princípio 5): quem está sendo dito ganha
   o acento e um halo. 250ms é o tempo de acompanhar sem piscar — mais rápido
   vira estrobo numa narração de dez números, mais lento atrasa o ponto em
   relação à voz. O halo é `drop-shadow` e não um anel extra: anel é elemento
   novo no DOM a cada ponto falado; sombra é a mesma forma, acesa. */
.viz .v-ancora, .viz .v-mudo, .viz .v-antes, .viz .v-depois, .viz .v-cenario {
  transition: fill .25s ease, stroke .25s ease, opacity .25s ease, r .25s ease, filter .25s ease;
}
.viz .v-aceso {
  r: 5px; opacity: 1;
  fill: var(--viz-acento); stroke: var(--viz-acento); stroke-opacity: .35; stroke-width: 6;
  filter: drop-shadow(0 0 6px var(--viz-acento));
  transition: fill .25s ease, stroke .25s ease, opacity .25s ease, r .25s ease, filter .25s ease;
}
.viz text.v-aceso { fill: var(--viz-acento); stroke: none; filter: none; }
@media (prefers-reduced-motion: reduce) {
  .viz .v-aceso { transition: none; }
}

/* ---- Spec 019 · o TILE DO NÚMERO (`numero`) -------------------------------
   Princípio 3: quando a resposta é um número, o gráfico É o número. O tile
   ocupa o `.viz-plot` inteiro e obedece à MESMA régua de altura do cartão
   (--viz-cromo + altura do plot, travada em test_palco_css.py) — por isso ele
   não declara `overflow`: esconder o que não coube é fingir que coube.
   A régua se cumpre pela ÚLTIMA linha do grid, `minmax(0, 1fr)`: valor,
   rótulo e variação têm altura própria e a sparkline é quem cede (ela é o
   contexto, o número é a resposta). Em 1024×768 as três linhas fixas somam
   ~82px contra um piso de plot de 120px (cartão com nota em tela curta).
   `4.2cqw` é a mesma régua da D2: o corpo do texto do gráfico sai da largura
   do palco, não do pixel — 1280 → 54px, 1920 → 81px, e o teto de 84px existe
   para o número não passar de uma linha em tela larga. */
.viz-plot .viz-numero {
  display: grid; grid-template-rows: auto auto auto minmax(0, 1fr);
  justify-items: center; align-content: center; gap: 2px;
  width: 100%; height: 100%; min-height: 0; text-align: center;
}
.viz-plot .viz-numero-valor {
  font: 700 clamp(40px, 4.2cqw, 84px)/1.05 "Geist", system-ui, sans-serif;
  color: var(--viz-heroi); letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
  white-space: nowrap;
}
.viz-plot .viz-numero-rotulo {
  font: 500 var(--viz-fs-legenda, 12px)/1.3 var(--mono); color: var(--muted);
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A variação é sinal, e o sinal tem cor: alta no acento, queda no cinza de
   leitura, estável apagada. Vermelho/verde de mercado ficam de fora — não são
   da paleta da marca, e "queda" aqui nem sempre é notícia ruim. */
.viz-plot .viz-numero-variacao {
  font: 600 var(--viz-fs-legenda, 12px)/1.3 var(--mono);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.viz-plot .viz-numero-variacao.alta { color: var(--viz-acento); }
.viz-plot .viz-numero-variacao.queda { color: var(--viz-neutro-txt); }
.viz-plot .viz-numero-variacao.estavel { color: var(--muted); }
/* A sparkline é a linha do tempo do número: largura cheia, ~48px de altura, e
   ZERO piso — é ela que cede quando a faixa encolhe (spec 011 §2). */
.viz-plot svg.viz-sparkline {
  display: block; width: 100%; height: 100%; max-height: none; min-height: 0; align-self: end;   /* ocupa o que sobra no tile (última linha do grid) */
}
.viz-plot svg.viz-sparkline .v-linha {
  fill: none; stroke: var(--viz-heroi); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
}
.viz-plot svg.viz-sparkline .v-area { fill: var(--viz-heroi); fill-opacity: .12; stroke: none; }


/* ---- A figura recua para o SEGUNDO PLANO quando há gráfico (FRONT-25) ----
   SÓ NA CONSTELAÇÃO. Com um especialista (spec 011) o gráfico é cartão em
   fluxo na faixa C e não passa por cima da figura — desfocar seria recuar sem
   disputa, que é a regra que o próprio FRONT-20 deixou: recuo resolve DISPUTA
   POR REGIÃO; sem disputa, sem recuo.

   Por que desfoque e não véu, onde ele ainda vale: eu tentei três véus no
   FRONT-17 e os três desenharam um retângulo. Papel interposto TEM BORDA —
   some a borda e sobra uma aresta suja. Desfocar não interpõe nada: muda o
   sujeito, e sujeito desfocado não tem aresta para aparecer. O que impede
   rótulo pequeno de ler sobre a figura é o DETALHE DE ALTA FREQUÊNCIA; o
   desfoque mata a alta frequência e preserva a massa.

   ALVO: só a figura. DE ESTADO, não permanente: sem gráfico a figura está
   nítida. Em CSS puro — quem sabe se há gráfico é o próprio elemento. */
.presenca { transition: filter .45s ease; }
body:not(.modo-unico):not(.modo-orbital) .stage .palco:has(> .palco-viz:not([hidden])) .presenca { filter: blur(4px); }


/* ---- A saída do gráfico (FRONT-27) --------------------------------------
   "Se agrupava de acordo com o insight e depois voltava ao normal": a entrada
   já existe — cada bolinha tem `ballIn` com atraso escalonado, e é ela que dá
   o dado TOMANDO FORMA. Faltava a saída, e sem ela o gráfico não voltava ao
   normal: ele simplesmente ficava.

   Não é o enxame de repouso voltando. Aquilo eram 70 bolinhas animadas por
   seno, permanentes, representando dado nenhum — decoração preenchendo
   silêncio. Isto tem duração, tem causa (o turno seguinte começou) e mostra o
   insight saindo de cena. Animação que CONTA algo, não que preenche.

   Dispersão de grupo e não bolinha a bolinha: o SVG teria de ser reescrito com
   outra animação por marcador, e o ganho não paga o custo de redesenhar tudo
   na saída. */
@keyframes vizSai {
  0%   { opacity: 1; transform: translateY(0)     scale(1);    }
  100% { opacity: 0; transform: translateY(10px)  scale(.965); }
}
.palco-viz.saindo {
  animation: vizSai .38s cubic-bezier(.4,0,.7,.2) forwards;
  pointer-events: none;
}


/* ---- Os glifos de interface saíram (F-6) --------------------------------
   O Lucas: "pode remover os emojis, não faz sentido, muito infantil". Onde já
   havia texto — `sim`, `não`, `fechar` — o texto basta e o símbolo saiu seco.

   O `✕` sozinho é outra história e não podia sair pelado: botão de fechar sem
   marca nenhuma vira um espaço em branco clicável. Virou FORMA DESENHADA (dois
   traços em SVG), não caractere. O vocabulário já existia no `btnMemoria` e no
   microfone, então isto não inventa linguagem nova — usa a que a interface já
   fala. Desenho também escala com a caixa e herda `currentColor`, o que um
   glifo de fonte nunca fez direito.

   Mesma coisa para o `●` que precedia o nome do domínio no painel de detalhe:
   ele era um marcador de COR, não um caractere. Virou um quadradinho de CSS,
   que é o que ele sempre quis ser. */
.console-x svg, .mem-x svg { width: 13px; height: 13px; display: block; }
.mem-x svg { width: 15px; height: 15px; }
.console-x, .mem-x { display: inline-flex; align-items: center; justify-content: center; }

.mem-det-orig { display: flex; align-items: center; gap: 7px; }
.mem-det-orig .ponto {
  width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto;
  display: inline-block;
}

/* ======================================================================
   O ROSTO DO TRUCKY DENTRO DO ORBI
   ----------------------------------------------------------------------
   O SVG é injetado pelo `trucky.js` dentro do `.node-orb` que já existe, e
   só quando a pele manda (`--orbi-rosto: sim`). Tudo aqui é ADITIVO: cada
   regra depende de `.rosto` estar na tela. Numa instância sem rosto nenhum
   seletor casa e a tela é a mesma, byte a byte.

   O ESTADO VEM DO `data-tk`, não do palco. Motivo medido: o `.stage.estado-*`
   só é escrito no caminho CLÁSSICO de TTS (app.js). Na voz realtime — o modo
   em produção — o estado do canal para no botão do microfone, e o palco fica
   em `idle` enquanto o agente fala. O `trucky.js` resolve canal + palco +
   áudio e escreve um estado só no próprio rosto, sem tocar no resto da tela.

   A BOCA SEGUE A AMPLITUDE REAL da fala (`--tk-boca`, de
   `VozRealtime.nivelSaida()`). O laço de tempo fixo abaixo é a RESERVA para
   o TTS clássico, onde não há AudioContext para medir — por isso ele só
   acende em `:not(.tk-audio)`.

   As cores de estado vêm dos mesmos tokens do anel (`--estado-ouvindo`,
   `--estado-pensando`, `--estado-falando`). Nenhum hex de cliente aqui.
   ====================================================================== */
.node-orb .rosto {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 2; pointer-events: none; overflow: visible;
  /* `.node-orb svg` (o ícone legado de 28px) centra por top/left 50% + um
     translate(-50%,-50%). Este SVG preenche o disco por `inset: 0`, então o
     translate sobrava e jogava o rosto um raio inteiro para cima e para a
     esquerda. Zerar aqui é obrigatório enquanto a regra legada existir. */
  transform: none;
}
/* Com rosto, o símbolo da marca sai de dentro do disco: os dois no mesmo
   lugar viram sobreposição, e o rosto já carrega a assinatura dele. */
body.com-rosto .node:is(.unico, .executivo) .node-logo { display: none; }

/* ---- pálpebra: o piscar, sempre presente (é o que dá vida em repouso) -- */
.rosto .palpebra { transform: translateY(0); animation: tkPiscar 5.6s infinite; }
@keyframes tkPiscar {
  0%, 92%, 100% { transform: translateY(0); }
  94.6%, 95.6%  { transform: translateY(33px); }
}

/* ---- olho: arregala ao ouvir ----------------------------------------- */
.rosto .olho { transform-box: fill-box; transform-origin: center; transition: transform .35s cubic-bezier(.22,1,.36,1); }
.rosto[data-tk="listening"] .olho { transform: scale(1.07); }

/* ---- globo: o olhar vaga enquanto pensa ------------------------------- */
.rosto .globo { transition: transform .5s cubic-bezier(.22,1,.36,1); }
.rosto[data-tk="thinking"] .globo { animation: tkVagar 3.8s ease-in-out infinite; }
@keyframes tkVagar {
  0%, 100% { transform: translate(-3px, -3.5px); }
  50%      { transform: translate(3.5px, -3.5px); }
}
/* Olhando para o gráfico: o palco baixa o olhar quando há viz na tela. */
.stage.com-viz .rosto .globo { transform: translate(0, 4.5px); animation: none; }

/* ---- sobrancelhas ----------------------------------------------------- */
.rosto .sobrancelha { transition: transform .35s cubic-bezier(.22,1,.36,1); }
.rosto[data-tk="listening"] .sobrancelha { transform: translateY(-2.5px); }
.rosto[data-tk="thinking"] .cenhos .sobrancelha:first-child {
  transform: translateY(-3.5px) rotate(-4deg); transform-box: fill-box; transform-origin: center;
}

/* ---- boca: escala a partir do lábio de cima, o queixo é que cai -------
   `--tk-boca` é escrito quadro a quadro pelo trucky.js SÓ quando há áudio
   medido; fora disso a reserva de cada estado é que vale. */
.rosto .boca {
  transform-box: fill-box; transform-origin: 50% 8%;
  transition: transform .3s cubic-bezier(.22,1,.36,1);
  transform: scaleY(var(--tk-boca, .44));           /* repouso é também o padrão */
}
.rosto[data-tk="listening"] .boca { transform: scaleY(var(--tk-boca, .32)); }
.rosto[data-tk="thinking"]  .boca { transform: scaleY(var(--tk-boca, .15)); }
/* Sem medida (TTS clássico): o pulso sintético, a mesma faixa .30–1. */
.rosto[data-tk="speaking"]:not(.tk-audio) .boca { animation: tkFalar .33s ease-in-out infinite; }
@keyframes tkFalar { 0%, 100% { transform: scaleY(.30); } 46% { transform: scaleY(1); } }
/* Com medida, quem suaviza é o JS (lerp .35): a transição do CSS por cima
   atrasaria a boca em relação ao som. */
.rosto.tk-audio .boca { transition: none; }

/* ---- LEDs da moldura -------------------------------------------------- */
.rosto .led-ciano { transition: opacity .3s; opacity: .55; }
.rosto[data-tk="listening"] .led-ciano { animation: tkRespira 1.15s ease-in-out infinite; }
.rosto[data-tk="thinking"]  .led-ciano { opacity: .75; }
.rosto[data-tk="speaking"]  .led-ciano { animation: tkCintila .33s ease-in-out infinite; }
@keyframes tkRespira { 0%, 100% { opacity: 1; }  50% { opacity: .35; } }
@keyframes tkCintila { 0%, 100% { opacity: .5; } 46% { opacity: 1; } }

/* ---- lanternas de teto: piscam alternadas enquanto pensa -------------- */
.rosto .led-ambar { transition: opacity .3s; opacity: .85; }
.rosto[data-tk="thinking"] .led-ambar:nth-of-type(1) { animation: tkPisca1 1.4s steps(1) infinite; }
.rosto[data-tk="thinking"] .led-ambar:nth-of-type(2) { animation: tkPisca2 1.4s steps(1) infinite; }
@keyframes tkPisca1 { 0%, 49% { opacity: 1; }   50%, 100% { opacity: .25; } }
@keyframes tkPisca2 { 0%, 49% { opacity: .25; } 50%, 100% { opacity: 1; } }

/* ---- fones: o anel interno segue o anel de estado de fora ------------- */
.rosto .fone-anel { transition: stroke .3s, stroke-opacity .3s; }
.rosto[data-tk="listening"] .fone-anel { stroke: var(--estado-ouvindo); animation: tkAro 1.15s ease-in-out infinite; }
.rosto[data-tk="thinking"]  .fone-anel { stroke: var(--estado-pensando, var(--alerta)); }
.rosto[data-tk="speaking"]  .fone-anel { stroke: var(--estado-falando); stroke-opacity: .95; }
@keyframes tkAro { 0%, 100% { stroke-opacity: .95; } 50% { stroke-opacity: .3; } }

/* ---- faróis da testa -------------------------------------------------- */
.rosto .farol { transition: opacity .3s; }
.rosto[data-tk="thinking"] .farol { animation: tkRespira 1.4s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .rosto .palpebra, .rosto .globo, .rosto .boca, .rosto .led-ciano,
  .rosto .led-ambar, .rosto .fone-anel, .rosto .farol { animation: none !important; }
}
