/* ═══════════ Campo de Batalha (gates-web) ═══════════ */
/* ═══ `fixed`, NÃO `absolute` (bug do Lucas, 30/07) ════════════════════════════════════════════
   "às vezes entro no campo de batalha e fica essa listra com outras coisas por baixo" — na foto dele,
   uma faixa embaixo mostrando o piso da cidade e a barra do HUD.
   CAUSA: com `absolute; inset:0` o overlay cobre o DOCUMENTO, não a tela. No iPhone a barra do Safari
   se retrai e a viewport passa a ser MAIOR que o documento; a diferença fica descoberta e o overworld
   aparece por baixo. O "às vezes" é isso: depende de a barra estar recolhida quando a batalha abre.
   Reproduzido encolhendo o documento para 368px numa tela de 428: sobravam 60px expondo o canvas do
   mundo — a mesma faixa da foto. Com `fixed`, o overlay se ancora na VIEWPORT e não há o que sobrar.
   Os filhos que já eram fixed (.bt-actions, .bt-turn) continuam valendo: #battle não tem transform
   nem filter, então não vira contexto de contenção para eles. */
#battle {
  position: fixed; inset: 0; z-index: 50; overflow: hidden;
  font-family: var(--game-font); color: #eaddc0;
  /* CENÁRIO REAL (artes do Lucas 13/07, 1672×941): imagem + véu leve pra HUD/nomes lerem bem.
     BOSQUE = default; .scen-estrada/.scen-corrompido (setadas no start() do battle.js) trocam a arte. */
  background:
    linear-gradient(180deg, rgba(8,12,6,.30) 0%, rgba(8,12,6,.08) 36%, rgba(4,7,3,.42) 100%),
    url("/assets/battle-bg/bosque.png") center / cover no-repeat,
    #141c0e;
}
#battle.hidden { display: none !important; }
/* vinheta única sobre a arte (bordas levemente escuras; sem mata falsa por cima da pintura) */
.bt-scene { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.bt-scene::before { content: ""; position: absolute; inset: 0;
  background: radial-gradient(120% 105% at 50% 46%, transparent 58%, rgba(0,0,0,.34) 100%); }
/* ::after (faixa com sombra inset) REMOVIDO 13/07 — era vestígio do fundo-gradiente e criava uma
   LINHA horizontal visível sobre as artes reais (borda da faixa ≠ altura do painel por viewport). */
.bt-scene::after { content: none; }

/* ═══ CENÁRIOS (pedido Lucas 11/07; artes reais 13/07) ═══ */
/* ESTRADA: campo de batalha de beira de estrada — muros arruinados, estandarte rasgado */
#battle.scen-estrada { background:
  linear-gradient(180deg, rgba(14,10,5,.30) 0%, rgba(14,10,5,.08) 36%, rgba(8,6,3,.44) 100%),
  url("/assets/battle-bg/estrada.png") center / cover no-repeat,
  #201810; }
/* BOSQUE AMALDIÇOADO: mata fechada com veias de corrupção roxas */
#battle.scen-corrompido { background:
  linear-gradient(180deg, rgba(10,4,16,.34) 0%, rgba(10,4,16,.10) 36%, rgba(6,2,10,.48) 100%),
  url("/assets/battle-bg/corrompido.png") center / cover no-repeat,
  #0d0714; }

/* campo (acima do painel de ação) */
/* bottom acompanha o painel de CARTÕES M3 (aprovado Lucas 13/07): painel PC subiu 196→265, mantendo o
   MESMO overlap decorativo de 46px que existia (150 = 196−46) → 219 = 265−46. A banda de formação é
   percentual, então PARTY_POS/ENEMY_POS se reacomodam sozinhos dentro do campo mais curto. */
/* 222 (maquete 220 aprovada): painel encolheu (só-heróis, sem zona de inimigos) → o campo cresce.
   field.bottom = altura do painel (sem overlap decorativo, como a maquete). PC 190 / celular 150 (
@media). */
.bt-field { position: absolute; left: 0; right: 0; top: 0; bottom: 190px; z-index: 1; }

/* unidade em campo (herói ou inimigo) */
.bt-unit { position: absolute; transform: translate(-50%,-50%); width: 118px; display: flex; flex-direction: column; align-items: center; }
/* 30/07 (martelo Lucas): o PULO morreu. bt-idle movia o corpo 4px na vertical — era o que fazia todo
   mundo flutuar. No lugar, RESPIRO por escala (mesmo princípio do .bt-breathe que ele aprovou em 10/07),
   3,5s, ancorado no pé e sem deslocamento vertical nenhum. Medido: variação vertical 0px. */
.bt-unit .bt-body { animation: bt-respira 3.5s ease-in-out infinite; transform-origin: 50% 100%; will-change: transform; }
/* dessincronia: em grupo, respirar em uníssono fica mecânico (mesma razão dos -0.8s/-1.6s do .bt-breathe) */
.bt-unit:nth-child(2n) .bt-body { animation-delay: -1.1s; }
.bt-unit:nth-child(3n) .bt-body { animation-delay: -2.2s; }
.bt-unit:nth-child(5n) .bt-body { animation-delay: -0.6s; }
/* HERÓIS por sprite-sheet (pedido Lucas 10/07): o bob antigo (translateY, pensado pro placeholder .cr)
   SAI daqui — Bárbaro/Maga já têm folha idle multi-frame animando (vida própria, bob duplicava o
   movimento); Monge/Druida/Arqueiro ganham a respiração sintética (.bt-breathe, abaixo) no lugar. */
.bt-unit.sprite-hero .bt-body { animation: none; }
.bt-unit.enemy.targetable { cursor: pointer; }
.bt-unit.enemy.targetable::after { content: "▼"; position: absolute; top: -30px; left: 50%; transform: translateX(-50%); color: #ffd84a; font-size: 16px; text-shadow: 0 0 6px #000; animation: bt-bounce .7s ease-in-out infinite; }
.bt-unit.enemy.targetable:hover .bt-body { filter: brightness(1.2) drop-shadow(0 0 8px rgba(255,210,80,.6)); }
.bt-unit.hero.targetable { cursor: pointer; }
.bt-unit.hero.targetable::after { content: "▼"; position: absolute; top: -30px; left: 50%; transform: translateX(-50%); color: #8fd257; font-size: 16px; text-shadow: 0 0 6px #000; animation: bt-bounce .7s ease-in-out infinite; }
.bt-unit.hero.targetable:hover .bt-body { filter: brightness(1.2) drop-shadow(0 0 8px rgba(143,210,87,.6)); }
/* SELO do PROVOCADOR (taunt): mob/herói com status "provocando" ganha rótulo dourado + brilho avermelhado */
/* ⚠️ O selo mudou de casa (05/08): ele morava no `.bt-uname::after`, e o `.bt-uname` está
   `visibility: hidden` desde que a placa assumiu o nome — ou seja, o selo EXISTIA (o ::after tem
   o conteúdo, medido) e NUNCA aparecia para ninguém. Agora ele nasce no `.pl-nome`, que é o nome
   que o jogador enxerga. Fonte 10px, a régua da casa: a 9px ele já estava abaixo dela.
   Provocar é a mecânica que decide EM QUEM o mob bate — esconder isso é esconder a regra do turno. */
.bt-unit.taunting .pl-nome::after { content: " 🛡PROVOCANDO"; color: #ffcf5c; font-size: 10px; text-shadow: 0 1px 2px #000; }
.bt-unit.taunting .bt-body { filter: drop-shadow(0 0 7px rgba(224,90,64,.55)); }
.bt-unit.active .bt-body { filter: drop-shadow(0 0 12px rgba(240,208,96,.8)); }
.bt-unit.active::before { content: ""; position: absolute; bottom: -8px; left: 50%; width: 70px; height: 16px; transform: translateX(-50%) scale(var(--cam, 1)); transform-origin: 50% 100%; border-radius: 50%; background: radial-gradient(closest-side, rgba(240,208,96,.55), transparent); } /* 243: brilho do turno acompanha a câmera */
/* 242: morto NÃO intercepta toque (pointer-events none) — com o porte novo, o corpo grande de um caído
   cobria o vizinho vivo e ROUBAVA o clique da mira (mira ficava pendurada; pego pela luta-por-toque). */
.bt-unit.dead { opacity: .18; filter: grayscale(1); transform: translate(-50%,-50%) translateY(10px) rotate(-4deg); transition: opacity .5s, transform .5s; pointer-events: none; }
@keyframes bt-respira { 0%,100%{transform:scaleY(1) scaleX(1)} 50%{transform:scaleY(1.018) scaleX(.995)} }
/* bt-idle mantido só para quem ainda referencie o nome; ninguém mais o usa */
@keyframes bt-idle { 0%,100%{transform:translateY(0)} 50%{transform:translateY(0)} }
@keyframes bt-bounce { 0%,100%{transform:translate(-50%,0)} 50%{transform:translate(-50%,5px)} }
/* ── RESPIRAÇÃO SINTÉTICA (Monge/Druida/Arqueiro — pedido Lucas 10/07) ──
   Wrapper INTERNO ao sprite (filho de .bt-body, avô de .cr-sprite): o lunge/hit/heal continuam animando
   .bt-body (translateX/filter) e o passo de frame do idle-cast anima .cr-sprite (background-position) —
   nenhum dos dois mexe em "transform" do MESMO elemento que a respiração, então nada briga por
   especificidade. Ainda assim, suspendemos via animation-play-state durante ataque/hit (pedido explícito),
   fica mais limpo visualmente um herói não "respirar" no meio do golpe. */
.bt-breathe { transform-origin: bottom center; animation: bt-breathe 2.4s ease-in-out infinite; will-change: transform; }
@keyframes bt-breathe { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(1.015); } }
/* dessincronia leve entre os 3 (senão respiram em uníssono — antinatural em grupo) */
.bt-breathe[data-hero="druida"] { animation-delay: -0.8s; }
.bt-breathe[data-hero="arqueiro"] { animation-delay: -1.6s; }
.bt-unit.attacking .bt-breathe, .bt-unit.hit .bt-breathe { animation-play-state: paused; }
/* prefers-reduced-motion (pedido Lucas 10/07, extensão do idle vivo p/ inventário): pausa a respiração
   sintética e a folha de frames (hero-strip) sem resetar geometria — só congela no frame/pose atual.
   !important no .cr-sprite: o `animation` inline (renderHero/renderLiveIdle) é shorthand com maior
   especificidade que a media query — sem isso o play-state "running" do shorthand ganha e ignora o SO. */
@media (prefers-reduced-motion: reduce) {
  .bt-breathe { animation-play-state: paused; }
  .cr-sprite { animation-play-state: paused !important; }
}
/* O corpo que hospeda um VFX fica estável durante toda a timeline. Isso impede que efeitos presos à
   unidade herdem o bob vertical ou um lunge que ainda estivesse terminando. */
.bt-body.skill-vfx-motion-lock { animation: none !important; }
.bt-body.skill-vfx-motion-lock .bt-breathe { animation-play-state: paused; }
/* heróis (esquerda) investem para a DIREITA; inimigos (direita) para a ESQUERDA */
.bt-unit.hero.attacking .bt-body { animation: bt-lunge-right .46s ease; }
.bt-unit.enemy.attacking .bt-body { animation: bt-lunge-left .46s ease; }
.bt-unit.hit .bt-mob,
.bt-unit.hit .bt-avatar,
.bt-unit.hit .skill-vfx-character {
  filter: brightness(0) saturate(100%) invert(22%) sepia(98%) saturate(7378%) hue-rotate(356deg) brightness(111%) contrast(122%) !important;
}
.bt-unit.healed .bt-body { z-index: 2; animation: bt-heal .6s ease; }
/* AURA verde no curado (pedido Lucas 11/07): o antigo símbolo ✚ subia na MESMA posição que o número
   da cura (.bt-float) e o encobria — trocado por um halo verde pulsante na unidade, que não colide
   com o número. O corpo recebe z-index explícito enquanto o filter de bt-heal cria seu próprio
   stacking context; assim a aura (1) permanece ATRÁS do corpo (2) e do número (6). */
.bt-unit.healed::after { content: ""; position: absolute; inset: -8px 4px; z-index: 1; pointer-events: none; border-radius: 50%;
  background: radial-gradient(circle at 50% 56%, rgba(120,220,90,.5), rgba(120,220,90,.16) 50%, transparent 70%);
  animation: bt-heal-aura .95s ease-out forwards; }
@keyframes bt-heal-aura { 0% { opacity: 0; transform: scale(.7); } 30% { opacity: 1; transform: scale(1.05); } 100% { opacity: 0; transform: scale(1.2); } }
@keyframes bt-lunge-right { 0%{transform:translateX(0)} 42%{transform:translateX(34px) scale(1.06)} 100%{transform:translateX(0)} }
@keyframes bt-lunge-left { 0%{transform:translateX(0)} 42%{transform:translateX(-34px) scale(1.06)} 100%{transform:translateX(0)} }

/* ═══ IMPACTO (Lucas 03/08: "gostei disso, pode implementar") ═══════════════════════════════════
   A batalha só PISCAVA uma cor no alvo (.bt-unit.hit acima) — quem levava a pancada ficava cravado
   no lugar. Num golpe 2D a colisão se vende por TRÊS coisas juntas: a pose de quem bate + a VÍTIMA
   RECUANDO + a CÂMERA sacudindo. Estas três regras são as duas que faltavam (maquete 466 provou a
   diferença com a MESMA animação dos dois lados). Vale pra TODA skill de herói e de mob.
   · O recuo mora no .bt-body (mesmo elemento do lunge — o defensor nunca está investindo ao mesmo
     tempo) e o sentido/força vêm de variáveis setadas pelo JS (herói recua p/ esquerda, mob p/ direita).
   · O tremor mora no .bt-field: sacode TODAS as unidades juntas, como uma câmera. As medições de
     placa (posicionaPlacas) são relativas a elementos DENTRO do field, então o tremor se cancela nelas.
   · translate3d força camada própria de composição: o tremor não repinta o cenário inteiro (celular). */
.bt-unit.bt-knock .bt-body { animation: bt-knock var(--knock-ms, 300ms) cubic-bezier(.18,.86,.32,1); }
@keyframes bt-knock {
  0%   { transform: translate3d(0,0,0) rotate(0) scale(1,1); }
  16%  { transform: translate3d(var(--knock-x, 12px), calc(var(--knock-x, 12px) * -0.18), 0) rotate(var(--knock-r, 5deg)) scale(1.05,.93); }
  46%  { transform: translate3d(calc(var(--knock-x, 12px) * 0.42), 0, 0) rotate(calc(var(--knock-r, 5deg) * 0.4)) scale(1.01,.99); }
  100% { transform: translate3d(0,0,0) rotate(0) scale(1,1); }
}
.bt-field.bt-shake { animation: bt-shake var(--shake-ms, 160ms) steps(1, end); }
@keyframes bt-shake {
  0%   { transform: translate3d(0,0,0); }
  20%  { transform: translate3d(calc(var(--shake-a, 4px) * -1), calc(var(--shake-a, 4px) * 0.5), 0); }
  40%  { transform: translate3d(var(--shake-a, 4px), calc(var(--shake-a, 4px) * -0.6), 0); }
  60%  { transform: translate3d(calc(var(--shake-a, 4px) * -0.5), 0, 0); }
  80%  { transform: translate3d(calc(var(--shake-a, 4px) * 0.35), calc(var(--shake-a, 4px) * 0.25), 0); }
  100% { transform: translate3d(0,0,0); }
}
/* CLARÃO do contato: nasce no ponto da colisão (JS ancora em px dentro do .bt-floats) e some. Fica
   ATRÁS dos números de dano (.bt-float, z 6) e na frente do corpo, como o flash de impacto clássico. */
.bt-impacto { position: absolute; z-index: 5; pointer-events: none; border-radius: 50%; transform: translate(-50%,-50%);
  background: radial-gradient(circle, rgba(255,255,255,.95) 0%, rgba(255,214,150,.85) 34%, rgba(224,90,50,0) 72%);
  animation: bt-impacto 260ms ease-out forwards; }
@keyframes bt-impacto {
  0%   { width: 10px; height: 10px; opacity: .95; }
  100% { width: var(--flash-d, 76px); height: var(--flash-d, 76px); opacity: 0; }
}
/* ═══ ESQUIVA (aprovada Lucas 03/08, prancha 499: borrão + fantasma AZUL-GELO) ═══
   Irmã do recuo acima e da mesma família: quem se mexe é o SPRITE QUE JÁ EXISTE, então isto vale
   para os 5 heróis e os 77 monstros sem uma linha de arte nova. Antes disso, errar um golpe só
   escrevia "errou" e o corpo ficava imóvel (7 lugares em battle.js).
   · --esq-x: para onde o defensor foge — sempre para LONGE de quem atacou (ver esquivaEl)
   · o BORRÃO vai no SPRITE, não no corpo: filtro no pai borraria também o fantasma, e o fantasma
     é justamente a parte que precisa ficar nítida (erro cometido e corrigido na maquete 499). */
.bt-unit.bt-esquiva .bt-body { animation: bt-esquiva var(--esq-ms, 420ms) cubic-bezier(.2,.9,.3,1); }
@keyframes bt-esquiva {
  0%   { transform: translate3d(0,0,0); }
  22%  { transform: translate3d(var(--esq-x, 16px), 0, 0); }
  42%  { transform: translate3d(calc(var(--esq-x, 16px) * 0.82), 0, 0); }
  100% { transform: translate3d(0,0,0); }
}
/* :not(.bt-fantasma) — o fantasma É um clone do sprite, então ele casava com este seletor e herdava
   a animação de borrão; o `filter` da animação vence o filtro estático e o azul-gelo virava `none`
   (medido na 1a prova). O fantasma tem filtro próprio e não borra. */
.bt-unit.bt-esquiva .cr-sprite:not(.bt-fantasma),
.bt-unit.bt-esquiva .mob-sprite:not(.bt-fantasma) { animation: bt-esquiva-borrao var(--esq-ms, 420ms) linear; }
@keyframes bt-esquiva-borrao {
  0%,8%    { filter: none; }
  26%      { filter: blur(2px) brightness(1.12); }
  46%      { filter: blur(1.6px) brightness(1.1); }
  62%,100% { filter: none; }
}
/* ═══ RASTROS DA ESQUIVA v2 (Lucas 04/08: "eu queria que a esquiva fosse a imagem do personagem
   repetida, e não uma sombra branca — como foi feito no passo sombrio A") ═══════════════════════
   Três figuras: o corpo e DOIS rastros do PRÓPRIO desenho, graduados por OPACIDADE — sem tingir
   nada. Quem conta o deslocamento é a transparência, não a cor: resolve os 77 mobs de uma vez e
   para de brigar com o roxo do Passo Sombrio.

   TRÊS CONSERTOS, TODOS MEDIDOS ANTES (scratchpad/sonda_esquiva_hoje.js) — a versão azul-gelo
   errava três coisas ao mesmo tempo, e o Lucas só tinha enxergado a primeira:
   1. FLIP E ESQUADRO DO PÉ: o fantasma animava `transform`, que é exatamente ONDE moram o
      scaleX(-1) do mob e o --mob-dx/--mob-dy do esquadro. Medido no orc: sprite real
      matrix(-1,0,0,1, 1.88, 17.87) contra fantasma matrix(1,0,0,1, -1.18, 0) — virado para o lado
      errado E 18px fora do chão. Conserto: animar a propriedade `translate`, que é independente e
      se compõe FORA do `transform` — o sprite fica com o transform dele intacto.
   2. TAMANHO: o clone era pendurado no .bt-body, FORA do .bt-mob/.bt-breathe, e todo seletor
      descendente que dá tamanho parava de casar. Medido: herói real 135×136 e fantasma 71×71
      (--scale caindo de 1.05 para 0.558); orc real 80×82 e fantasma 134×134 (68% maior).
      Conserto: o clone nasce no MESMO PAI do sprite — herda as mesmas regras por construção.
   3. ESPAÇAMENTO (Lucas: "o primeiro clone quase não desloca do segundo, dando a sensação que só
      tem dois"): estavam a 0,34 e 0,12 do trajeto — 6,6px de vão entre si contra 20px até o corpo.
      Agora dividem o caminho em terços: corpo 1,00 · rastro 1 em 0,58 · rastro 2 em 0,22.

   POR QUE O FATOR (--f - 1): o rastro é FILHO do .bt-body, e o .bt-body já desliza o trajeto
   inteiro (bt-esquiva acima). Para o rastro PARAR na fração --f do caminho, o deslocamento próprio
   dele tem que ser (--f - 1) do trajeto — o pai soma 1,00 e ele desconta. Por isso usa a MESMA
   duração e a MESMA curva do corpo: assim a soma bate em todo instante, não só no pico.

   !important no `animation`: mesmo motivo documentado no .cr-sprite lá em cima — renderHero emite
   `animation` INLINE (shorthand) para o herói de folha multi-quadro, e regra sem !important perde.
   Sem ele o rastro do Bárbaro rodaria a folha em vez de esvanecer, e ficaria preso em opacity 0. */
.bt-fantasma {
  position: absolute; pointer-events: none; z-index: -1; opacity: 0;
  animation: bt-rastro var(--esq-ms, 420ms) cubic-bezier(.2,.9,.3,1) forwards !important;
}
/* --op subiu de .50/.26 para .70/.40 na variante E da prancha 587 (Lucas 04/08). Motivo medido, não
   gosto: com a tira de fantasma que sobra à mostra (12% do bicho a 44px de recuo), a .26 do rastro
   de trás sumia contra o mato do Bosque. A graduação entre os dois é o que conta a distância. */
.bt-fantasma--1 { --f: .58; --op: .70; }
.bt-fantasma--2 { --f: .22; --op: .40; }
/* --esq-r é o --esq-x JÁ DIVIDIDO pela escala local do desenho (conta feita em esquivaEl). O corpo
   translada FORA da caixa que escala o sprite; o rastro, DENTRO dela. Usar --esq-x aqui fazia o
   rastro do orc parar a 7,8px do corpo em vez de 13,1 — ele mentia sobre onde o corpo esteve. */
@keyframes bt-rastro {
  0%   { opacity: 0; translate: 0 0; }
  10%  { opacity: var(--op, .5); }
  22%  { translate: calc(var(--esq-r, var(--esq-x, 16px)) * (var(--f, .58) - 1)) 0; }
  42%  { opacity: calc(var(--op, .5) * .6);
         translate: calc(var(--esq-r, var(--esq-x, 16px)) * 0.82 * (var(--f, .58) - 1)) 0; }
  74%  { opacity: 0; }
  100% { opacity: 0; translate: 0 0; }
}
/* ═══ PASSO SOMBRIO — as duas sombras do buff ════════════════════════════════════════════════
   Aprovado pelo Lucas 04/08 (variante A da prancha 559; distância 16px escolhida por ele).
   NÃO é floreio do lance: a skill não tem dano nem alvo, ela dá Acelerado + Foco + Evasivo por 2
   turnos. As sombras são a MARCA do buff e ficam enquanto ele durar.
   Como a esquiva, são cópias do próprio sprite — zero arte nova, serve qualquer herói ou mob.
   O GATILHO é o status `evasivo`, que só o Passo concede: foi exatamente por isso que ele nasceu
   separado do `acelerado` (48 skills dão acelerado — a sombra vazaria em todas elas). */
.bt-sombra {
  position: absolute; pointer-events: none; z-index: -1;
  filter: brightness(.25) sepia(1) hue-rotate(240deg) saturate(3.2) brightness(1.5);
  animation: bt-sombra-respira 2600ms ease-in-out infinite !important;
}
/* ⚠️ POR QUE % E NÃO PX — o 16px que o Lucas escolheu era medido na MAQUETE, onde o sprite tinha
   128px e nada era escalado. Em campo a unidade inteira leva transform de escala (tamanho por
   espécie, party grande, viewport), e um deslocamento em px é multiplicado por essa escala:
   medido em sonda_sombra_sumiu.js, com o sprite a 273px na tela a sombra saía 2,3px do corpo —
   0,8% da largura, contra os 12,5% da maquete. Era por isso que ela sumia.
   12,5% = os mesmos 16px sobre os 128px de referência, agora imune a qualquer escala. */
.bt-sombra--esq { translate: -12.5% 0; animation-delay: -300ms !important; }
.bt-sombra--dir { translate:  12.5% 0; animation-delay: -900ms !important; }
@keyframes bt-sombra-respira { 0%,100% { opacity: .44 } 50% { opacity: .27 } }
@media (prefers-reduced-motion: reduce) {
  .bt-unit.bt-knock .bt-body, .bt-field.bt-shake { animation: none; }
  .bt-unit.bt-esquiva .bt-body, .bt-unit.bt-esquiva .cr-sprite,
  .bt-unit.bt-esquiva .mob-sprite { animation: none; }
  .bt-fantasma { display: none; }
  /* a sombra do Passo FICA (ela informa um buff ativo), só para de respirar */
  .bt-sombra { animation: none !important; opacity: .44; }
}
@keyframes bt-heal { 0%,100%{filter:none} 40%{filter:brightness(1.5) drop-shadow(0 0 10px rgba(120,220,90,.8))} }

/* corpo do monstro (placeholder CSS por cor/elemento) */
/* +30% GERAL (Lucas 13/07): sem as barras flutuantes no campo sobrou espaço → heróis e monstros
   crescem 30% em TODAS as vias de render (.cr, .cr-sprite, .mob-sprite), e o BOX cresce junto
   (box=visual → nome/aura ancoram certo). Valores antigos anotados em cada regra. */
.bt-mob { position: relative; width: 125px; height: 117px; } /* era 96×90 */
/* ═══ PLACA AO PÉ (layout do Lucas, 30/07) ═══════════════════════════════════════════════════════
   Ele desenhou: NOME acima da cabeça, VIDA/MANA ao PÉ do personagem, dentro da cena — nada de
   status "divididos" num rodapé. O posicionamento é por MEDIÇÃO (aplicaPlacas em battle.js): mede o
   bbox do alfa do frame visível, acha topo e base REAIS do desenho e ancora neles. Por isso aqui só
   tem aparência — nenhuma coordenada. Sprite alto e sprite baixo ganham a mesma placa no mesmo lugar
   relativo ao corpo, e o transform de escala do .bt-unit não desloca nada (o erro é medido e somado).
   O nome usa .pl-nome próprio em vez do .bt-uname porque o antigo vive no FLUXO (empurra o corpo). */
/* TETO DE LARGURA — o .bt-uname antigo tinha max-width (96px no PC, 70px no celular) + ellipsis, e
   a placa nova nasceu sem herdar isso: foi essa regressão, e não o texto em si, que deixou
   "Elemental de Fogo Corrompido ·Elite" ocupar 269px (29% da tela do Lucas). Com "Corrompido" e
   "·Raridade" fora, o pior nome real mede 134px — por isso o teto é 150 e não 70: nenhum nome de
   hoje é truncado (ele tem baixa visão; reticências são o último recurso) e nada pode estourar. */
.pl-nome { position: absolute; z-index: 14; pointer-events: none; white-space: nowrap;
  max-width: 150px; overflow: hidden; text-overflow: ellipsis;
  font: 600 10px/1.2 var(--font-title), system-ui, sans-serif; color: #f2e6c8;
  text-shadow: 0 1px 2px #000, 0 0 5px #000; }
.bt-unit.is-boss .pl-nome { color: #ffd24a; text-shadow: 0 0 8px rgba(255,200,60,.6), 0 1px 3px #000; }
/* CORROMPIDO (escolha do Lucas 30/07): contorno escuro cercando a letra + halo roxo por fora. Ele
   recusou a chapa preta atras do texto — "ficou muito chapado, quase como se tivesse sido colocado
   por engano". Isto tambem SUBSTITUI a palavra "Corrompido" no rotulo, que saiu para poupar largura:
   quem tem a letra cercada e roxa por fora e corrompido. paint-order poe o contorno ATRAS do
   preenchimento; sem ele o traco come metade da letra e o nome fica ilegivel. */
/* a COR fica por conta da raridade (escrita inline por montaPlacas); aqui só o contorno e o halo,
   que são o que marca a corrupção — por isso a regra não declara `color`. */
.bt-unit.corrupted .pl-nome { text-shadow: none;
  -webkit-text-stroke: 2.5px #07060a; paint-order: stroke fill;
  filter: drop-shadow(0 0 3px #2b123d) drop-shadow(0 0 9px rgba(90,40,150,.55)); }
/* HP e MP LADO A LADO com 1px de respiro (martelo Lucas: "gap mínimo para uma não sobrepor a outra").
   Colar de vez juntava as duas bordas douradas numa divisa grossa; 1px deixa cada uma fechada. */
.pl-barras { position: absolute; z-index: 14; pointer-events: none; display: flex; gap: 1px; }
.pl-barras .bar { flex: 1 1 0; height: 13px; position: relative; overflow: hidden;
  border: 1px solid #d9a441; border-radius: 3px; background: #241c14;
  box-shadow: 0 0 0 1px rgba(0,0,0,.7); }
.pl-barras .bar i { position: absolute; inset: 0; right: auto; transition: width .18s ease-out; }
.pl-barras .bar.hp i { background: linear-gradient(#8fc95c, #5f9636); }
.pl-barras .bar.mp i { background: linear-gradient(#5fa8dd, #3a6f9e); }
.pl-barras .bar.inimigo i { background: linear-gradient(#d4635a, #93342c); }
.pl-barras .bar span { position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  font-size: 10px; color: #fff; text-shadow: 0 1px 2px #000; font-variant-numeric: tabular-nums; }
.pl-barras .bar em { position: absolute; left: 3px; top: 50%; transform: translateY(-50%);
  font-size: 8px; font-style: normal; color: #d8cba6; text-shadow: 0 1px 2px #000; }
/* ═══ FAIXA DE STATUS DA PLACA (opção A, martelo Lucas 30/07: "embaixo da barra") ═══════════════
   Terceira peça da placa, irmã do nome e das barras. Posicionada por medição em posicionaPlacas(),
   como as outras — aqui só a aparência.
   O MARKUP vem de heroStatusChips() — o MESMO que alimentava o roster: .octo-sts > .ost > em + b.
   Reusar em vez de inventar um segundo formato foi decisão consciente: era exatamente a duplicação
   de caminhos que fez os status sumirem quando o roster caiu.
   O TAMANHO usa a régua --sti da casa (20px no PC, 16px no celular, definida logo abaixo do .sti) —
   mudar o ícone de status continua sendo mudar UM número, nos três lugares de uma vez.
   A CONTAGEM DE TURNOS vira selo no canto do ícone, e não um número ao lado como no roster: aqui a
   largura é o recurso escasso (a placa disputa espaço com o vizinho de cena), enquanto no painel
   lateral sobrava. `white-space: nowrap` + cap de 3 chips garantem uma tira de uma linha só. */
/* FILHA da .pl-barras: `top:100%` a cola embaixo da barra sem uma linha de JS de posição — ver o
   comentário em montaPlacas() sobre por que ela deixou de ser irmã. A barra tem overflow visível
   (só as .bar internas recortam), então a faixa aparece inteira para fora dela. */
/* z-index 13 = ABAIXO do nome e das barras (14), de propósito. O escape lateral resolve quase toda
   sobreposição, mas quando a cena está apertada demais para resolver, o que tem de vencer é o NOME:
   saber DE QUEM é a barra vale mais que ver o terceiro ícone de status. */
/* ═══ A COLUNA DE BAIXO (05/08) ═══════════════════════════════════════════════════════════════
   Antes existiam DOIS blocos disputando o mesmo ponto — o pé do monstro — cada um posicionado por
   um sistema diferente: a `.pl-status` pendurada na barra (absoluta) e a escada do bestiário
   `.bt-tgt` no FLUXO da unidade. Eles não se conheciam, então se atropelavam em toda mira. Foi o
   que o Lucas relatou: "aquela barra de informações que é desbloqueada pelo bestiário conflita
   com outras informações".
   Agora os dois são irmãos numa MESMA coluna flex pendurada na barra. Empilham por construção,
   com `gap` — não existe mais o que colidir, e some a necessidade de um segundo guarda
   anticolisão. Quem desliza para fugir da barra de ações é a coluna inteira (ver escapaStatus). */
.pl-abaixo { position: absolute; top: calc(100% + 2px); left: 50%; transform: translateX(-50%);
  z-index: 13; pointer-events: none; display: flex; flex-direction: column; align-items: center;
  gap: 2px; }
.pl-status { display: flex; align-items: center; white-space: nowrap; }
.pl-status .octo-sts { display: inline-flex; align-items: center; gap: 4px; }
.pl-status .ost { position: relative; display: inline-flex; align-items: center; }
.pl-status .ost > em { font-style: normal; font-size: calc(var(--sti) * 0.8); line-height: 1;
  display: inline-flex; align-items: center; filter: drop-shadow(0 1px 1px #000); }
/* TURNOS — canto de baixo. 8px→10px (05/08): a régua de fonte física da casa é 10px e este número
   estava abaixo dela desde que nasceu. O Lucas usa óculos; número de 8px no celular é decoração. */
.pl-status .ost > b { position: absolute; right: -3px; bottom: -3px; font-family: var(--font-title);
  font-size: 10px; line-height: 1.15; font-weight: 400; color: #fff; background: #1a1610;
  border: 1px solid #6b5a33; border-radius: 3px; padding: 0 2px; }
/* ═══ PILHAS — canto de cima (05/08) ═══════════════════════════════════════════════════════════
   O Sangrando empilha até 3× e TRIPLICA o dano por turno, e nenhum dos três renderizadores lia
   `st.stacks` — o chip mostrava só os turnos. Ou seja: a UI escondia o número que mais dói.
   Cantos opostos de propósito: turnos embaixo-direita (onde sempre estiveram), pilhas em
   cima-esquerda. Um número por canto, nunca dois disputando o mesmo lugar num chip de 16px.
   Só nasce quando stacks > 1 — status que não empilha continua com um número só. */
.pl-status .ost > u { position: absolute; left: -4px; top: -4px; font-family: var(--font-title);
  font-size: 10px; line-height: 1.15; text-decoration: none; color: #ffd9d2; background: #4a1712;
  border: 1px solid #9c3d31; border-radius: 3px; padding: 0 2px; }
/* "+N" — 9px→10px pela mesma régua. E ele agora RECEBE TOQUE: prometia abrir e não abria (medido:
   pointer-events none e nenhum handler no projeto inteiro). Área de 44px vem do ::after invisível,
   porque o chip desenhado tem 22×15 e crescer o desenho empurraria a fileira. */
.pl-status .ost.more { position: relative; font-family: var(--font-title); font-size: 10px; color: #e8c877;
  background: rgba(20,16,10,.8); border: 1px solid #6b5a33; border-radius: 3px; padding: 1px 3px;
  pointer-events: auto; cursor: pointer; }
.pl-status .ost.more::after { content: ""; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%); }
.pl-status .ost.more:active { background: rgba(60,48,26,.95); }
/* quando a fileira está aberta, o chip vira o botão de fechar */
.pl-status .ost.more.aberto { color: #141210; background: #e8c877; }
/* ═══ ROSÁRIO DE CHI NA PLACA, ACIMA DA BARRA (Lucas 30/07) ═════════════════════════════════════
   Irmã da faixa de status e filha da mesma .pl-barras, só que ancorada em bottom:100% em vez de
   top:100% — chi em cima, HP/MP no meio, status embaixo. Pendurada na barra, ela acompanha escala,
   respiro, clamp de borda e qualquer deslocamento do herói sem uma linha de JS de posição.
   A aparência das contas é a mesma de 28/07 (a arte que ele aprovou); só o lugar mudou. */
.pl-chi { position: absolute; bottom: calc(100% + 3px); left: 50%; transform: translateX(-50%);
  z-index: 13; pointer-events: none; display: flex; justify-content: center; }
.pl-chi .pl-chi-in { position: relative; display: flex; align-items: center; justify-content: center;
  gap: 3px; min-height: 10px; max-width: 118px; flex-wrap: wrap; }
.pl-chi .pl-chi-in::before { content: ""; position: absolute; left: 4px; right: 4px; top: 50%;
  height: 2px; transform: translateY(-50%); border-radius: 2px;
  background: linear-gradient(90deg, transparent, #5a4d33, #5a4d33, transparent); }
.pl-chi b { position: relative; width: 10px; height: 10px; border-radius: 50%; background: #0d0c11;
  box-shadow: inset 0 0 0 1px #4a4030, 0 0 0 1px #06050a; }
.pl-chi b.on { background: radial-gradient(circle at 35% 32%, #9ae8f5, #6ec6d9 55%, #2b7d90);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.35), 0 0 0 1px #0a3b45, 0 0 6px rgba(110,198,217,.8); }
.pl-chi b.marco { box-shadow: inset 0 0 0 1px #4a4030, 0 0 0 1px var(--gold,#d9b24c); }
.pl-chi b.on.marco { box-shadow: inset 0 0 0 1px rgba(255,255,255,.35), 0 0 0 1px var(--gold,#d9b24c), 0 0 7px rgba(212,169,78,.9); }
.pl-chi .pl-chi-in.muitos { gap: 2px; }
.pl-chi .pl-chi-in.muitos b { width: 7px; height: 7px; }
/* o herói morto perde a placa junto com o corpo */
.bt-unit.dead .pl-nome, .bt-unit.dead .pl-barras, .bt-unit.dead .pl-status, .bt-unit.dead .pl-chi { opacity: .35; }

/* ═══ BOTÕES DE AÇÃO SÓ-ÍCONE ═══════════════════════════════════════════════════════════════════
   56 → 39px (Lucas 30/07 de manhã: "30% menores") → 47px (Lucas 30/07 à tarde: "tem que crescer pelo
   menos uns 20%, está pequena, tanto no celular quanto no PC"). 39 × 1,2 = 46,8 → 47.
   O ÍCONE cresce junto, na mesma proporção (21 × 1,2 = 25,2 → 25), senão o botão engorda e o desenho
   fica boiando num vão maior. A BORDA também (8 → 10): botao_chip.png tem slice 26, e borda fina num
   botão maior deixa a moldura raquítica. Com 47 de caixa e 10 de cada lado sobram 27px de faixa
   central — longe do zero que gerou a moldura dupla do botao_acao.png documentada no shop.js.
   A moldura NÃO é nova: o .bt-btn já traz botao_chip.png no ::before (26 fill stretch) — pôr outra
   por cima foi o que gerou as duas molduras que ele apontou. Aqui só se dá forma quadrada ao botão. */
.bt-btn-ico { width: var(--acoes-h); height: var(--acoes-h); padding: 0; display: grid; place-items: center;
  background: none; border: 0; box-shadow: none; min-height: 0; gap: 0; }
.bt-btn-ico::before { border-width: 10px !important; }
.bt-btn-ico > img { width: 25px; height: 25px; image-rendering: pixelated; }

/* ═══ PORTE POR ESPÉCIE (242) — a ESCALA DE CLASSE (--sz) e a PROFUNDIDADE COSMÉTICA (--octo-s, 0.94/1.0 =
   ±6%, regra 1 do doc) compõem num único transform com origem no PÉ (50% 100%) → o mob escala POUSADO.
   As duas vars vêm do sizeLayout() (battle.js), que mede o render real (PNG/arte-texto/blob) e mira a
   altura-alvo da classe — por isso a MESMA regra vale pros 3 renders (fallback normalizado, regra 2).
   HERÓIS também recebem --sz = CÂMERA DE CENA (243, fator global ≥1) × escala de ENCONTRO (martelo 2, <1 só
   com colossal na cena) — o sizeLayout calcula os dois e ainda expõe --cam no .bt-field p/ as elipses. */
.bt-unit .bt-mob { transform: scale(calc(var(--sz, 1) * var(--octo-s, 1) * var(--hcal, 1))); transform-origin: 50% 100%; }
/* --hcal (23/07, martelo Lucas "igualar todos"): calibração POR HERÓI que iguala a ALTURA visível do
   personagem — as folhas preenchem o quadro 128 em 107-116px (arqueiro menor); --hcal leva todos a ~115px.
   Default 1 (mobs/invocados intocados). Setado inline no .bt-mob.hero-sprite (battle.js HERO_SCALE_CAL). */
.bt-unit.hero .bt-avatar { transform: scale(var(--sz, 1)); transform-origin: 50% 100%; }
/* ÂNCORAS ACOMPANHAM O PORTE: nome + barra fina + alvo-falante + seta de mira sobem juntos até a testa do
   sprite escalado via --lift (px, medido por unidade no sizeLayout — substitui os translateY fixos por
   classe); --liftx é o nudge lateral anti-colisão (rótulo nunca sobre o corpo do colossal vizinho). */
/* 29/07: .bt-tgt SAIU desta regra. O --lift existe pra SUBIR nome/barra até a testa de um sprite alto;
   agora que a escada de alvo mora ao PÉ do monstro (martelo do Lucas), herdar o lift a jogaria de volta
   por cima do corpo — exatamente o que ele mandou consertar. Ela mantém só o nudge lateral (--liftx),
   que é anti-colisão com o vizinho e continua valendo embaixo. */
.bt-unit.enemy .bt-uname, .bt-unit.enemy .bt-mob-hp { transform: translate(var(--liftx, 0px), var(--lift, 0px)); }
.bt-unit.enemy .bt-tgt { transform: translateX(var(--liftx, 0px)); }
.bt-unit.enemy.targetable::after { top: calc(-30px + var(--lift, 0px)); }
/* ═══ BARRA FINA DE HP DO MOB NA CENA (222) — o painel de INIMIGOS morreu ═══════════════════════════════════
   ~5px, SEM número, ancorada logo abaixo do nome (fica no fluxo da coluna do .bt-unit). Fração colorida (cor por
   hpFillCss no <i>), estilo aço fino casando com o kit. refresh() atualiza só a largura/cor. KO esmaece junto. */
.bt-unit.enemy .bt-mob-hp { position: relative; z-index: 6; width: 62px; height: 5px; margin: 1px 0 3px; border-radius: 3px; overflow: hidden;
  background: linear-gradient(180deg, #2b2f36, #14161b); box-shadow: inset 0 0 0 1px #05060a, inset 0 1px 0 rgba(255,255,255,.12), 0 1px 2px rgba(0,0,0,.6); }
.bt-unit.enemy .bt-mob-hp > i { position: absolute; left: 0; top: 0; bottom: 0; display: block; border-radius: 2px 1px 1px 2px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), inset 0 -2px 2px rgba(0,0,0,.35); transition: width .3s ease; }
/* TRAVA DE LARGURA DO NOME NA CENA (maquete 220, âncora de nome/barra): o nome do mob NUNCA cruza o vizinho —
   cap de largura + elipse. A barra fina de HP herda a mesma largura (não passa do nome). Sem isso, nomes longos
   ("Arqueiro Mercenário ·Incomum") transbordam e roçam o sprite ao lado (interseção). PC generoso / celular menor. */
.bt-unit.enemy .bt-uname { max-width: 96px; overflow: hidden; text-overflow: ellipsis; }
.bt-unit.enemy .bt-mob-hp { max-width: 96px; }
/* ═══ ALVO FALANTE (236, martelo Lucas: "é daquele jeito mesmo") — escada de conhecimento no ALVO MIRADO ═══
   .bt-tgt tem ALTURA ZERO no fluxo (o conteúdo transborda por um filho ABSOLUTO) → o box da coluna do mob
   fica IDÊNTICO (pés/nome/barra intocados, técnica provada na 234). Só aparece na MIRA (.targetable).
   Chips 12px com fundo 100% OPACO (régua 213: nada de transparência em texto informativo); largura presa
   ao cap do nome (96px) → não cruza o vizinho (padrão 234: 0 interseção). FRAQUEZA = dourada. */
/* 242: wrap 96→66px (COLUNA de chips) — com o espaçamento por footprint real os mobs ficam mais próximos
   e o wrap largo tocava o corpo do vizinho; a coluna estreita preserva os 12px físicos (régua 213) e zera
   a interseção (a suíte mede chip×corpo real). Os chips seguem o rótulo (--lift/--liftx). */
/* ⚠️ A `.bt-tgt` do FLUXO está APOSENTADA (05/08): era ela que caía por cima da faixa de status.
   O conteúdo dela agora nasce em `.pl-tgt`, dentro da coluna `.pl-abaixo` da placa. Mantida como
   `display:none` em vez de removida do HTML porque o box de altura zero dela ainda é contado pela
   régua dos pés no chão do celular — mesmo motivo da `.bt-statuses`. */
.bt-unit.enemy .bt-tgt { display: none !important; }
/* A escada agora só aparece na mira, como sempre — mas dentro da coluna da placa. */
.pl-tgt { display: none; }
.bt-unit.enemy.targetable .pl-tgt { display: block; }
/* COMPENSAÇÃO DE HORDA (242): o eScale que encolhia a .bt-unit inteira MORREU (o porte agora escala só o
   sprite via --sz; nome/barra/chips ficam em px cheios por natureza). A compensação 1/--esc fica como
   segurança (var nunca mais é setada → fator 1, neutro) — chips seguem 12px FÍSICOS em qualquer horda. */
/* ⚠️ O --tgx (nudge lateral) e o --tgy (subida quando vazava a base) MORRERAM aqui: os dois eram
   remendos de um bloco que se posicionava sozinho e vivia esbarrando nos vizinhos. Dentro da
   coluna da placa não há o que empurrar — a coluna inteira já é posicionada, escapada e presa na
   borda pelo mesmo código que cuida da barra de vida. Menos duas variáveis de posição. */
.bt-tgt-in { display: flex; flex-wrap: wrap; justify-content: center; align-content: flex-start;
  gap: 2px; width: 66px; }
.tgt-chip { padding: 1px 5px; border-radius: 6px; background: #0c0a07; border: 1px solid #55492f;
  font-family: var(--game-font); font-size: 12px; line-height: 1.2; color: #e8dcc0;
  white-space: nowrap; box-shadow: 0 1px 2px rgba(0,0,0,.65); }
.tgt-chip.weak { color: #ffd84a; border-color: #c8922a; font-weight: 700; }
.tgt-chip.hp { color: #fff; font-variant-numeric: tabular-nums; }
/* NÍVEL do mob no alvo mirado (06/08, martelo Lucas). Herda o .tgt-chip inteiro — mesmo tamanho de
   fonte (12px, acima da régua de 10px), mesmo padding, mesma borda. Só o número recebe tabular-nums,
   pra "Nv 8" e "Nv 10" ocuparem a mesma largura e o chip não pular de tamanho entre um mob e outro.
   .oculto = ainda sem Marco de bestiário: fica apagado, para LER como informação por conquistar em
   vez de parecer defeito. */
.tgt-chip.nv { color: #e8dcc0; font-variant-numeric: tabular-nums; font-weight: 700; }
.tgt-chip.nv.oculto { color: #8a8069; border-color: #3f3728; }
/* CAÇADOR III (100 abates): filete dourado na barra de cena — a peça .dominada RELIGADA (era da .bt-hpbar
   aposentada). Sobrepõe o contorno escuro que a barra já tem, sem tocar a geometria (box-shadow). */
.bt-unit.enemy.dominada .bt-mob-hp { box-shadow: inset 0 0 0 1px #05060a, inset 0 0 0 2px rgba(216,173,90,.7), 0 1px 2px rgba(0,0,0,.6); }
/* mob GRANDE/COLOSSO: nome sobe (transform) p/ escapar da arte alta — a barra segue no fluxo, logo abaixo do nome. */
/* CÍRCULO DE RARIDADE: elipse colorida pulsante sob o mob (só não-comum) — diferencia mobs raros */
.bt-unit .bt-body { position: relative; }
/* 243: a aura escala com a CÂMERA DE CENA (--cam, setado no .bt-field pelo sizeLayout) — o corpo cresce via
   --sz e a elipse acompanha (origem no pé; o pulse compõe o mesmo fator). Nome/barra/chips seguem FÍSICOS. */
/* 30/07 — A AURA VIROU ARTE: círculo de fogo do PixelLab, uma cor por raridade (escolha do Lucas),
   no lugar da elipse desenhada em CSS. A posição de cada criatura vem de AURA_FIX (js/mob-battle-sprites),
   que ele calibrou nos 77 um a um; o battle.js escreve --au-w/--au-h/--au-x/--au-y por unidade. */
/* Sem transform aqui de propósito: quem posiciona é aplicaAuras() (battle.js), que MEDE o sprite já
   escalado e escreve left/top/width/height em px. Um translate ou um scale nesta regra entraria em
   cima da medição e reintroduziria o descolamento que a medição existe para eliminar. */
.bt-rar-aura { position: absolute; left: 0; top: 0; z-index: -1; pointer-events: none;
  width: var(--au-w, 86px); height: var(--au-h, 24px);
  background: center/100% 100% no-repeat; image-rendering: pixelated;
  border: 0; box-shadow: none; border-radius: 0; opacity: .92; animation: none; }
@keyframes bt-rar-pulse { 0%, 100% { opacity: .4; transform: translateX(-50%) scale(var(--cam, 1)) scaleX(.93); } 50% { opacity: .72; transform: translateX(-50%) scale(var(--cam, 1)) scaleX(1.05); } }
/* BLOB fallback (242): o scale fixo 1.35 do "quadradão" MORREU — o blob agora é NORMALIZADO pela classe da
   espécie via --sz (sizeLayout mede os 117px nativos e mira a altura-alvo), regra 2 do doc de tamanhos. */
/* HERÓIS no PC: −30% do tamanho (box=visual → nome/barra acompanham). O default do .cr é --px:6px; 6×0.7=4.2px.
   O @media do celular (mais abaixo) sobrescreve com 2.55px. */
.bt-mob.hero-art.has-art .cr { --px: 7.1px; } /* era 5.46 → +30% (pedido Lucas 13/07: herói +30% no campo) */
/* MONSTRO com arte CSS (creature-art) no PC (242): --px NORMALIZADO pelo grid (--gh vem inline do
   renderCreature) → a arte-texto nasce na altura do CORPO do herói (~119px) e o --sz da classe faz o resto.
   Mata o "sem-PNG gigante": rei_goblin/warlord/dragao/golem_runico/quimera/elemental_fogo entram na régua.
   Escopado à batalha — glossário/ficha NÃO mudam. */
.bt-mob.has-art:not(.hero-art) .cr { --px: calc(119px / var(--gh, 17)); }
/* ── HERÓIS por SPRITE-SHEET aprovado (js/hero-battle-sprites.js) ── ADITIVO: convive com o .cr antigo
   (fallback) e com o bob/lunge/hit/heal do .bt-body (que animam o PAI, não o filho .cr-sprite).
   REGRA DE OURO: --scale casa a ALTURA do box com o .cr antigo (17 linhas × 4.2px = 71.4px ≈ 128×0.558),
   então nome/barras/números não saem do lugar — trocamos só a TÉCNICA de render, não a métrica. */
/* TUNING Lucas 11/07: BUFF de ~12% no herói (0.558 → 0.625). Altura do box 128×0.625 = 80px (era 71.4).
   As barras/nome acompanham (flex column) — provado 0 sobreposição entre os 5 heróis (zigue-zague do PARTY_POS
   dá ~190px de folga vertical, buff só consome ~4px por lado). */
.bt-mob.hero-sprite.has-art .cr-sprite { --scale: 1.05; } /* era 0.81 → +30% (pedido Lucas 13/07) → 128×1.05 ≈ 134px */
.cr-sprite {
  --fw: 128px; --fh: 128px; --scale: 0.558; --total: 1; --start: 0; --frames: 1;
  width: calc(var(--fw) * var(--scale)); height: calc(var(--fh) * var(--scale));
  /* background-image vem INLINE do renderHero (url() em var resolveria relativo ao css/ → 404). */
  background-repeat: no-repeat;
  background-size: calc(var(--total) * var(--fw) * var(--scale)) calc(var(--fh) * var(--scale));
  background-position-x: calc(var(--start) * var(--fw) * var(--scale) * -1); background-position-y: 0;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
@keyframes hero-strip {
  from { background-position-x: calc(var(--start) * var(--fw) * var(--scale) * -1); }
  to   { background-position-x: calc((var(--start) + var(--frames)) * var(--fw) * var(--scale) * -1); }
}
/* VFX de skill. O runtime controla cada frame pela timeline aprovada. */
.skill-vfx {
  position: absolute;
  pointer-events: none;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  will-change: background-position, transform, left, top;
}

.skill-vfx-field { z-index: 4; max-width: 100%; max-height: 100%; }
.skill-vfx-proj { z-index: 5; }
.skill-vfx-projectile-wrapper { width: 0; height: 0; z-index: 5; transform-origin: 0 0; }
.skill-vfx-area-wave { z-index: 5; transform-origin: center; }
.skill-vfx-unit { z-index: 5; }
.skill-vfx-prison-under { z-index: 1; }
.bt-body > .bt-mob,
.bt-body > .bt-avatar,
.bt-body > .bt-breathe { position: relative; z-index: 2; }
.skill-vfx-prison-over { z-index: 5; }
.skill-vfx-character { z-index: 5; }
.skill-vfx-travel-wrapper { width: 0; height: 0; z-index: 5; }
.skill-vfx-travel-character { z-index: 5; }
/* A semente PLANTADA pertence ao chão: z-index NEGATIVO a mantém ATRÁS das unidades (.bt-unit é
   z-index auto), então nunca cobre sprite de herói mesmo num campo apertado. Ver startSeedStatus. */
.skill-vfx-seed-status { z-index: -1; }

.bt-body.skill-vfx-character-active > .bt-mob,
.bt-body.skill-vfx-character-active > .bt-avatar {
  visibility: hidden;
}

.bt-unit.attacking .bt-body.skill-vfx-character-active {
  animation: none !important;
}
/* SPRITE DE MONSTRO (PixelLab, PNG único). Reusa a engine .cr-sprite (frame único pelos defaults).
   --scale enche o .bt-mob (96×90) como os heróis; FLIP scaleX(-1) → gerados p/ direita, encaram p/ esquerda. */
/* TUNING Lucas 11/07 — escala de MOB calibrada com o herói BUFADO (80px no PC):
   base 0.66→0.60 (128×0.60 = 76.8px ≈ herói, "coerente"; antes 84.5px = +18% vs herói, o "tico grande");
   big 0.82→0.72 (92.2px, o colosso T4/T5 fica CONTIDO — antes com o bug do 1.35 ia a ~142px);
   boss 0.95→0.82 (105px, ainda o maior, ~1.31× herói). max-* = TRAVA de segurança: mesmo que uma
   fornada futura marque um --scale exagerado, o sprite não passa de ~150px (nem 40vh) e não cobre vizinho. */
/* +30% (13/07): base 0.60→0.78 (100px). Rodada 2 (Lucas): BIG +25% em cima (0.94→1.18, 151px),
   boss acompanha a hierarquia (1.07→1.34, 171px) e nasce o tier COLOSSO (dragão/hidra, +50% sobre
   o big antigo: 1.41, 180px) — classe setada no mobKind (battle.js). Trava 195px/46vh segura tudo. */
/* --mob-dx/--mob-dy: ESQUADRO DO PÉ (mapa em mob-battle-sprites.js) — alinha o PÉ do desenho ao
   centro/linha da aura. translate vem ANTES do scaleX na cadeia → age em ESPAÇO DE TELA (pós-flip),
   e o % escala junto com o tamanho do elemento (vale em qualquer --scale). */
/* 242: base do PNG = MESMA caixa do herói (1.05 → medio idêntico, martelo 1). As travas max-* morreram
   (cortariam o colossal 3× e o grande 1.5×); a escala agora é 100% determinística pela classe (--sz do
   sizeLayout) — os overrides big/boss/colosso/pequeno e o .crowded foram aposentados junto com o regex. */
.mob-sprite { --scale: 1.05; transform: translate(var(--mob-dx, 0%), var(--mob-dy, 0%)) scaleX(-1); transform-origin: center; }
/* 29/07 (Lucas: "os monstros têm que observar para o lado esquerdo"): os três sprites que já foram
   desenhados olhando pra ESQUERDA levam .no-flip (ver OLHA_ESQUERDA em js/mob-battle-sprites.js) —
   sem isso o scaleX(-1) universal os deixava de costas pro grupo. */
.mob-sprite.no-flip { transform: translate(var(--mob-dx, 0%), var(--mob-dy, 0%)); }
/* ESPÍRITO DA MATA (pet do Druida, 28/07): fica do lado dos HERÓIS e encara os inimigos → SEM o flip
   dos mobs (o arquivo já olha para a direita). Menor que um mob: é uma criatura de apoio. */
.summon-sprite { --scale: .62; transform-origin: bottom center; }
/* 30/07 — as regras de porte (sz-miudo/pequeno/grande/colossal) que fixavam width/height/bottom da aura
   MORRERAM. Quem manda agora é AURA_FIX: o Lucas calibrou os 77 um a um no editor de campo, e o JS
   escreve --au-w/--au-h/--au-x/--au-y por criatura. Uma tabela medida vence quatro faixas chutadas. */
.bt-mob .body { position: absolute; inset: 8px 10px 6px; border-radius: 48% 48% 44% 44% / 54% 54% 46% 46%;
  box-shadow: inset -8px -10px 18px rgba(0,0,0,.45), inset 6px 8px 14px rgba(255,255,255,.16), 0 10px 14px -6px rgba(0,0,0,.55); }
.bt-mob .glow { position: absolute; inset: -4px; border-radius: 50%; filter: blur(11px); opacity: .5; z-index: -1; }
.bt-mob .eye { position: absolute; top: 40%; width: 14px; height: 16px; border-radius: 50%; background: radial-gradient(circle at 40% 35%, #fff, #d8d2c0 60%); box-shadow: 0 0 7px rgba(255,255,255,.5); }
.bt-mob .eye::after { content:""; position:absolute; left:50%; top:52%; width:6px; height:7px; transform:translate(-50%,-50%); border-radius:50%; background:#1a1420; }
.bt-mob .eye.l { left: 30%; } .bt-mob .eye.r { right: 30%; }
/* avatar do herói */
.bt-avatar { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 26px; color: #120c18;
  box-shadow: inset -4px -6px 12px rgba(0,0,0,.4), inset 4px 6px 12px rgba(255,255,255,.32), 0 8px 14px -4px rgba(0,0,0,.5); }

/* nome ACIMA da cabeça; barras (HP/mana) no PÉ — z-index p/ ficarem SEMPRE na frente do sprite
   (mob grande é alto e tem transform → criava contexto de empilhamento e cobria a barra). */
.bt-uname { position: relative; z-index: 6; font-size: 11.5px; font-weight: 700; text-shadow: 0 1px 3px #000; white-space: nowrap; margin-bottom: 5px; }
/* ═══ P1 (236, martelo Lucas) — CENA LIMPA: nome flutuante + emojis de status dos HERÓIS saem da cena
   (a informação vive na LINHA DO NOME do roster Octopath). MOBS ficam (nome/status deles só existem na
   cena). TÉCNICA: visibility (não display) — o BOX continua ocupando o MESMO espaço no fluxo da coluna,
   então a régua dos pés no chão (margin-top negativo do celular conta com a faixa .bt-statuses) fica
   INTOCADA — box idêntico provado na prancha 234. INVOCADO (.summon) preserva o rótulo: ele não entra
   no roster, a cena é o único lugar da vida/nome dele. */
.bt-unit.hero:not(.summon) .bt-uname { visibility: hidden; }
/* ═══ A FILEIRA ANTIGA DE STATUS MORREU PARA TODO MUNDO (05/08) ═════════════════════════════════
   BUG que o Lucas viu e mandou eu procurar sem dizer o quê. Medido no jogo rodando, com 8 status
   num mob: ele mostrava a lista em DOIS lugares ao mesmo tempo — a `.bt-statuses` (8 ícones, sem
   contagem de turnos e SEM TETO) e a `.pl-status` da placa (3 chips + "+5"). As duas se cobriam em
   30% no PC, e DISCORDAVAM entre si sobre o que o bicho tinha.
   O herói já estava certo: a regra acima o desligou quando a placa nasceu. O inimigo e o invocado
   ficaram de fora do seletor, e ninguém percebeu porque a placa cobria 81% da fileira velha (isso
   está anotado em battle.js:1354 desde 30/07 — a evidência estava escrita e não virou conserto).
   Agora a fileira sai da tela para TODOS. Quem mostra status é a placa, e só ela.
   ⚠️ `visibility`, não `display`: o BOX tem que continuar ocupando o mesmo espaço, porque a régua
   dos pés no chão (margin-top negativo do celular) conta com a altura da .bt-statuses. Trocar por
   display:none sobe todo mundo alguns pixels e quebra a prancha 234. */
.bt-statuses { visibility: hidden; }
/* barras mais altas (redesign 10/07: HP 13px/mana 10px, era 7/4) empurram um pouco mais o conjunto pro
   PÉ do sprite — gap e margem sobem junto pra não ficar espremido contra o corpo nem contra .bt-statuses. */
.bt-bars { position: relative; z-index: 6; display: flex; flex-direction: column; align-items: center; gap: 3px; width: 108px; margin-top: 12px; }/* 7→12 (13/07): +5px afasta a barra do pé do sprite, pedido do Lucas ("um pouco, nada drástico"). Só desktop — o mobile tem regra própria abaixo. */
.bt-uname .rar { font-size: 9.5px; }
/* MINI-BARRA do INVOCADO (fix 16/07): vida do espírito NÃO vai pro painel do GRUPO — fica numa barrinha
   fina, verde, com moldura escura, discreta, sobre o próprio sprite (padrão da régua atual dos rosters). */
/* 243: nome do invocado sobe o quanto o avatar cresceu com a câmera (--sumlift, setado no addSummonDOM/
   sizeLayout) — âncora acompanha; sem câmera a var é 0 e nada muda. */
.bt-unit.summon .bt-uname { transform: translateY(calc(0px - var(--sumlift, 0px))); }
.bt-unit.summon .bt-summonbar { position: relative; z-index: 6; width: 46px; height: 5px; margin-top: 3px;
  background: #15130f; outline: 1px solid #55492f; box-shadow: 0 0 0 2px #0b0a07, inset 0 0 0 1px #2a2418; border-radius: 2px; overflow: hidden; }
.bt-unit.summon .bt-summonbar > i { display: block; height: 100%; background: linear-gradient(180deg, #82c653, #4e8a2e); transition: width .3s ease; }
/* BÔNUS DE BESTIÁRIO — badge do Marco de Caçador do grupo contra este tipo (payoff visível do farm). */
.bt-know { position: relative; z-index: 6; display: inline-block; margin-bottom: 4px; padding: 1px 6px; border-radius: 8px;
  font-size: 9px; font-weight: 700; color: #f0e2b0; background: rgba(40,32,14,.72); border: 1px solid rgba(236,208,122,.5);
  text-shadow: 0 1px 2px #000; white-space: nowrap; letter-spacing: .2px; }
/* CHEFE (categoria Boss = combatRole/raridade 'boss'): coroa + nome dourado + brilho no sprite. */
.bt-unit.is-boss .bt-uname { color: #ffd24a; text-shadow: 0 0 8px rgba(255,200,60,.6), 0 1px 3px #000; }
.bt-unit.is-boss .bt-uname::before { content: "\265B "; color: #ffe487; } /* ♛ coroa */
/* 30/07 — HALO DE RARIDADE animado ("tipo Dragon Ball", pedido do Lucas). Três camadas que respiram
   fora de compasso, na cor --rc da raridade. Liga em chefe, mini-chefe, alvo mirado e hover. 3,5s.
   ⚠️ --rc é escrito na .bt-unit, não na .bt-rar-aura. Estava na aura, que é IRMÃ do .bt-mob — e
   custom property só desce de pai para filho, nunca de lado. O halo caía no fallback dourado para
   TODA raridade, que foi o "sumiu o brilho" que o Lucas notou (30/07). */
@keyframes bt-halo-rar {
  0%,100% { filter: drop-shadow(0 0 4px var(--rc, #ffcd46))
    drop-shadow(0 0 10px color-mix(in srgb, var(--rc, #ffcd46) 70%, transparent))
    drop-shadow(0 0 18px color-mix(in srgb, var(--rc, #ffcd46) 30%, transparent)); }
  45% { filter: drop-shadow(0 0 7px var(--rc, #ffcd46))
    drop-shadow(0 0 17px color-mix(in srgb, var(--rc, #ffcd46) 85%, transparent))
    drop-shadow(0 0 30px color-mix(in srgb, var(--rc, #ffcd46) 45%, transparent)); }
  70% { filter: drop-shadow(0 0 5px var(--rc, #ffcd46))
    drop-shadow(0 0 13px color-mix(in srgb, var(--rc, #ffcd46) 75%, transparent))
    drop-shadow(0 0 22px color-mix(in srgb, var(--rc, #ffcd46) 35%, transparent)); }
}
.bt-unit.is-boss .bt-mob, .bt-unit.rar-miniboss .bt-mob,
.bt-unit.enemy.targetable:hover .bt-mob, .bt-unit.enemy.alvo .bt-mob {
  animation: bt-halo-rar 3.5s ease-in-out infinite; will-change: filter; }
.bt-unit.is-boss .bt-hpbar { height: 13px; box-shadow: inset 0 0 0 1px rgba(255,210,90,.5); }
/* ══ AURA DE CORRUPÇÃO (Bíblia Visual §4): preto + roxo, veias pulsantes. Sobre o cenário escuro de Mori. ══ */
.bt-unit.corrupted .bt-mob { filter: drop-shadow(0 0 6px rgba(122,63,208,.85)) drop-shadow(0 0 12px rgba(40,10,60,.9)); animation: bt-corrupt-pulse 1.7s ease-in-out infinite; }
.bt-unit.corrupted .bt-uname { color: #c69bff; text-shadow: 0 0 8px rgba(122,63,208,.75), 0 1px 3px #000; }
.bt-unit.corrupted .bt-uname::before { content: "\2620 "; color: #a05ad0; } /* ☠ caveira */
.bt-unit.corrupted .bt-rar-aura { border-color: rgba(122,63,208,.85) !important; box-shadow: 0 0 14px 4px rgba(80,20,120,.7); }
/* halo roxo-escuro atrás do corpo, independente da aura de raridade (todo corrompido tem, mesmo common) */
.bt-unit.corrupted .bt-body::before { content: ""; position: absolute; left: 50%; top: 50%; width: 78%; height: 78%; transform: translate(-50%,-50%); border-radius: 50%; z-index: -2; pointer-events: none; background: radial-gradient(circle, rgba(122,63,208,.55), rgba(20,4,34,.15) 62%, transparent 72%); filter: blur(6px); animation: bt-corrupt-pulse 1.7s ease-in-out infinite; }
@keyframes bt-corrupt-pulse { 0%,100% { opacity: .7; } 50% { opacity: 1; filter: drop-shadow(0 0 10px rgba(140,70,230,1)) drop-shadow(0 0 16px rgba(50,12,80,.95)); } }
/* VELOCIDADE VIVA (28/07, martelo Lucas): medalhão que TROCOU de posição na fita (Lento/Acelerado
   pousou) dá um pulinho — feedback instantâneo da nova ordem, sem texto. */
.bt-med.hop { animation: bt-fita-hop .5s ease; }
@keyframes bt-fita-hop { 0%, 100% { transform: translateY(0); } 35% { transform: translateY(-8px); } 70% { transform: translateY(2px); } }
.bt-unit.is-boss .bt-hpbar > i { background: linear-gradient(90deg,#c0271b,#ff7a4a); }
/* SKIN PixelLab (barra_frame.png) — moldura via border-image DIRETO no elemento (box-sizing:border-box
   mantém a ALTURA total idêntica; a borda só reparte espaço interno pro preenchimento <i>). Slice 7/26
   (não 14): a arte tem um glitch de brilho fantasma nas linhas 8–10 (resquício de um estado preenchido
   anterior) — CORRIGIDO direto no PNG (repintado p/ a cor plana do trilho) + slice mais justo no eixo
   vertical (7) pra nunca puxar linhas do meio do trilho pra dentro da borda superior/inferior. */
/* REDESIGN 10/07 (pedido Lucas): HP sobe 7→13px, mana 4→10px (ainda menor que a vida, mas não mais
   "fio de linha"). O BUG da moldura ("mana comprime diferente") era o border-width horizontal divergir
   (hp 3px vs mana 2px) com a MESMA fatia de imagem (7 26) — moldura da mana renderizava mais fina/achatada
   que a da vida. Fix: border-width IDÊNTICO (1px 3px) nas duas → mesma espessura de moldura, só a altura
   total (logo o "trilho" de preenchimento no meio) muda entre HP e mana. */
.bt-hpbar { width: 100%; height: 13px; border-radius: 4px; background: rgba(0,0,0,.55); box-shadow: inset 0 0 0 1px rgba(0,0,0,.5); overflow: hidden;
  box-sizing: border-box; border-width: 1px 3px; border-style: solid; border-color: transparent;
  border-image: url("../assets/ui/pixellab/barra_frame.png") 7 26 stretch; }
.bt-hpbar > i { display: block; height: 100%; width: 100%; background: linear-gradient(90deg,#c0392b,#e0705a); transition: width .35s ease; }
.bt-unit.hero .bt-hpbar > i { background: linear-gradient(90deg,#9bf07e,#2f8c28); }
/* inimigo comum fica ligeiramente menor que herói/chefe (~11px, "proporcional" — pedido Lucas); chefe
   (.is-boss, acima) iguala o herói em 13px — a distinção dele já vem da coroa/nome dourado/brilho. */
.bt-unit.enemy .bt-hpbar { height: 11px; }
.bt-manabar { width: 100%; height: 10px; border-radius: 3px; background: rgba(0,0,0,.5); overflow: hidden;
  box-sizing: border-box; border-width: 1px 3px; border-style: solid; border-color: transparent; /* era 1px 2px — moldura mais fina que a da vida */
  border-image: url("../assets/ui/pixellab/barra_frame.png") 7 26 stretch; }
.bt-manabar > i { display: block; height: 100%; background: linear-gradient(90deg,#7ea8f0,#2f55a0); transition: width .35s; }
.bt-statuses { display: flex; gap: 3px; height: 20px; margin-top: 4px; align-items: center; } /* 15→20: a arte de status (--sti) é maior que o emoji de 13px que morava aqui */
.bt-statuses span { font-size: 13px; filter: drop-shadow(0 1px 1px #000); display: inline-flex; align-items: center; }

/* ══ NÚMEROS de HP/mana nas barras (pedido Lucas 10/07) — GEOMETRIA IDÊNTICA: .bt-barwrap só existe pra
   dar um ancestral position:relative ao número; ele não tem padding/margin próprios, então a barra
   (.bt-hpbar/.bt-manabar) fica com a MESMA altura/borda de sempre — o número é uma camada ABSOLUTA por
   cima (position:absolute; inset:0), fora do fluxo, então nem sequer entra na altura do wrap. Como a
   barra é mais baixa (7/4px) que o texto (8-9px), a camada de número fica FORA do .bt-hpbar (que segue
   com overflow:hidden só pro preenchimento <i>) — assim o dígito não é cortado pelo clip da barra.
   width:100% é OBRIGATÓRIO aqui: .bt-bars é flex-column com align-items:center (default), então um filho
   flex SEM largura própria encolhe pro conteúdo — sem isso o wrap colapsava pra só a borda (~6px) e
   arrastava a barra junto (bug pego no teste de geometria antes/depois). */
.bt-barwrap { position: relative; width: 100%; }
.bt-hp-num, .bt-mana-num {
  position: absolute; inset: 0; z-index: 7; display: flex; align-items: center; justify-content: center;
  font-size: 8px; line-height: 1; font-weight: 700; color: #fff; letter-spacing: .2px; white-space: nowrap;
  text-shadow: 0 1px 2px #000, 0 1px 2px #000, 0 0 3px #000; pointer-events: none; font-variant-numeric: tabular-nums;
}
/* ESPÉCIE DOMINADA (3º Marco de bestiário, ≥100 abates — 🔨 MARTELO Lucas 22/07; era 200, antes 2500):
   regra LEGADA da .bt-hpbar aposentada da cena — a versão VIVA do filete mora na .bt-mob-hp (ver bloco
   ALVO FALANTE mais acima). Mantida por segurança p/ qualquer .bt-hpbar remanescente. */
.bt-unit.dominada .bt-hpbar { box-shadow: inset 0 0 0 1px rgba(0,0,0,.5), inset 0 0 0 2px rgba(216,173,90,.65); }

/* número de dano/efeito flutuante */
/* LAYER dos floats (fix Lucas 21/07): números vivem AQUI, irmãos das unidades (não filhos de `.bt-unit`),
   pra que `.bt-unit.dead {opacity:.18}` da unidade morta NÃO componha/apague o número do golpe fatal.
   O float nasce ancorado (left/top inline) na posição da unidade no instante do spawn — ver Battle.floatOn. */
.bt-floats { position: absolute; inset: 0; pointer-events: none; z-index: 6; }
.bt-float { position: absolute; top: 4px; left: 50%; transform: translateX(-50%); font-weight: 800; font-size: 22px; text-shadow: 0 2px 4px #000, 0 0 5px rgba(0,0,0,.6); pointer-events: none; animation: bt-floatup 1.05s ease-out forwards; z-index: 6; }
.bt-float.crit { font-size: 30px; }
.bt-float.miss { color: #cfc6b4; font-size: 17px; }
/* ONDA 4 (28/07, auditoria Q35): as classes cosméticas dos floats NÃO EXISTIAM — 4 dos 6 tipos de número
   saíam idênticos ao dano. Cada tipo ganha cara própria (tamanho/peso/deslocamento leve). */
.bt-float.shield { font-size: 15px; font-weight: 700; margin-left: 14px; }
.bt-float.resist { font-size: 14px; font-weight: 700; font-style: italic; }
.bt-float.regen { font-size: 17px; }
.bt-float.reflect { font-size: 15px; margin-left: -14px; }
.bt-float.soak { font-size: 15px; margin-left: 10px; }
@keyframes bt-floatup { 0%{opacity:0; transform:translate(-50%,10px)} 15%{opacity:1} 68%{opacity:1; transform:translate(-50%,-26px)} 100%{opacity:0; transform:translate(-50%,-40px)} }

/* painel de ação (base) — SKIN PixelLab (hud_panel.png) via ::before absoluto (inset:0), atrás do
   conteúdo: o box-model (padding/altura) do painel continua o MESMO de antes, só a pele muda por baixo
   (regra de ouro do Lucas: geometria idêntica, HUD não pode invadir a tela nem quebrar celular/PC). */
/* ════════════════ PAINEL DE AÇÃO 222 (maquete 220 "Octopath" aprovada Lucas 22/07) ════════════════
   2 ZONAS: GRUPO (roster Octopath SÓ-heróis, 2 col) | CENTRO (menu-mini + arena de skills/mensagens).
   O PAINEL DE INIMIGOS MORREU — a vida do mob é a barra fina na CENA. Skin PixelLab hud_panel.png (::before)
   preservada. Base = PC (medidas generosas); o @media(max-height:500px) reescreve p/ o CELULAR (812×375, régua
   mobile-first). Selectors legados .bt-turn/.bt-actions/.bt-detail seguem vivos aqui dentro. */
.bt-panel { position: absolute; left: 0; right: 0; bottom: 0; height: 190px; z-index: 2; background: #0b0d10cc; border-top: 0; } /* 265→190: painel só-heróis (222) → cena maior */
.bt-panel::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border: 34px solid transparent; border-image: url("../assets/ui/pixellab/hud_panel.png") 40 fill stretch; }
/* 2 colunas [roster Octopath | centro]. PC generoso: roster 340px. */
.bt-panel-in { position: relative; z-index: 1; height: 100%; display: grid; grid-template-columns: 340px 1fr; gap: 14px; padding: 10px 16px 8px; }
.bt-allies { min-width: 0; }
/* zona do GRUPO = só o roster Octopath (sem título lateral) */
/* ZONA vira LINHA (título lateral + roster) — CARTÕES M3 aprovados Lucas 13/07: o título GRUPO/INIMIGOS
   saiu de cima (gastava altura) e virou faixa VERTICAL na lateral, como na maquete 111. */
.bt-zone { min-width: 0; display: flex; flex-direction: row; align-items: stretch; gap: 6px; }
/* INIMIGOS espelha: roster à esquerda, título vertical à DIREITA (row-reverse coloca o título — 1º filho — no fim) */
.bt-enemies { flex-direction: row-reverse; }
.bt-ztitle { writing-mode: vertical-rl; transform: rotate(180deg); align-self: center; text-align: center; margin: 0;
  font-family: var(--font-title); font-size: 9px; color: #e6cf8f; letter-spacing: 1px; }
.bt-enemies .bt-ztitle { color: #e79a7a; }
/* roster = coluna de cartões colados (gap 4px da maquete); centralizado na altura, cartões abraçam o conteúdo */
.bt-roster { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 4px; justify-content: center; align-items: flex-start; }
/* GRUPO: cartões PREENCHEM a largura da zona (como o mock 111 — repeat(5,1fr) em 240px) → a borda direita
   do cartão ENCOSTA nos chips (gap ~12px, não os 55px do left-hug). Só o GRUPO; INIMIGOS segue left-hug
   (comment acima) pra puxar pro centro e sair da borda ornamentada da direita. */
.bt-allies .bt-roster { align-items: stretch; }

/* ═══ ROSTER OCTOPATH (maquete 220 aprovada Lucas 22/07) — 2 colunas, tanque-primeiro, número DENTRO da barra ═══
   Base = PC (medidas generosas); o @media(max-height:500px) reescreve p/ o celular (812×375, régua mobile-first:
   tanque barra 18/nº 13/folga 2.5 · pares 16/12/2). Célula LIMPA (sem moldura painterly). O .octo vence o
   .hr/.mr legado por especificidade (.ally-roster.octo .hr). */
.ally-roster.octo { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; align-content: center; height: 100%; }
/* 🔨 MARTELO 1 (230, 22/07) — matriz V-C da maquete 228 com o RETRATO+ANEL da V1 (227) nas 5 células:
   a célula vira GRADE [retrato | nome/barras] (retrato col-1 ocupa as 2 linhas). --oport dimensiona o anel
   (PC 36/30; celular 32/26 pela matriz V-C). row-gap = a antiga folga vertical nome↔barras. */
.octo .hr { display: grid; grid-template-columns: var(--oport, 30px) minmax(0, 1fr); grid-template-rows: auto auto;
  column-gap: 7px; row-gap: 3px; align-content: center; align-items: center; padding: 4px 10px 5px; border-radius: 8px;
  background: linear-gradient(180deg, rgba(28,24,17,.62), rgba(12,10,7,.72)); box-shadow: inset 0 0 0 1px rgba(214,178,90,.16), 0 1px 3px rgba(0,0,0,.5); }
/* ═══ P1 (236) — LINHA 1 = [nome | chips de status] (variante A da 234, martelo Lucas 22/07) ═══
   .octo-l1 é FLEX: o nome encolhe com ellipsis (min-width:0), os chips têm coluna própria (flex:none) —
   os efeitos ficam NA MESMA LINHA do nome e NUNCA descem sobre as barras (que seguem na row 2 do grid). */
.octo .octo-l1 { grid-column: 2; grid-row: 1; align-self: end; display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.octo .octo-l1 .octo-name { flex: 0 1 auto; min-width: 0; }
/* chips: ícone (em) + CONTAGEM DE TURNOS (b). Régua baixa visão: contagem NUNCA <10px físico no celular. */
/* ═══ ARTE DE STATUS (28/07, opção C do Lucas) ═══════════════════════════════════════════════════
   --sti é a régua ÚNICA do ícone de status: mude aqui e os três lugares (cena, painel do grupo, chip
   do alvo) acompanham juntos. PC 20px (o espaço que o Lucas apontou), celular 16px (@media abaixo).
   Por que 16 e não menos: pixel art reduzido de 64px só ganha do emoji a partir de ~16px — abaixo
   disso o emoji lê melhor, medido lado a lado na prancha 323. align-self:center porque <img> não tem
   linha de base como o emoji tinha — sem isso o ícone "afunda" em relação à contagem. */
:root { --sti: 20px; }
.sti { width: var(--sti); height: var(--sti); image-rendering: pixelated; display: inline-block;
  vertical-align: middle; filter: drop-shadow(0 1px 1px #000); }
.octo .octo-sts { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; }
.octo .ost { display: inline-flex; align-items: center; gap: 1px; }
.octo .ost em { font-style: normal; font-size: 12px; line-height: 1; filter: drop-shadow(0 1px 1px #000); }
.octo .ost b { font-family: var(--game-font); font-size: 11px; line-height: 1; font-weight: 700; color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000; }
.octo .ost.more { font-family: var(--game-font); font-size: 10px; line-height: 1; color: #cdbf9a; text-shadow: 0 1px 2px #000; }
.octo .obar-row { grid-column: 2; grid-row: 2; }
/* retrato redondo: soquete escuro + face_<key>.png SOB o anel dourado real (assets/hud/batalha/anel.png) */
.octo .oport { grid-column: 1; grid-row: 1 / span 2; position: relative; width: var(--oport, 30px); height: var(--oport, 30px); align-self: center; }
.octo .oport img { display: block; image-rendering: pixelated; }
.octo .oport .sok { position: absolute; inset: 2px; border-radius: 50%; background: radial-gradient(circle at 50% 36%, #2c2517, #0d0a06 82%); box-shadow: inset 0 1px 2px rgba(0,0,0,.6); }
.octo .oport .fc { position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); border-radius: 50%; object-fit: cover; object-position: center top; }
.octo .oport .rg { position: absolute; inset: 0; width: 100%; height: 100%; }
.octo .hr.ko .oport .fc { filter: grayscale(1) brightness(.42); }
/* 🔨 MARTELO 3 (230, 22/07): "quero 'Bárbaro', não MAIÚSCULA — e diminui pelo menos 2 da fonte".
   A caixa-alta vinha da PRÓPRIA Silkscreen (--font-title, fonte caps-only) — troca p/ Pixelify Sans
   (pixel display COM minúsculas + acento; declarada em style.css, rollback oficial) e desce 15→13px.
   Régua de baixa visão: piso 12px — os pares (12px no celular) ficam NO piso, nunca abaixo. */
.octo .octo-name { font-family: "Pixelify Sans", var(--font-body); font-size: 13px; line-height: 1; color: #e9d59a; letter-spacing: .4px; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 0 #000; }
.octo .octo-name .kotag { color: #e0574a; font-size: 10px; }
.octo .octo-name .hprep { font-size: 12px; margin-left: 3px; opacity: .85; }
.octo .obar-row { display: flex; flex-direction: row; gap: 6px; align-items: stretch; }
/* barra: track escuro + preenchimento (cor por fração) + NÚMERO DENTRO (rótulo esq / valor dir) */
.octo .obar { position: relative; flex: 1 1 0; min-width: 0; height: 20px; border-radius: 5px; overflow: hidden;
  background: linear-gradient(180deg, #191b20, #0c0d10); box-shadow: inset 0 0 0 1px #05060a, inset 0 1px 0 rgba(255,255,255,.10), 0 1px 2px rgba(0,0,0,.55); }
.octo .obar > i { position: absolute; left: 0; top: 0; bottom: 0; display: block; border-radius: 4px 2px 2px 4px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.3), inset 0 -2px 3px rgba(0,0,0,.32); transition: width .3s ease; }
.octo .obar > .lab { position: absolute; left: 5px; top: 50%; transform: translateY(-50%); z-index: 2;
  font-family: var(--font-title); font-size: 10px; line-height: 1; color: #f4ead0; opacity: .9; text-shadow: 0 1px 2px #000; letter-spacing: .2px; }
.octo .obar > b { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); z-index: 2; font-weight: 400;
  font-family: var(--game-font); font-size: 13px; line-height: 1; color: #fff; text-shadow: 0 1px 2px #000, 0 0 3px rgba(0,0,0,.7); }
.octo .obar.mp > .lab { color: #cfe6ff; } .octo .obar.mp > b { color: #eaf4ff; }
/* escudo emendado na ponta do HP (shieldSegHtml) — reusa a régua branco/azul-gelo dentro da calha Octopath */
.octo .obar.hp i.sh { background: linear-gradient(180deg, #f6f8fc, #d3dae6); z-index: 1; }
.octo .obar.hp i.shm { background: linear-gradient(180deg, #b6ecf8, #6fc4da); z-index: 1; }
.octo .obar.hp i.shfil { inset: -1px 0 -1px auto; width: 3px; background: linear-gradient(180deg, #fff, #e6ecf5); box-shadow: 0 0 5px 1px rgba(238,242,248,.9); z-index: 1; }
/* herói ATIVO = célula ACESA (anel/borda dourado) + ▶ no nome */
.octo .hr.on { box-shadow: inset 0 0 0 2px #ffd84a, 0 0 12px rgba(255,216,74,.42); background: linear-gradient(180deg, rgba(60,48,20,.5), rgba(30,22,8,.6)); }
.octo .hr.on .octo-name { color: #ffe9a8; text-shadow: 0 0 6px rgba(240,208,96,.85), 0 1px 0 #000; }
.octo .hr.ko { opacity: .55; }
/* TANQUE (por papel) = 1ª célula, SOLO full-width, a MAIOR (barra um tico maior + nome maior).
   ⚠️ row-gap explícito (não `gap`): o shorthand esmagaria o column-gap do retrato. */
.octo .hr.tankcell { grid-column: 1 / -1; order: -1; padding: 5px 11px 6px; row-gap: 4px; column-gap: 8px; --oport: 36px; }
.octo .hr.tankcell .octo-name { font-size: 15px; } /* MARTELO 3: era 17 (−2; tanque segue +2 sobre os pares) */
.octo .hr.tankcell .obar-row { gap: 8px; }
.octo .hr.tankcell .obar { height: 22px; }
.octo .hr.tankcell .obar > b { font-size: 16px; }
.octo .hr.tankcell .obar > .lab { font-size: 11px; }
/* mira de CURA pela célula (só quando targetingMode='ally'): realce verde, ganha do estilo base */
.octo .hr.targetable { cursor: pointer; box-shadow: inset 0 0 0 2px rgba(143,210,87,.9), 0 0 10px 1px rgba(120,220,90,.45); }
.octo .hr.targetable:hover { box-shadow: inset 0 0 0 2px #aef07e, 0 0 14px 2px rgba(120,220,90,.8); }

/* ── GRUPO (heróis): nome + moldura única [vida 9px / mana 6px] + números (HP branco, mana menor) ── */
/* GRUPO = mesmo padrão do INIMIGOS (Lucas 13/07): NOME EM CIMA na linha inteira, barras embaixo.
   Com o nome fora da lateral, as barras AFINARAM (estética; ver .stack e .mr .b.hp). */
/* ═══ CARTÕES M3 (aprovados Lucas 13/07 — maquete _previews/111_tela_comprimida_mock.html) ═══
   Cada linha do roster vira um CARTÃO com moldura botao_chip.png. Receita da maquete: ::before absoluto
   com border 14px + border-image slice 26 fill stretch, realce brightness(1.12)/contrast(1.06) SÓ no aro,
   e padding interno mínimo (~5px vert / 9px horiz). O ::before é ABSOLUTO → não vira item do grid da
   linha, então a geometria interna (nome em cima, bloco vida+mana, números à direita) fica INTOCADA;
   só ganhamos a embalagem. Markup existente (.hr/.hn/.stack/.hv e .mr/.mn/.b.hp/.mv) NÃO muda. */
/* ═══ ROSTER L-B (maquete 208 ★, reforma 22/07) — "só muda a roupa" da linha: retrato redondo com ANEL
   dourado real + calha de aço + barra viva (cor por fração) + NÚMERO 9px (só valor atual) dentro da calha.
   SEM moldura de chip por linha (rows limpas sobre o painel) — o número/valor mora DENTRO da barra. */
.hr, .mr:not(.more) { position: relative; z-index: 0; padding: 2px 4px; }
.hr { display: grid; grid-template-columns: 24px minmax(0, 1fr); grid-template-rows: auto auto; column-gap: 7px; row-gap: 1px; align-items: center; }
.hr.ko { opacity: .55; }
/* retrato: face_<key>.png (soquete escuro) SOB o anel dourado real (assets/hud/batalha/anel.png) */
.hport { grid-column: 1; grid-row: 1 / span 2; position: relative; width: 24px; height: 24px; align-self: center; }
.hport .hface { position: absolute; inset: 2px; width: calc(100% - 4px); height: calc(100% - 4px); border-radius: 50%;
  object-fit: cover; object-position: center top; image-rendering: pixelated; }
.hport .hring { position: absolute; inset: 0; width: 100%; height: 100%; }
.hr.ko .hport .hface { filter: grayscale(1) brightness(.42); }
.hport .hko { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-title); font-weight: 800; font-size: 8px; color: #e0574a; text-shadow: 0 1px 2px #000; }
.hn { grid-column: 2; grid-row: 1; font-family: var(--font-title); font-size: 9px; color: #d9c493;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 0 #000; }
.hn.on { color: #ffe9a8; text-shadow: 0 0 5px rgba(240,208,96,.9); }
.hn .hn-mp { color: #6f9fd6; } /* mana compacta ao lado do nome (208 não grava HP/MP na mini) */
.hn .hprep { font-size: 9px; margin-left: 3px; opacity: .85; }
.stack { grid-column: 2; grid-row: 2; display: grid; grid-template-rows: 8px 3px; gap: 1px; }
.stack .b { position: relative; }
.b { position: relative; }
.b.hp { border-radius: 3px; overflow: hidden; background: #151109; outline: 1px solid #5a4a2c; box-shadow: inset 0 1px 2px rgba(0,0,0,.7); }
.b.mp { border-radius: 2px; overflow: hidden; background: #10131c; outline: 1px solid #2f3a52; }
.b i { position: absolute; left: 0; top: 0; bottom: 0; display: block; }
.b.hp i { background: linear-gradient(180deg, #8fd15e, #4e8a2e); } /* padrão (inimigo); herói sobrescreve inline pela fração */
.b.hp > i:first-child { box-shadow: inset 0 1px 0 rgba(255,255,255,.28), inset 0 -2px 2px rgba(0,0,0,.3); }
.b.mp i { background: linear-gradient(180deg, #5fa8ec, #33619c); }
/* FRENTE 2+3 (15/07, contrato 131) — segmentos de ESCUDO na barra de vida (herói .stack E mob .mr).
   .sh = branco-perolado (escudo geral) · .shm = azul-gelo (escudo mágico, distinto do azul da mana #56a0e6)
   · .shfil = filete + brilho na moldura direita quando há TRANSBORDO. left/width vêm inline (régua da barra). */
.b.hp i.sh { background: linear-gradient(180deg, #f6f8fc, #d3dae6); border-left: 1px solid rgba(10,14,22,.35);
  box-shadow: 0 0 3px rgba(238,242,248,.55), inset 0 1px 0 rgba(255,255,255,.85), inset 0 -1px 0 rgba(120,132,150,.5); z-index: 2; }
.b.hp i.shm { background: linear-gradient(180deg, #b6ecf8, #6fc4da); border-left: 1px solid rgba(10,20,26,.4);
  box-shadow: 0 0 3px rgba(143,216,234,.6), inset 0 1px 0 rgba(230,250,255,.8), inset 0 -1px 0 rgba(60,120,140,.5); z-index: 3; }
.b.hp i.shfil { inset: -1px 0 -1px auto; width: 3px; background: linear-gradient(180deg, #fff, #e6ecf5);
  box-shadow: 0 0 5px 1px rgba(238,242,248,.9); border-radius: 1px; z-index: 4; }
.b.hp i.shfil.m { background: linear-gradient(180deg, #dff6fc, #8fd8ea); box-shadow: 0 0 5px 1px rgba(143,216,234,.9); }
/* número da vida (SÓ valor atual, 9px) DENTRO da calha, à direita, centrado verticalmente */
.hv { position: absolute; right: 3px; top: 50%; transform: translateY(-50%); z-index: 3;
  font-family: var(--game-font); font-size: 9px; line-height: 1; color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000; white-space: nowrap; }
.hv b { color: #fff; font-weight: normal; }

/* ── INIMIGOS: nome por raridade (cor inline) + barra estreita na moldura + valor pela régua do glossário ── */
/* EMPILHADO (Lucas 13/07): nome truncava na coluna fixa (106px; "Abominação Corrompida" etc.) →
   nome ganha a LINHA de cima inteira (~240px, cabe qualquer nome do bestiário) e barra+valor
   descem pra linha 2. Vale também pro celular (@media abaixo). */
/* INIMIGO L-B (spec §4): retrato redondo pequeno (arte da fita) + mini-calha de HP + número 9px na calha */
.mr { display: grid; grid-template-columns: 22px minmax(0, 1fr); grid-template-rows: auto auto; column-gap: 7px; row-gap: 1px; align-items: center; }
.mr.ko { opacity: .4; filter: grayscale(.85); }
.eport { grid-column: 1; grid-row: 1 / span 2; position: relative; width: 22px; height: 22px; align-self: center;
  border-radius: 50%; overflow: hidden; background: radial-gradient(circle at 50% 42%, #241f18, #14110c 80%);
  box-shadow: 0 0 0 1px #3a2f1e, inset 0 0 0 1px rgba(0,0,0,.55); }
.eport .eface { width: 100%; height: 100%; object-fit: cover; object-position: center top; image-rendering: pixelated; }
.eport .eface.spr { transform: scale(1.6); transform-origin: center top; } /* recorte do sprite de batalha (sem retrato dedicado) */
.eport .eini { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-title); font-weight: 800; font-size: 10px; text-shadow: 0 1px 2px #000; }
.mn { grid-column: 2; grid-row: 1; font-family: var(--font-title); font-size: 9px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 0 #000; }
/* "+N…" (excedente de inimigos) segue LINHA simples (excluído do layout L-B via :not(.more)) */
.mr.more { display: block; grid-column: unset; font-family: var(--font-title); font-size: 8px; color: #8d8066; padding: 2px 4px; }
.mr .b.hp { grid-column: 2; grid-row: 2; position: relative; height: 8px; border-radius: 3px; overflow: hidden;
  background: #151109; outline: 1px solid #5a4a2c; box-shadow: inset 0 1px 2px rgba(0,0,0,.7); }
.mv { position: absolute; right: 3px; top: 50%; transform: translateY(-50%); z-index: 3;
  font-family: var(--game-font); font-size: 9px; line-height: 1; color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000; white-space: nowrap; }
.mv b { color: #fff; font-weight: normal; }
.mv .mvm { display: none; } /* dentro da calha: só o valor atual (208); a régua do glossário segue via getKnowTier */
.mr .b.dom { outline-color: #e8c25a; box-shadow: inset 0 1px 2px rgba(0,0,0,.7), 0 0 4px rgba(232,194,90,.4); }
.dom-star { color: #e8c25a; }
/* ═══ MIRA PELOS CARTÕES (pedido Lucas 13/07) ═══ durante a seleção de alvo, os cartões viáveis do roster
   ficam clicáveis e acesos: INIMIGO = moldura DOURADA (ataque/skill/frasco), HERÓI = moldura VERDE (cura).
   Realce por box-shadow (não ocupa layout, igual ao filete 'dominada') — o ::before/border-image segue por
   baixo. Os handlers moram por delegação no container (buildDOM), então o realce sobrevive ao re-render. */
.mr.targetable, .hr.targetable { cursor: pointer; border-radius: 6px; }
.mr.targetable { box-shadow: inset 0 0 0 2px rgba(255,210,80,.9), 0 0 10px 1px rgba(255,200,70,.45); }
.hr.targetable { box-shadow: inset 0 0 0 2px rgba(143,210,87,.9), 0 0 10px 1px rgba(120,220,90,.45); }
.mr.targetable:hover { box-shadow: inset 0 0 0 2px #ffe08a, 0 0 14px 2px rgba(255,200,70,.8); }
.hr.targetable:hover { box-shadow: inset 0 0 0 2px #aef07e, 0 0 14px 2px rgba(120,220,90,.8); }

/* FIX 2b (Lucas 14/07): no PC (mouse) o INDICADOR de alvo (▼ no campo + realce dourado/verde nos cartões)
   aparece SÓ no alvo sob o CURSOR, não em todos. Todos seguem clicáveis: a classe .targetable e o
   cursor:pointer continuam em TODOS — só o realce vira hover. @media (hover:hover) and (pointer:fine) trava
   isso pra valer só onde há mouse; no TOQUE (celular/tablet, sem hover) NADA muda — todos ficam marcados,
   porque sem cursor não existe "alvo sob o mouse". */
@media (hover: hover) and (pointer: fine) {
  /* campo: esconde o ▼ de todos os alvos e revela só no que está sob o mouse */
  .bt-unit.enemy.targetable::after, .bt-unit.hero.targetable::after { display: none; }
  .bt-unit.enemy.targetable:hover::after, .bt-unit.hero.targetable:hover::after { display: block; }
  /* cartões: sem realce persistente; o realce de hover (.mr/.hr .targetable:hover, acima) vira o único indicador */
  .mr.targetable, .hr.targetable, .octo .hr.targetable { box-shadow: none; }
}

/* ── CENTRO: menu-mini (chips emoldurados) à esquerda + arena (skills/mensagens) à direita ── */
/* menu de ações na LARGURA DO TEXTO (Lucas 13/07): coluna fixa 104px esticava os chips — max-content
   dimensiona pela ação mais larga (✦ Skills/⚔ Atacar) e devolve o resto pra arena.
   RECLAIM 222 (anti-vazamento): align-content:start ancora o conteúdo no topo → a grade de skills sempre cabe. */
.bt-center { min-width: 0; display: grid; grid-template-columns: max-content 1fr; gap: 10px; align-content: start; }
/* CHIPS NA MEDIDA DO TEXTO (pedido Lucas 13/07): FLEX COLUNA compacta; cada botão abraça o próprio texto.
   🔨 MARTELO 2 do Lucas (230, 22/07): "tem que SAIR essa moldura EXTERNA dos botões" — a PLACA escura
   (fundo/borda/box-shadow do bloco, entrou na 222) MORREU. Chips SOLTOS sobre o painel; cada um segue
   com a própria borda ornamentada (botao_chip.png via .bt-btn::before). Padding vertical só p/ respiro. */
.bt-actions { display: flex; flex-direction: column; gap: 5px; align-content: center; justify-content: center; align-items: stretch;
  padding: 8px 0; }
.bt-arena { position: relative; min-width: 0; display: grid; align-content: start; }
/* skills/bolsa ABERTAS: a arena para de centralizar e o detalhe assume a altura toda
   (grade no TOPO, sobra vira área de rolagem — refino Lucas 13/07 pós-painel-v3) */
.bt-arena:has(.bt-detail:not(:empty)) { align-content: stretch; grid-template-rows: 1fr; }
/* ═══ "ESCOLHA UMA AÇÃO" — ACIMA DA BARRA DE AÇÕES E CENTRALIZADO (Lucas 30/07) ═════════════════
   Ele perguntou: "esse escolha uma ação quase invisível é para a barra de ações, certo? Ela está
   lateralmente, quero ela em cima da barra de ações e centralizada".
   A dica vivia no fluxo da .bt-arena, que no celular é uma coluna alinhada à esquerda — daí o texto
   ficar de lado, longe do que ele manda fazer. Agora ela sai do fluxo e se ancora na MESMA régua da
   barra de ações (fixed + left:50% + translateX(-50%)), empilhada logo acima dela: as duas se movem
   juntas e a centralização é automática em qualquer largura.
   A ALTURA DA BARRA virou variável para os dois não saírem de sincronia — foi mexer no tamanho do
   botão hoje que descobriu quanto número solto dependia dele. */
/* +10% pedido pelo Lucas (31/07): 47 → 52. Tudo que depende do tamanho da barra lê daqui — a dica
   "escolha uma ação" se ancora nesta variável, e a cartela de perícia usa o mesmo --acoes-bottom. */
:root { --acoes-h: 52px; --acoes-bottom: 8px; }
.bt-turn { position: fixed; left: 50%; bottom: calc(var(--acoes-bottom) + var(--acoes-h) + 7px);
  transform: translateX(-50%); z-index: 29; text-align: center; pointer-events: none;
  width: max-content; max-width: 92vw; }
.bt-turn .who { display: none; } /* herói ativo já é sinalizado por ▶ no roster GRUPO */
/* .bt-turnline MORREU (P2 236, martelo Lucas 22/07): o "Vez de X" saiu do painel inferior e virou a
   PLACA-CHIP .bt-turnchip junto à fita de turno (ver bloco da fita) — o espaço do painel voltou pro centro. */
/* "quase invisível" foram as palavras dele: 7px num cinza #6d6250 sobre o gramado. A régua da casa é
   ≥13px físicos no celular (ele tem baixa visão), e esta é uma instrução do jogo, não um enfeite —
   sobe para 13px com cor legível e sombra que a descola do fundo, em vez de sumir dentro dele. */
.bt-turn .tip { font-family: var(--font-title); font-size: 13px; color: #cbbb92; letter-spacing: 1px;
  text-transform: uppercase; line-height: 1.5; text-shadow: 0 1px 3px #000, 0 0 8px rgba(0,0,0,.85); }
/* ═══ MOLDURA CHEIA DO HERÓI ATIVO (spec §3, maquete 207 aprovada — asset painterly ÷ suave, NUNCA pixelated) ═══
   Todos os filhos posicionados em % do crop nativo 440×102 → a moldura escala sem desalinhar o fill. O NÚMERO
   fica em 9px FIXO (não escala com a moldura). Geometria: socket cx54 cy50 r48; calha HP L139 W129 / MP L308 W127
   (T39 H24); fill nasce no inset final (+3px), útil = calha − inset. */
.bt-turn.has-frame { text-align: center; }
.bt-hero-frame { position: relative; width: 220px; max-width: 100%; margin: 0 auto; aspect-ratio: 440 / 102; }
.bt-hero-frame .hf-bg { position: absolute; inset: 0; width: 100%; height: 100%; image-rendering: auto; } /* painterly: suavizado */
.bt-hero-frame .hf-sock { position: absolute; left: 3.64%; top: 11.76%; width: 17.27%; height: 74.51%;
  border-radius: 50%; overflow: hidden; background: radial-gradient(circle at 50% 42%, #23201a, #14110c 80%); }
.bt-hero-frame .hf-sock img { width: 100%; height: 100%; object-fit: cover; object-position: center top; image-rendering: pixelated; }
.bt-hero-frame .hf-rail { position: absolute; top: 38.24%; height: 23.53%; overflow: hidden; }
.bt-hero-frame .hf-rail.hp { left: 31.59%; width: 29.32%; }
.bt-hero-frame .hf-rail.mp { left: 70.00%; width: 28.86%; }
.bt-hero-frame .hf-rail > i { position: absolute; top: 0; bottom: 0; display: block;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28), inset 0 -2px 3px rgba(0,0,0,.35); }
.bt-hero-frame .hf-num { position: absolute; top: 50%; transform: translateY(-50%); height: 9px;
  display: flex; align-items: center; justify-content: flex-end; padding-right: 4px;
  font-family: var(--game-font); font-size: 9px; line-height: 1; color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000; white-space: nowrap; }
.bt-hero-frame .hf-num.hp { left: 31.59%; width: 29.32%; }
.bt-hero-frame .hf-num.mp { left: 70.00%; width: 28.86%; }
.bt-hero-frame .hf-num b { font-weight: normal; }
/* arena: quando .bt-detail tem conteúdo (skills/bolsa) ele ocupa a zona e a dica some */
.bt-detail:empty { display: none; }
/* RECLAIM 222 (anti-vazamento): gap 6→2 entre a fileira de abas e a grade → as 2 fileiras de ícones 44px cabem
   no painel 150 sem tocar a borda. align-content:start ancora no TOPO (a sobra vira folga embaixo, não estica). */
.bt-detail:not(:empty) { display: grid; grid-template-rows: auto 1fr; gap: 2px; align-content: start; height: 100%; min-height: 0; }
/* a dica some quando skills/bolsa abrem. Era `.bt-detail:not(:empty) + .bt-turn` (irmão adjacente),
   que deixou de casar quando a dica saiu de dentro da arena — ver o comentário no HTML da .bt-turn.
   O :has() já é usado na casa (.bt-arena:has(.bt-detail:not(:empty)), logo acima). */
.bt-center:has(.bt-detail:not(:empty)) .bt-turn { display: none; }
/* mesma condição pela CLASSE (31/07): se o `:has()` acima for descartado por algum navegador, a dica
   "escolha uma ação" continuaria acesa POR BAIXO da cartela. Duas regras dizendo a mesma coisa por
   caminhos diferentes — a que o navegador entender, vale. */
#battle.bt-skillmode .bt-turn { display: none; }

/* CHIP (botão do menu-mini) e SKIN base de todo .bt-btn — pele botao_chip.png via ::before */
.bt-btn { cursor: pointer; border: 0; font-family: var(--font-title); font-size: 7px; font-weight: 400; letter-spacing: .4px; color: #eaddc0; text-transform: uppercase;
  padding: 0 8px; position: relative; z-index: 0; background: none; box-shadow: none; transition: filter .12s, transform .08s; }
.bt-btn::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  border: 9px solid transparent; border-image: url("../assets/ui/pixellab/botao_chip.png") 26 fill stretch; }
.bt-btn:hover { filter: brightness(1.25); } .bt-btn:active { transform: translateY(1px); }
/* ═══ DESABILITADO ESMAECE O CONTEÚDO, NÃO A MOLDURA (Lucas 30/07) ══════════════════════════════
   Ele apontou: "a moldura do botão da mochila na batalha está errada, está a antiga cinza". Não era
   moldura antiga — os cinco botões usam o MESMO botao_chip.png. O que mudava era este filtro: um
   `grayscale(.6)` no botão INTEIRO descolore também a borda dourada, e com dois botões desabilitados
   (skills sem skill, bolsa sem consumível) a barra ficava com duas molduras cinza no meio de três
   douradas — lê como arte de outra época, não como "indisponível".
   Agora o esmaecimento é do ÍCONE e do rótulo; a moldura só perde um pouco de presença, mantendo a
   cor. A barra fica coerente e continua dizendo que o botão não pode ser usado. */
.bt-btn:disabled { cursor: default; color: #857c6b; }
.bt-btn:disabled > img, .bt-btn:disabled .k { filter: grayscale(.6) brightness(.55); }
.bt-btn:disabled::before { opacity: .5; }
/* fonte +3 (7→10, pedido Lucas 13/07 — padrão em TODOS os menus); min-height/padding sobem só o bastante
   pra fonte respirar SEM inflar além do texto (o botão continua abraçando o rótulo). */
.bt-actions .bt-btn { display: flex; align-items: center; justify-content: center; gap: 4px; min-height: 26px; padding: 3px 12px; font-size: 10px; }
.bt-actions .bt-btn .k { display: none; } /* contagem escondida nos chips (maquete limpa) */

/* ── SKILLS/BOLSA na arena: linha de abas (grade auto) + grade de skills (3 col PC) ── */
.bt-skilltabs { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; min-height: 24px; }
/* ABAS +3 (7→10, PRINCIPAL reclamação do Lucas 13/07); padding sobe um filete pra fonte maior respirar. */
/* FIX 1 (Lucas 14/07): a aba "Arcano" (🔮) saía da linha de base das outras NO CELULAR. Causa raiz: a
   altura da aba era DEFINIDA PELO CONTEÚDO (sem height fixa; line-height:normal). No aparelho do Lucas o
   glifo do emoji 🔮 renderiza mais ALTO que 🔥/⚡/★, então só a aba dele inflava a line-box e — com
   align-items:center na fileira — descia/subia sozinha. Correção: ALTURA FIXA + inline-flex centralizado +
   line-height:1 em TODA aba e no "Voltar" → nenhum emoji consegue mais mexer na caixa (ativa ou não). */
.bt-tab { cursor: pointer; border: 0; font-family: var(--font-title); font-size: 10px; font-weight: 400; letter-spacing: .3px; color: #cdbf9b; text-transform: uppercase;
  display: inline-flex; align-items: center; justify-content: center; height: 24px; line-height: 1; padding: 0 10px; position: relative; z-index: 0; background: none; }
.bt-tab::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  border: 8px solid transparent; border-image: url("../assets/ui/pixellab/botao_chip.png") 26 fill stretch; }
.bt-tab.on { color: #ffe9a8; filter: brightness(1.3); }
.bt-skilltabs .bt-btn.back { display: inline-flex; align-items: center; justify-content: center; height: 24px; line-height: 1; padding: 0 9px; font-size: 13px; margin-left: auto; } /* SÓ ÍCONE ↩ (18/07): compacto, ancorado no fim da linha das abas */
.bt-skilltabs .bt-btn.back::before { border-width: 8px; }
/* LISTA DE SKILLS (Lucas 13/07): era grade de colunas 1fr — botão esticava a coluna inteira,
   "enorme comparado com o texto" e a grade parecia flutuar no meio. Agora FLEX WRAP: cada botão
   ABRAÇA o próprio texto (nome+custo) e a fileira nasce no topo-esquerda. Fonte = padrão do .bt-btn. */
.bt-skilllist { display: flex; flex-wrap: wrap; gap: 5px; align-content: flex-start; overflow-y: auto; min-height: 0; -webkit-overflow-scrolling: touch; }
/* fonte 9→12 (+3, novo padrão de TODOS os menus — pedido Lucas 13/07); min-height sobe pra fonte respirar. */
.bt-skilllist .bt-btn { flex: 0 0 auto; display: flex; align-items: center; gap: 7px; min-height: 30px; padding: 2px 12px; text-transform: none; text-align: left; font-size: 10px; } /* −2px (18/07): skills cabendo no grid até os ícones chegarem */
.bt-skilllist .bt-btn .k { display: inline; font-family: var(--game-font); font-size: 11px; letter-spacing: 0; color: #7fa0c9; opacity: 1; } /* custo proporcional (+2) */
.bt-skilllist::-webkit-scrollbar { width: 6px; }
.bt-skilllist::-webkit-scrollbar-thumb { background: rgba(217,178,76,.4); border-radius: 4px; }
/* ═══ GRID DE ÍCONES DE SKILL (maquete 201, reforma 22/07) ═══ botões-ícone 48×48 (toque ≥44px); abas
   FUNDIDAS (a COR do ícone = a escola). Ícone real assets/skills/<id>.png POR CIMA do FALLBACK procedural
   (tile na cor da escola + inicial). Estados: sem mana = cinza + custo vermelho; recarga = véu + nº; ULT =
   moldura dourada (+ "1×" quando usada); SELECIONADO (1º toque) = moldura dourada acesa. */
/* GRID 222 (maquete 220) → LARGURA 6 (Lucas, 31/07): com a grade mostrando TODAS as perícias de uma vez,
   5 por fileira dava 3 fileiras no pior caso (Yinnorin, 12). Em 6 o mesmo caso fecha em 2.
   CÉLULA 44 → 52 (maquete 433-D, aprovada 31/07): a moldura ornamentada precisa de faixa, e em 52 a
   JANELA de arte visível sobe de 38 para 42px — a moldura entra e o desenho ainda ganha.
   max-width força o wrap em EXATAMENTE 6 (6×52 + 5×gap = 332 ≤ 332 < 7×52 + 6×4 = 388) → nunca vaza.
   ⚠ Para voltar à cartela A (sem ornamento, célula 44): docs/REFERENCIA_cartela_A_aro_leve.md */
.bt-skillgrid { display: flex; flex-wrap: wrap; gap: 4px; align-content: flex-start; max-width: 332px; overflow: visible; }
.bt-skbtn { position: relative; flex: 0 0 auto; width: 52px; height: 52px; padding: 0; border: 0; cursor: pointer;
  background: none; border-radius: 7px; transition: filter .1s, transform .06s; }
.bt-skbtn:active:not(:disabled) { transform: translateY(1px); }
.bt-skbtn:disabled { cursor: default; }
/* MOLDURA NA COR DA ESCOLA (Lucas, 31/07, maquete 424-B). Era #55492f fixo para todas — com a grade
   mostrando tudo junto, o marrom único apagava a diferença entre fogo, cura e apoio. Agora a borda usa
   o realce da própria escola (--m, o mesmo de SKILL_SCHOOL_COLORS) + um halo curto da mesma cor.
   ⚠ Esta regra vem ANTES de .ult e .on de propósito: a dourada da Ultimate e o aro de seleção têm
   especificidade maior e continuam vencendo — a cor da escola não pode engolir nenhum dos dois. */
.bt-skbtn .ski { position: absolute; inset: 2px; border-radius: 4px; overflow: hidden;
  background: linear-gradient(160deg, var(--a, #3a3f47), var(--b, #22252b)); border: 2px solid var(--m, #55492f);
  box-shadow: inset 0 0 0 1px #0b0a07, 0 0 6px -2px var(--m, #55492f), 0 1px 2px rgba(0,0,0,.5); }

/* ═══ MOLDURA ORNAMENTADA POR ESCOLA (maquete 433-D, aprovada Lucas 31/07) ═══════════════════════════
   A moldura é a slot_comum.png do acervo, RECORTADA da margem transparente de 6px que ela tem (o arquivo
   é 181×181, o desenho é 169×169 — desenhar sem recortar deixa a moldura 3,5% menor que a caixa e ela
   "descola" da borda) e recolorida pixel a pixel: a LUMINÂNCIA de cada ponto fica, que é o relevo do
   metal, e só o matiz vira a cor da escola. Uma arte por escola em assets/ui/molduras/escola_*.png.
   ESTICADA, não fatiada: foi a variante que o Lucas escolheu — faixa fina, ornamento discreto, e a
   janela de arte fica MAIOR que a de antes (42px contra 38). Medida com o painel sobre fundo magenta:
   0 furo no anel em todas as escolas.
   ⚠ Este bloco é AUTOCONTIDO — apagá-lo devolve a cartela A. Ver docs/REFERENCIA_cartela_A_aro_leve.md */
.bt-skbtn::before { content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background-size: 100% 100%; background-repeat: no-repeat; image-rendering: pixelated; }
.bt-skbtn.sch-steel::before    { background-image: url("../assets/ui/molduras/escola_steel.png"); }
.bt-skbtn.sch-fire::before     { background-image: url("../assets/ui/molduras/escola_fire.png"); }
.bt-skbtn.sch-electric::before { background-image: url("../assets/ui/molduras/escola_electric.png"); }
.bt-skbtn.sch-arcane::before   { background-image: url("../assets/ui/molduras/escola_arcane.png"); }
.bt-skbtn.sch-nature::before   { background-image: url("../assets/ui/molduras/escola_nature.png"); }
.bt-skbtn.sch-water::before    { background-image: url("../assets/ui/molduras/escola_water.png"); }
.bt-skbtn.sch-earth::before    { background-image: url("../assets/ui/molduras/escola_earth.png"); }
.bt-skbtn.sch-shadow::before   { background-image: url("../assets/ui/molduras/escola_shadow.png"); }
.bt-skbtn.sch-ult::before      { background-image: url("../assets/ui/molduras/escola_ult.png"); }
.bt-skbtn .skfb { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-title); font-weight: 800; font-size: 18px; color: var(--m, #c7ccd6); text-shadow: 0 1px 2px #000; } /* 44px (222): 20→18 */
.bt-skbtn .skimg { position: absolute; inset: 0; width: 100%; height: 100%; image-rendering: pixelated; }
/* ═══ BADGES LEGÍVEIS (baixa visão) — iteração 213, aprovado Lucas 22/07 ═══════════════════════════
   Régua da casa: contraste alto, NADA de transparência em texto informativo. Todo texto sobre o ícone vira
   CHIP 100% OPACO. Custo em azul-claro nítido (11px), sem-mana em vermelho VIVO, recarga com número BRANCO
   em chip central (15px), "1×" da ult em chip DOURADO. */
/* 1) custo de mana — chip OPACO no rodapé direito (não cobre o centro do ícone) */
.bt-skbtn .skc { position: absolute; right: 3px; bottom: 3px; z-index: 5; padding: 1px 4px;   /* +1: dentro da moldura */
  background: #0c0a07; border: 1px solid #6a5b37; border-radius: 5px;
  font-family: var(--game-font); font-size: 11px; line-height: 1; font-weight: 700; color: #c4e9ff;
  text-shadow: none; box-shadow: 0 1px 2px rgba(0,0,0,.65); }
.bt-skbtn .skc i { font-style: normal; color: #c4e9ff; font-size: 10px; margin-left: 1px; text-shadow: none; }
/* 4) SEM mana — mesmo chip opaco, texto vermelho VIVO (opacidade cheia) */
.bt-skbtn .skc.no, .bt-skbtn .skc.no i { color: #ff6a52; }
.bt-skbtn .skc.no { border-color: #8a2f22; }
/* 2) recarga — o véu escurece o ícone (semântica); o NÚMERO vem em chip sólido central, branco 15px */
.bt-skbtn .skveil { position: absolute; inset: 0; z-index: 4; display: flex; align-items: center; justify-content: center;
  background: rgba(6,8,12,.74); border-radius: 7px; }
.bt-skbtn .skveil b { display: flex; align-items: center; justify-content: center; min-width: 20px; padding: 2px 6px;
  background: #0a0906; border: 1px solid #6b5c38; border-radius: 6px;
  font-family: var(--game-font); font-size: 15px; line-height: 1; font-weight: 700; color: #fff;
  text-shadow: none; box-shadow: 0 1px 3px rgba(0,0,0,.75); }
/* 3) "1×" da ult / ⛓ enraizado (canto sup. esq.) — chip sólido, zero alpha no texto */
.bt-skbtn .skused { position: absolute; left: 4px; top: 4px; z-index: 6; padding: 1px 4px;   /* +1: dentro da moldura */
  background: #0c0a07; border: 1px solid #c8922a; border-radius: 5px;
  font-family: var(--font-title); font-weight: 800; font-size: 11px; line-height: 1; color: #ffd84a;
  text-shadow: none; box-shadow: 0 1px 2px rgba(0,0,0,.65); }
.bt-skbtn:hover:not(:disabled) { filter: brightness(1.18); }
.bt-skbtn.ult .ski { border-color: #c8922a; box-shadow: inset 0 0 0 1px #4c3408, 0 0 6px rgba(255,216,74,.45); } /* ULT = moldura dourada */
/* ═══ PERÍCIA AINDA NÃO LIBERADA (Lucas 31/07: "todas as skills aparentes, mas bloqueadas pelo nível") ═══
   A grade passou a desenhar o KIT INTEIRO (HERO_KIT), não só o que já abriu. A travada mantém a MOLDURA
   da escola — o jogador vê desde o Nv1 que existe uma perícia de fogo ali esperando — e apaga só o miolo:
   a arte fica em silhueta (dá pra reconhecer, não pra usar), com cadeado + o NÍVEL que abre.
   O nível vem de rungs[0].level, a mesma régua de getAvailableSkillIds — nada de número inventado. */
.bt-skbtn.travada { cursor: default; }
.bt-skbtn.travada .ski { filter: grayscale(1) brightness(.34) contrast(.9); }
.bt-skbtn .sklock { position: absolute; inset: 0; z-index: 6; display: flex; align-items: center;
  justify-content: center; font-size: 17px; line-height: 1; text-shadow: 0 1px 3px #000, 0 0 6px #000; }
/* chip do nível: mesmo desenho do chip de mana (opaco, sem texto em transparência — régua de baixa visão) */
.bt-skbtn .sklv { position: absolute; right: 3px; bottom: 3px; z-index: 7; padding: 1px 4px;
  background: #0c0a07; border: 1px solid #6a5b37; border-radius: 5px;
  font-family: var(--game-font); font-size: 10px; line-height: 1; font-weight: 700; color: #f0d99a;
  text-shadow: none; box-shadow: 0 1px 2px rgba(0,0,0,.65); }

.bt-skbtn.nomana .ski { filter: grayscale(.85) brightness(.6); } /* SEM mana = ícone cinza */
.bt-skbtn.cd .ski, .bt-skbtn.rooted .ski { filter: grayscale(.4) brightness(.7); }
/* MARCAÇÃO DE SELEÇÃO reforçada (pedido Lucas 28/07: "às vezes é meio contra intuitivo") — o 1º toque
   agora grita: aro dourado DUPLO pulsando + seta ▲ "confirmar" sob o ícone. O 2º toque confirma. */
.bt-skbtn.on .ski { border-color: #ffd84a; box-shadow: inset 0 0 0 1px #ffd84a, 0 0 0 2px rgba(255,216,74,.85), 0 0 14px 3px rgba(255,216,74,.6); animation: skbtn-pulsa 1s ease-in-out infinite; }
.bt-skbtn.on { filter: brightness(1.18); position: relative; }
.bt-skbtn.on::after { content: "▲ de novo confirma"; position: absolute; left: 50%; bottom: -13px; transform: translateX(-50%);
  font-size: 9px; line-height: 1; color: #ffe6a8; text-shadow: 0 1px 2px #000; white-space: nowrap; pointer-events: none; }
@keyframes skbtn-pulsa { 0%,100% { box-shadow: inset 0 0 0 1px #ffd84a, 0 0 0 2px rgba(255,216,74,.85), 0 0 14px 3px rgba(255,216,74,.6); }
  50% { box-shadow: inset 0 0 0 1px #fff, 0 0 0 2px #ffd84a, 0 0 18px 4px rgba(255,216,74,.85); } }

/* ═══ ABAS DE ESCOLA (maquete 212 + iteração 214, aprovado Lucas 22/07) ═══════════════════════════
   Fileira de chips por ESCOLA/categoria (via skillCategory) no topo da grade de perícia — a grade mostra só
   a aba ATIVA (1 fileira na maioria dos casos; o Druida deixa de ser um paredão de 12). Régua 214: o chip
   INATIVO nunca "apaga" — fundo sólido OPACO (alpha 1) + borda 2px na COR PLENA da escola + glifo cor plena +
   contador 11px creme. O ATIVO se distingue por EXPANSÃO (abre o rótulo) + preenchimento vivo, JAMAIS por
   apagar o inativo. DESABILITADA (aba sem skill castável) = véu dessaturado, ainda legível. Toque ≥44px.
   Cores da escola (--tm): cura #7fd257 · apoio #6fc4da · suprema #ffd84a · ataque #e2795e (+ elementos da Maga). */
/* ═══ ABAS MINI 21px GLIFO-ONLY (🔨 MARTELO 4 do Lucas, 230 22/07: "podem diminuir mais um pouco") ═══════
   Herança da 220 (25px): GLIFO-ONLY (sem rótulo), ATIVA **NÃO EXPANDE** — fill na COR PLENA + glifo ESCURO;
   INATIVAS: borda 2px cor plena + glifo cor plena + contador 11px creme. Visual desce 25→21px; a HIT-AREA
   ≥44px por PSEUDO (::after centrado, 44px) FICA — o toque grande sobrevive à aba menor ainda.
   4 escolas + ↩ cabem INTEIRAS (nowrap, sem corte — medido na prova 230). */
.bt-sktabs { display: flex; flex-wrap: nowrap; align-items: stretch; gap: 4px; min-height: 21px; margin-bottom: 2px;
  overflow-x: auto; overflow-y: visible; scrollbar-width: none; }
.bt-sktabs::-webkit-scrollbar { display: none; }
.bt-sktab { box-sizing: border-box; flex: 0 0 auto; min-width: 38px; min-height: 21px; height: 21px; padding: 0 7px; cursor: pointer;
  border-radius: 7px; display: flex; align-items: center; justify-content: center; gap: 5px; position: relative;
  font-family: var(--font-title); letter-spacing: .3px;
  background: #15110a;                        /* fundo SÓLIDO escuro do kit — OPACO (alpha 1) */
  border: 2px solid var(--tm);                /* traço CHEIO na cor da escola (inativa) */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 1px 3px #0009;
  opacity: 1; }
/* HIT-AREA por pseudo — ONDA 4 (28/07, auditoria visual #4): o pointer-events:none NEUTRALIZAVA o pseudo
   (hit real medido: 21px, não os 44 prometidos). Agora o pseudo recebe toque (auto), tem os 44px do
   contrato (regress_battle_v2 cobra ≥44) e cresce pra CIMA (borda/padding do painel = espaço morto),
   com só 4px pra baixo — crescer simétrico roubava o toque da primeira fileira de ícones de skill
   (irmão posterior no DOM vence a sobreposição). */
.bt-sktab::after, .bt-sktabs .bt-btn.back::after { content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 44px; }
.bt-sktab .em { font-size: 15px; line-height: 1; color: var(--tm); filter: drop-shadow(0 1px 1px #000c); } /* GLIFO na cor plena */
.bt-sktab .lb { display: none; }            /* GLIFO-ONLY: rótulo NUNCA aparece (nem na ativa) */
.bt-sktab .cnt { position: static; padding: 0; background: transparent; border-radius: 0;
  font-family: var(--game-font); font-size: 11px; line-height: 1; font-weight: 700; color: #f7eecb; } /* contador 11px creme */
/* ATIVO — NÃO EXPANDE: fill na cor plena + glifo/contador ESCUROS (sem abrir rótulo).
   31/07: "ativo" mudou de sentido. A aba deixou de ser a única escola VISÍVEL e virou FILTRO — a grade abre
   com tudo e a aba cheia significa "peneirado por esta escola"; tocar nela de novo limpa o filtro. */
.bt-sktab.on { padding: 0 7px;
  background: linear-gradient(180deg, rgba(255,255,255,.12), rgba(0,0,0,.28)), var(--tm);
  border-color: var(--tm);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.42), 0 0 10px -1px var(--tm), 0 1px 3px #0009; }
.bt-sktab.on .em { color: #120d04; filter: drop-shadow(0 1px 0 rgba(255,255,255,.28)); }
.bt-sktab.on .cnt { color: #160f04; font-weight: 800; }
/* DESABILITADA — aba sem skill castável agora: véu leve, dessaturado, mas AINDA legível (nunca fantasma) */
.bt-sktab.dis { cursor: default; background: #100d08; border-color: #6a6350; }
.bt-sktab.dis .em { color: #9c927a; filter: none; }
.bt-sktab.dis .cnt { color: #9c927a; }
/* ↩ Voltar na fileira de abas: encostado no fim + hit-area ≥44px (::after acima).
   ⚠ 31/07 — "o botão de voltar ficou o antigo, não o novo" (Lucas). Ele tinha razão e o erro é meu:
   o ↩ que ele ampliou e aprovou na prancha 400 era o de 20px de glifo dentro de um botão de 52px; o
   que eu deixei no jogo era este, herdado do MARTELO 4 de 22/07 — 21px de altura e glifo de 12px, que
   nasceu para uma fileira de abas de 21px, não para ser o botão de saída da cartela.
   Agora ele cresce para 28px com glifo de 18px: continua na linha das abas (que sobem junto), mas com
   a presença do que ele aprovou. A moldura é a MESMA botao_chip dos outros — só o porte muda. */
.bt-sktabs .bt-btn.back, .bt-skilltabs .bt-btn.back {
  margin-left: auto; display: inline-flex; align-items: center; justify-content: center; line-height: 1;
  height: 28px; min-height: 28px; min-width: 44px; padding: 0 10px; font-size: 18px; position: relative;
  color: #f2e6c8; }
.bt-sktabs .bt-btn.back::before, .bt-skilltabs .bt-btn.back::before { border-width: 9px; }
/* a fileira de abas passa a acompanhar a altura do ↩ para os dois ficarem na mesma linha de base */
.bt-sktabs, .bt-skilltabs { min-height: 28px; align-items: center; }
/* LOG: overlay transparente no canto superior direito (fora do painel) */
/* ONDA 4 (28/07, auditoria visual #9): z 10→1 — o log NUNCA mais pinta por cima do nome/raridade dos mobs
   (z≤6) nem fura o véu de Vitória/Derrota (z=8). Custo aceito: mob à direita pode cobrir o log às vezes
   (o inverso do defeito antigo do fix 16/07 — melhor o LOG perder do que o nome do alvo). */
.bt-log { position: absolute; top: 8px; right: 12px; z-index: 1; width: 300px; max-width: 32vw; pointer-events: none; overflow: hidden; display: flex; flex-direction: column; align-items: flex-end; text-align: right; font-size: 12px; line-height: 1.45; }
.bt-log p { margin: 0; opacity: .5; text-shadow: 0 1px 2px #000, 0 0 6px rgba(0,0,0,.9); } .bt-log p.new { opacity: 1; color: #fff; }
.bt-log .g { color: var(--gold,#d9b24c); font-weight: 700; }

/* ═══ FITA DE ORDEM DE TURNO (variante A aprovada Lucas 14/07) — medalhões sobre o campo, canto sup. ESQUERDO ═══
   Ordem REAL do round (SPD) vinda do motor (renderFita em battle.js). pointer-events:none → NÃO rouba clique
   do campo; z-index alto pra ficar sobre as unidades sem cobrir a fileira de trás (fica no cantinho). PC mostra
   rótulo "ORDEM DE TURNO" + nome do ativo ao lado; celular reduz (só medalhões) via @media. */
.bt-fita { position: absolute; left: 20px; top: 14px; z-index: 8; pointer-events: none; }
.bt-fita-title { color: #cdbf9a; font-family: var(--font-title); font-size: 11px; letter-spacing: .12em; margin-bottom: 8px; text-shadow: 0 1px 2px #000; } /* selo "AGORA" removido (Lucas 15/07) → folga encolheu 18→8px */
.bt-fita-body { display: flex; align-items: flex-start; gap: 10px; }
.bt-fita-row { display: flex; align-items: flex-start; gap: 8px; }
.bt-fita-name { align-self: center; color: #f2e6c4; font-family: var(--font-title); font-weight: 700; font-size: 15px; letter-spacing: .02em; text-shadow: 0 2px 3px #000; }
.bt-more { align-self: center; color: #cdbf9a; font-family: var(--font-title); font-weight: 700; text-shadow: 0 1px 2px #000; padding-left: 2px; }
/* medalhão: retrato (herói, já vem no aro) OU aro vazio (.aro) + sprite/inicial de mob dentro */
/* ═══ P2 (236, martelo Lucas 22/07) — PLACA-CHIP "Vez de <herói>" junto à fita ═══════════════════════
   O texto que morava no painel inferior (celular) agora é um CHIP ao lado dos medalhões: Pixelify caixa
   normal 12px (recomendação da prancha 234; Lucas pediu MENOR que os 13 propostos), fundo OPACO (régua
   213 de legibilidade), centrado na altura do medalhão grande. Só o renderFita celular o emite; a base
   esconde por segurança. Posição provada na 234: 0 interseção com nomes da cena e com o log. */
.bt-turnchip { display: none; }
@media (max-height: 500px) {
  .bt-turnchip { display: block; align-self: center; margin-left: 7px; padding: 2px 8px; border-radius: 7px;
    background: #0c0a07; border: 1px solid rgba(214,178,90,.55);
    font-family: "Pixelify Sans", var(--font-body); font-size: 12px; line-height: 1.25; color: #ffe9a8;
    text-shadow: 0 1px 0 #000; white-space: nowrap; box-shadow: 0 1px 3px rgba(0,0,0,.6); }
}
.bt-med { position: relative; flex: 0 0 auto; border-radius: 50%; background-size: cover; background-position: center; image-rendering: pixelated; filter: drop-shadow(0 3px 4px rgba(0,0,0,.6)); }
.bt-med.aro { background-image: url("../assets/ui/pixellab/god_retrato_aro.png"); }
/* MEDALHÃO DE MOB (fix 16/07): SÓ O ROSTO. Uma máscara circular escura DENTRO do aro esconde o fundo
   falso/quadriculado do PNG e o miolo do aro; o sprite é AMPLIADO e ancorado no TOPO (recorte da cabeça),
   object-fit cover pra preencher o círculo sem esticar. Vale p/ qualquer mob (dragão/goblin/slime). */
.bt-medmask { position: absolute; inset: 14%; border-radius: 50%; overflow: hidden; background: radial-gradient(circle at 50% 42%, #23201a, #14110c 78%); }
.bt-medspr { width: 100%; height: 100%; object-fit: cover; object-position: center top; transform: scale(1.72); transform-origin: center top; image-rendering: pixelated; }
.bt-medini { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: var(--font-title); font-weight: 800; text-shadow: 0 1px 2px #000; }
/* contorno sutil aliado (verde) × inimigo (vermelho) — ideia forte da variante B nesse detalhe */
.bt-med.ally { box-shadow: 0 0 0 2px rgba(120,200,110,.7); }
.bt-med.foe  { box-shadow: 0 0 0 2px rgba(224,110,90,.7); }
/* ATIVO ("AGORA"): aro dourado + brilho; ganha do contorno de lado */
.bt-med.active { box-shadow: none; filter: drop-shadow(0 0 10px rgba(232,192,96,.8)) drop-shadow(0 4px 6px rgba(0,0,0,.6)); }
.bt-med.active::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; border: 3px solid var(--gold,#e8c060);
  box-shadow: 0 0 12px 2px rgba(232,192,96,.55), inset 0 0 8px rgba(232,192,96,.3); }
.bt-ord { position: absolute; left: -3px; bottom: -3px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 8px;
  background: #1a1712; border: 1px solid #6d5a2e; color: #e8dcc0; font-family: var(--game-font); font-weight: 700; text-align: center; line-height: 14px; }
/* .bt-now (selo "AGORA") removido a pedido do Lucas 15/07 — o aro dourado do .bt-med.active basta. */

/* ═══ BARRA DE CHI DO MONGE (martelo Lucas 28/07 — proposta A "Rosário" de 311_barra_chi, EMBAIXO do
   Monge NA CENA; o ☯ saiu dos chips de status/roster). Contas jade no fio; a 3ª conta tem aro dourado
   (marco do custo dos Punhos do Dragão). Com teto alto (Faminto por Chi) as contas encolhem (.muitos). */
.bt-chi { position: relative; margin-top: 3px; display: flex; align-items: center; justify-content: center; gap: 3px; min-height: 12px; max-width: 118px; flex-wrap: wrap; }
.bt-chi::before { content: ""; position: absolute; left: 4px; right: 4px; top: 50%; height: 2px; transform: translateY(-50%); background: linear-gradient(90deg, transparent, #5a4d33, #5a4d33, transparent); border-radius: 2px; }
.bt-chi b { position: relative; width: 10px; height: 10px; border-radius: 50%; background: #0d0c11; box-shadow: inset 0 0 0 1px #4a4030, 0 0 0 1px #06050a; }
.bt-chi b.on { background: radial-gradient(circle at 35% 32%, #9ae8f5, #6ec6d9 55%, #2b7d90); box-shadow: inset 0 0 0 1px rgba(255,255,255,.35), 0 0 0 1px #0a3b45, 0 0 6px rgba(110,198,217,.8); }
.bt-chi b.marco { box-shadow: inset 0 0 0 1px #4a4030, 0 0 0 1px var(--gold,#d9b24c); }
.bt-chi b.on.marco { box-shadow: inset 0 0 0 1px rgba(255,255,255,.35), 0 0 0 1px var(--gold,#d9b24c), 0 0 7px rgba(212,169,78,.9); }
.bt-chi.muitos { gap: 2px; }
.bt-chi.muitos b { width: 7px; height: 7px; }

/* resultado */
.bt-result { position: absolute; inset: 0; z-index: 8; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; background: rgba(8,7,5,.74); animation: bt-fade .4s ease; cursor: pointer; }
@keyframes bt-fade { from{opacity:0} to{opacity:1} }
.bt-result h2 { font-family: var(--font-title); font-size: 34px; margin: 0; letter-spacing: .04em; text-shadow: 0 3px 12px #000; }
.bt-result.win h2 { color: #ecd07a; } .bt-result.lose h2 { color: #d16a5a; }
.bt-result .sub { opacity: .85; font-size: 14px; }
/* ── Tela de Recompensa (vitória) — POPUP ARPG: XP do grupo + ouro (3 moedas) + loot ── */
.bt-reward { cursor: default; background: rgba(6,5,9,.8); }
/* ═══ RECOMPENSA "FAIXA DE CONQUISTA" (escolha Lucas 28/07, prancha 316 D) — moldura DOURADA (a mesma
   do painel de batalha, botao_chip fatia 26) + card MAIS ESTREITO ("menos largo", martelo dele). ═══ */
/* LEITURA CONFORTÁVEL (martelo Lucas 28/07: "ler tudo sem ficar apertado no celular") — largura um fio
   maior que a v1 estreita + tipografia no PISO de leitura dele (corpo ≥12.5-13px) + mais respiro. */
.rw-pop { width: min(392px, 92vw); max-height: min(90vh, 640px); overflow: hidden; padding: 12px 13px 11px; display: flex; flex-direction: column; gap: 9px;
  border: 16px solid transparent; border-image: url("../assets/ui/pixellab/botao_chip.png") 26 fill stretch;
  box-shadow: 0 24px 60px -18px #000;
  animation: rw-pop .35s cubic-bezier(.2,.9,.3,1.2); }
/* corpo ROLÁVEL: o Continuar é rodapé fixo do card (fora daqui) e nunca cobre o conteúdo */
.rw-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 9px; padding-right: 3px; }
.rw-body::-webkit-scrollbar { width: 6px; }
.rw-body::-webkit-scrollbar-thumb { background: #7c6234; border-radius: 3px; }
.rw-body::-webkit-scrollbar-track { background: rgba(0,0,0,.35); }
/* HERÓI DO ESPÓLIO: o drop de maior raridade, grande, com a faixa da própria cor */
.rw-hero { display: flex; align-items: center; gap: 12px; padding: 7px 14px; border-radius: 12px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--rc) 20%, transparent), transparent); }
.rw-hero .rw-lic.big { width: 54px; height: 54px; font-size: 29px; border-radius: 11px; flex: 0 0 auto; }
.rw-hname { font-size: 14.5px; font-weight: 800; color: #f0ece0; line-height: 1.3; }
.rw-hinfo { display: flex; flex-direction: column; gap: 3px; align-items: flex-start; min-width: 0; }
.rw-hero .rw-lmeta { font-size: 12px; opacity: .85; }
.rw-minors .rw-lrow { padding: 5px 8px; }
.rw-minors .rw-lic { width: 30px; height: 30px; font-size: 15px; border-radius: 7px; }
.rw-minors .rw-lname { font-size: 13px; }
.rw-minors .rw-lmeta { font-size: 12px; }
/* LINHA DE VENDA minimalista (almas + lixo) — expansível no toque; corpo ≥13px (régua do Lucas) */
.rw-sell { cursor: pointer; border: 0; font-family: inherit; font-size: 13px; color: #a99e8a; text-align: center;
  background: rgba(0,0,0,.3); border-radius: 10px; padding: 8px 12px; min-height: 36px; box-shadow: inset 0 0 0 1px #2a2114; }
.rw-sell i { font-style: normal; color: #b89355; }
.rw-sell:hover { filter: brightness(1.15); }
.rw-sell-list.hidden { display: none; }
@keyframes rw-pop { from{opacity:0;transform:translateY(10px) scale(.96)} to{opacity:1;transform:none} }
/* RÉGUA DE LEITURA (28/07): nada de texto informativo abaixo de ~12px; rótulos 10.5px por serem caixa-alta curta */
.rw-eb { display: none; } /* "Batalha concluída" era redundante com o título — sai e devolve altura ao conteúdo */
.rw-h2 { font-family: var(--font-title); font-weight: 700; font-size: 20px; letter-spacing: .06em; text-transform: uppercase; color: #f2e6c8; text-shadow: 0 0 18px rgba(230,200,102,.35); margin: 0 0 2px; }
.rw-strip { display: grid; grid-template-columns: 1.5fr 1fr; gap: 12px; align-items: center; padding: 9px 11px; border-radius: 10px; background: rgba(255,255,255,.03); box-shadow: inset 0 0 0 1px rgba(255,255,255,.06); }
.rw-lbl { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; opacity: .7; font-weight: 700; }
.rw-xpn { font-size: 22px; font-weight: 900; color: #68d77a; font-variant-numeric: tabular-nums; }
.rw-pts { font-size: 11.5px; font-weight: 800; color: #a9e34b; }
.rw-xpbar { height: 7px; border-radius: 4px; background: rgba(255,255,255,.1); overflow: hidden; margin-top: 5px; }
.rw-xpbar i { display: block; height: 100%; width: 0; border-radius: 4px; background: linear-gradient(90deg,#6fd0ff,#4aa0ff); box-shadow: 0 0 8px rgba(90,169,224,.9); }
.rw-prog { display: flex; justify-content: space-between; font-size: 12px; opacity: .85; margin-top: 4px; font-variant-numeric: tabular-nums; }
.rw-goldbox { text-align: right; }
.rw-goldrow { display: flex; align-items: center; justify-content: flex-end; gap: 6px; margin-top: 3px; }
.rw-goldnum { font-size: 21px; font-weight: 900; color: #ffdf8a; font-variant-numeric: tabular-nums; }
.rw-loot { display: flex; flex-direction: column; gap: 7px; }
.rw-lrow { display: grid; grid-template-columns: 34px 1fr auto; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 9px;
  background: rgba(255,255,255,.03);
  background: linear-gradient(90deg, color-mix(in srgb, var(--rc) 15%, transparent), rgba(255,255,255,.015));
  box-shadow: inset 0 0 0 1px var(--rc), 0 0 14px -7px var(--rc); }
.rw-lic { width: 34px; height: 34px; border-radius: 8px; display: grid; place-items: center; font-size: 17px; background: linear-gradient(160deg, rgba(255,255,255,.08), rgba(0,0,0,.4)); box-shadow: inset 0 0 0 1.5px var(--rc); overflow: hidden; }
/* ÍCONES REAIS (28/07): a arte pixel do item (CPCItems.iconHtml) mora dentro do soquete sem esticar */
.rw-lic img, .rw-lic .itemimg, .rw-lic canvas, .rw-lic svg { max-width: 100%; max-height: 100%; image-rendering: pixelated; }
.rw-lname { font-size: 13px; font-weight: 800; color: #f0ece0; line-height: 1.3; }
.rw-lmeta { font-size: 12px; opacity: .82; margin-top: 2px; }
/* chip de raridade é caixa-alta curta, mas ainda assim vai a 11px (Lucas: baixa visão) */
.rw-lrar { font-size: 11px; font-weight: 800; color: #0a0910; background: var(--rc); padding: 2px 8px; border-radius: 20px; white-space: nowrap; text-transform: uppercase; }
.rw-nodrop { font-size: 13px; opacity: .6; font-style: italic; text-align: center; padding: 6px; }
.rw-bsec { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; opacity: .7; font-weight: 700; margin-top: 2px; }
/* ═══ CAÇADAS EM RETRATOS (martelo Lucas 28/07: "ao invés do nome, a foto de perfil; clicando, dá o nome
   com a barra embaixo — fecha em 1 ou 2 linhas") — FILEIRA de medalhões (mesma arte da fita de turno) com
   badge "+N" de mortes e ★ no marco cruzado; o TOCADO abre a linha de detalhe (nome · barra · total/meta).
   Antes: uma linha por espécie (e antes disso, um cartão de 3 linhas). ═══ */
.rw-best { display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; padding: 1px 0; }
.rw-bmed { position: relative; width: 44px; height: 44px; flex: 0 0 auto; cursor: pointer; padding: 0; border: 0;
  border-radius: 50%; background: url("../assets/ui/pixellab/god_retrato_aro.png") center/cover no-repeat;
  filter: grayscale(.55) brightness(.8); transition: filter .12s, transform .12s; }
.rw-bmed:hover { filter: grayscale(.2) brightness(1); }
.rw-bmed.on { filter: none; transform: scale(1.06); box-shadow: 0 0 0 2px var(--gold,#e8c060), 0 0 12px rgba(232,192,96,.55); }
.rw-bmed .bt-medmask { position: absolute; inset: 14%; border-radius: 50%; overflow: hidden; background: radial-gradient(circle at 50% 42%, #23201a, #14110c 78%); }
.rw-bmed .bt-medini { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 18px; color: #e8dcc0; text-shadow: 0 1px 2px #000; }
.rw-bplus { position: absolute; right: -4px; bottom: -4px; min-width: 22px; height: 19px; padding: 0 5px; border-radius: 10px;
  background: #1a1712; box-shadow: inset 0 0 0 1px #6d5a2e; color: #ecd07a; font-size: 12px; font-weight: 800; line-height: 19px; font-variant-numeric: tabular-nums; }
.rw-bstar { position: absolute; left: -3px; top: -4px; font-size: 13px; color: #ecd07a; text-shadow: 0 0 7px rgba(236,208,122,.9); }
/* linha de DETALHE do retrato selecionado */
.rw-bdet { display: grid; grid-template-columns: 1fr 64px auto; align-items: center; gap: 9px; padding: 5px 9px; border-radius: 8px;
  background: rgba(255,255,255,.03); box-shadow: inset 0 0 0 1px rgba(255,255,255,.06); }
.rw-bname { font-size: 12.5px; font-weight: 700; color: #e8e2d2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rw-bmeter { height: 6px; border-radius: 3px; background: rgba(0,0,0,.5); overflow: hidden; box-shadow: inset 0 0 0 1px rgba(255,255,255,.06); }
.rw-bmeter i { display: block; height: 100%; width: 0; border-radius: 3px; background: linear-gradient(90deg, #68d77a, #e6c866); transition: width .12s linear; }
.rw-bnum { font-size: 12px; opacity: .85; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rw-bnum b { color: #ecd07a; font-weight: 800; }
/* ONDA 4 (28/07, auditoria visual #2): STICKY no rodapé do card rolável — no celular o card de espólios
   estoura o viewport (T6: 868px de conteúdo p/ 330 de janela) e o Continuar nascia até 469px FORA da tela,
   sem nenhuma pista de rolagem. Sticky mantém o botão sempre visível e o conteúdo rola por trás. */
/* BOTÃO DO KIT (martelo Lucas 28/07: "esse botão de continuar destoa muito de tudo no jogo") — era um
   gradiente dourado liso, estranho ao pixel-art. Agora usa a MESMA receita do Sair da Loja e das abas:
   botao_chip.png fatia 26 fill + fonte de título dourada em caixa-alta. */
.rw-continue { cursor: pointer; flex: 0 0 auto; margin-top: 2px; min-height: 42px; padding: 4px 18px;
  font-family: var(--font-title); font-size: 13px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700;
  color: #f0c77f; text-shadow: 0 1px 2px #000; background: none;
  border: 13px solid transparent; border-image: url("../assets/ui/pixellab/botao_chip.png") 26 fill stretch; }
.rw-continue:hover { color: #ffe6a8; filter: brightness(1.12); }

/* ═══════════ VIEWPORT PC CURTO (~1360×820) — fix 16/07, item 4 ═══════════
   Nesta faixa de altura (501–900px, exclui 1080 e o celular ≤500) a fileira da FRENTE dos heróis ficava
   21–24% ATRÁS do painel inferior (Monge sem pés na grama — 149a_07/08): o painel (265px) sobe até y=555
   e o box do sprite grande (134px) da fileira da frente (top ~85%) afunda até ~578, entrando no painel.
   Levanta TODOS os heróis um filete (margin-top negativo, igual à receita do celular) — a fileira de trás
   já está bem acima da grama e não regride; 1920 (1080) e 812 (≤500) NÃO entram nesta media query. */
@media (min-height: 501px) and (max-height: 900px) {
  .bt-unit.hero { margin-top: -30px; }
}

/* ═══════════ CELULAR (paisagem curta) — antes tudo usava tamanho de desktop ═══════════ */
/* Alvo: telefone deitado (≤500px de altura). Encolhe unidades/painel/barras p/ não empilhar,
   deixa o campo maior, e transforma o menu de perícia num BOTTOM-SHEET em grade (2 col). */
@media (max-height: 500px) {
  /* campo maior, painel menor */
  .bt-field { bottom: 0; }     /* 30/07: rodapé morreu — a cena ocupa a tela inteira no celular */
  /* ANCORAGEM DA GRAMA no celular (14/07 — bug "pés voando"): a arte é `cover` no #battle (container inteiro).
     No PC (retrato-alto) o `cover` é dirigido pela ALTURA e mostra a imagem inteira → a grama cai onde os pés
     pousam. No CELULAR (paisagem larga) o `cover` é dirigido pela LARGURA e RECORTA topo+base → com `center` o
     horizonte sobe e a banda de formação (top% fixo, a MESMA régua dos dois viewports) fica ACIMA da grama.
     Empurrando o enquadramento p/ baixo (mostra mais grama, menos céu) o horizonte desce e TODOS os pés
     (heróis, mobs, invocações) pousam na grama. Vale p/ as 3 cenas (a classe da cena reescreve `background`
     shorthand → repetimos o seletor com a mesma especificidade, e por vir depois no arquivo, vence). PC intacto. */
  #battle,
  #battle.scen-estrada,
  #battle.scen-corrompido { background-position: center 80%; }
  /* .bt-scene::after removido (linha horizontal sobre a arte) — ver base do arquivo */
  /* ═══ PAINEL 230 = V-C ESTÁTICA (🔨 martelo 1 do Lucas, 22/07: "quero o V-C, mas ESTÁTICO") ═══
     ALTURA FIXA 142px nos DOIS estados (normal e grade aberta) — SEM dinâmica/transição. Conta: o piso da
     grade (2 fileiras de 44 + abas, que na V-C dava 140/141) CABE com folga ≥2 (o assert anti-vazamento
     mede; com as abas do martelo 4 em 21px o piso desceu ainda mais). Cena 375−142 = 233 (+8 vs 150). */
  .bt-panel { height: auto; min-height: 0; }   /* 30/07: rodapé morreu, campo cheio */
  .bt-panel::before { border-width: 20px; }
  /* roster Octopath 356px (matriz V-C: barra tanque 145.5 / pares 60.5) | centro 1fr; padding 10 lateral (V-C) */
  .bt-panel-in { grid-template-columns: 356px 1fr; gap: 8px; padding: 6px 10px 4px; }

  /* unidades bem menores → cabem sem sobrepor na largura estreita */
  .bt-unit { width: 86px; } /* era 72 (+30% 13/07, acompanha os sprites) */
  .bt-mob { width: 75px; height: 70px; } /* era 58×54 */
  /* 242 (celular): as MESMAS regras do PC — base do mob = caixa do HERÓI (martelo 1) e o resto vem do --sz
     por classe. O big/boss/colosso/pequeno/crowded do regex morreu; blob normalizado pelo sizeLayout. */
  .bt-mob.has-art .cr { --px: calc(55.5px / var(--gh, 17)); } /* arte-texto = corpo do herói no celular (~55px) */
  .bt-mob.hero-art.has-art .cr { --px: 3.3px; }
  /* SPRITE-SHEET dos heróis no celular: +30% 13/07 (0.38 → 0.49). 128×0.49 ≈ 63px. */
  .bt-mob.hero-sprite.has-art .cr-sprite { --scale: 0.49; }
  /* MOB no celular (242, martelo 1): base 0.62→0.49 = MESMA caixa do herói (humano idêntico ao herói;
     antes o "médio" nascia 27% maior). Sem travas max-* — a escala é determinística pela classe. */
  .mob-sprite { --scale: 0.49; }
  /* auras por PORTE no celular (versões menores das do PC) */
  /* 30/07: aura por AURA_FIX (ver acima) — o celular herda o mesmo % do lado da caixa. */
  /* mini-barra do invocado menor no celular */
  .bt-unit.summon .bt-summonbar { width: 34px; height: 4px; margin-top: 2px; }
  /* (242) .bt-mob.big/.boss .has-art do celular REMOVIDOS — classes do regex aposentado; o porte é o --sz. */
  .bt-avatar { width: 42px; height: 42px; font-size: 18px; }
  .bt-uname { margin-bottom: 2px; }
  /* TRAVA DE NOME na cena — celular (campo estreito): cap menor p/ o nome nunca cruzar o vizinho (interseção 0) */
  .bt-unit.enemy .bt-uname, .bt-unit.enemy .bt-mob-hp { max-width: 70px; }
  /* ALVO FALANTE no celular: chips seguem 12px FÍSICOS (compensação 1/--esc; régua da 234 medida neste
     viewport) e o wrap fica nos MESMOS 96px CSS do PC — largura que deixa 💧 + "⚡ fraco" na mesma linha
     (pilha de 2-3 linhas como na prancha). Interseção 0 com o vizinho provada na suíte (checkKnowLadder). */
  /* barras cresceram (10/07: HP 5→10px, mana 3→8px) → bt-bars ganha um pouco mais de gap/margem, mesma
     lógica do PC, só em escala menor (celular é mais apertado). */
  .bt-bars { width: 54px; margin-top: 4px; gap: 2px; }
  /* SUBIR A BANDA DE FORMAÇÃO NO MOBILE (13/07): sem mexer em PARTY_POS/ENEMY_POS. O bump do desktop
     (bt-bars 7→12) NÃO chega aqui (regra própria acima, margin-top:4), mas a fileira da frente já vinha
     com o BOX da unidade (barras + a faixa reservada de .bt-statuses ~15px) cruzando a borda inferior do
     field (monge +8.6px / mago +5.8px). Como .bt-unit é position:absolute e o top% é o CENTRO (transform
     translate(-50%,-50%)), um margin-top negativo levanta a unidade sem tocar no transform (não conflita
     com .bt-unit.dead nem com o scale inline dos mobs). −12px zera o estouro do box com folga (monge→−3.4,
     mago→−4.2) e as barras em si ficam bem dentro. Só os HERÓIS estouram (pilha HP+mana+status mais alta
     que a do mob, que só tem HP); mobs não precisam. */
  .bt-unit.hero { margin-top: -12px; }
  .bt-uname { font-size: 8.5px; }
  .bt-uname .rar { font-size: 7.5px; }
  /* REDESIGN 10/07: HP ~10px, mana ~8px (proporcional ao PC); border-width IDÊNTICO nas duas (era
     1px 2px na vida e 0 1px na mana — moldura visivelmente mais fina/achatada na mana) pra moldura casar. */
  .bt-hpbar { height: 10px; border-width: 1px 2px; } .bt-manabar { height: 8px; border-width: 1px 2px; }
  .bt-unit.enemy .bt-hpbar { height: 9px; } /* inimigo comum levemente menor, mesma regra do PC */
  .bt-unit.is-boss .bt-hpbar { height: 10px; } /* chefe iguala herói no celular (regra desktop deu 13px — grande demais aqui) */
  .bt-hp-num, .bt-mana-num { font-size: 7px; }
  /* NÚMEROS SEMPRE VISÍVEIS (pedido Lucas 10/07): antes só a unidade .active mostrava HP/mana no celular
     — removido o gate; todo herói mostra os números o tempo todo, igual ao PC (regra do inimigo/bestiário
     não muda, ela mora em refresh()/knowledgeBadge, não aqui). */
  /* CELULAR: a arte de status desce de 20 → 16px (o piso em que ela ainda ganha do emoji) e a faixa
     acompanha. O cap de chips cai de 4 → 3 no JS (heroStatusChips) pelo mesmo corte de @media. */
  :root { --sti: 16px; }
  .bt-statuses { height: 16px; margin-top: 3px; gap: 2px; }
  .bt-statuses span { font-size: 11px; }
  /* rosário de Chi compacto no celular (largura da coluna do herói ~66px) */
  .bt-chi { max-width: 66px; gap: 2px; margin-top: 2px; min-height: 9px; }
  .bt-chi b { width: 7px; height: 7px; }
  .bt-chi.muitos b { width: 5px; height: 5px; }
  /* o rosário na placa segue a mesma régua de celular do antigo (contas de 7px, 5px com teto alto) */
  .pl-chi .pl-chi-in { max-width: 66px; gap: 2px; min-height: 9px; }
  .pl-chi b { width: 7px; height: 7px; }
  .pl-chi .pl-chi-in.muitos b { width: 5px; height: 5px; }
  .bt-float { font-size: 16px; } .bt-float.crit { font-size: 21px; } .bt-float.miss { font-size: 13px; }
  .bt-rar-aura { width: 48px; height: 15px; }
  .bt-unit.rar-elite .bt-rar-aura, .bt-unit.rar-unique .bt-rar-aura, .bt-unit.rar-legendary .bt-rar-aura { width: 54px; height: 16px; }
  .bt-unit.rar-miniboss .bt-rar-aura { width: 62px; height: 19px; opacity: .92; }
  .bt-unit.enemy.targetable::after, .bt-unit.hero.targetable::after { top: -18px; font-size: 12px; }

  /* ═══════════ PAINEL MOBILE v3 (maquete aprovada Lucas 14/07: _previews/120_refino_final_mock.html) ═══════════
     Modelo NOVO (aposenta o "cartão só no ativo" + títulos verticais): UMA moldura única grande em volta de todo
     o painel de informação; títulos GRUPO/INIMIGOS HORIZONTAIS no topo de cada coluna; rosters de colunas ~164px
     com barras ~120px, TODOS os heróis/mobs em linha (nome+valor em cima, barras embaixo); o ativo é destacado só
     pela cor/▶ do nome (sem moldura individual). Centro: linha "▶ Vez de X" no topo + chips de ação em LISTA
     VERTICAL à esquerda + arena (skills/dica) à direita, tudo ancorado no TOPO. Skills abrem INLINE (sem folha). */
  .bt-log { font-size: 10px; line-height: 1.3; max-width: 40vw; top: 6px; right: 8px; }

  /* MOLDURA ÚNICA em volta do painel inteiro (chip + realce) — 230: 2 colunas [roster V-C 356 | centro] */
  .bt-panel-in { position: relative; grid-template-columns: 356px 1fr; gap: 8px; padding: 6px 10px 4px; }
  .bt-panel-in::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    border: 12px solid transparent; border-image: url("../assets/ui/pixellab/botao_chip.png") 26 fill stretch;
    filter: brightness(1.12) contrast(1.06); }

  /* ═══ ROSTER V-C no CELULAR (🔨 martelos 1+3, 22/07 — matriz da maquete 228 que o Lucas escolheu):
     roster 356 → barra tanque 145.5 / pares 60.5 · barras 18/16 · Nº DENTRO 14/12.5 (≥12, régua) ·
     retrato+anel 32/26 · folgas verticais comprimidas (row-gap 0; pad 2/3) — piso normal ~V-C. */
  /* Nomes (matriz A da prancha 234, martelo Lucas 236): tanque 12 / pares 11 — NUNCA <11 (piso da matriz;
     a régua de nº das barras ≥12 segue intacta logo abaixo). Era tanque 13 / pares 12 (martelo 3 da 230). */
  .ally-roster.octo { gap: 1px 8px; }
  .octo .hr { padding: 2px 8px 3px; row-gap: 0; column-gap: 6px; --oport: 26px; }
  .octo .octo-name { font-size: 11px; }
  .octo .octo-name .kotag { font-size: 8.5px; }
  .octo .octo-name .hprep { font-size: 10px; }
  /* P1 (236): chips de status na linha do nome — martelo "diminuam SÓ MAIS UM POUCO" (eram 12 no mock da
     234): ícone 10px nos pares / 11px no tanque; CONTAGEM 10px / 11px (nunca <10px físico — régua). */
  .octo .octo-l1 { gap: 5px; }
  .octo .octo-sts { gap: 3px; }
  .octo .ost em { font-size: 10px; }
  .octo .ost b { font-size: 10px; }
  .octo .ost.more { font-size: 10px; }
  .octo .hr.tankcell .ost em { font-size: 11px; }
  .octo .hr.tankcell .ost b { font-size: 11px; }
  .octo .obar-row { gap: 5px; }
  .octo .obar { height: 16px; }
  .octo .obar > b { font-size: 12.5px; }
  .octo .obar > .lab { font-size: 9.5px; left: 4px; }
  .octo .obar.mp > b { right: 4px; }
  .octo .hr.tankcell { padding: 2px 9px 3px; row-gap: 0; column-gap: 8px; --oport: 32px; }
  .octo .hr.tankcell .octo-name { font-size: 12px; } /* matriz A (234/236): tanque 12 (era 13) */
  .octo .hr.tankcell .obar-row { gap: 7px; }
  .octo .hr.tankcell .obar { height: 18px; }
  .octo .hr.tankcell .obar > b { font-size: 14px; }
  .octo .hr.tankcell .obar > .lab { font-size: 10.5px; }

  /* CENTRO v3 → 236 (P2): a linha "▶ Vez de X" SAIU do painel (virou a placa-chip da fita, em cima) —
     o centro vira só [lista de chips ESQ | arena DIR] e o espaço liberado volta pras ações/arena. */
  .bt-center { grid-template-columns: max-content 1fr; grid-template-rows: 1fr;
    grid-template-areas: "acts arena"; gap: 2px 8px; align-content: start; }
  /* chips = LISTA VERTICAL SOLTA (MARTELO 2: a placa morreu) encostada à esquerda, topo alinhado aos grids.
     V-C (martelo 1): chip 1 linha ~83×18, rótulo MANTIDO (baixa visão); os 5 cabem no painel 142 com folga.
     ⚠️ TOQUE (correção sobre a maquete 228, provado no teste 230): o hit de 40px da matriz NÃO pode ser usado
     numa coluna com passo de 20px — os ::after se sobrepõem e o chip DEBAIXO (pinta por cima, DOM depois)
     ROUBA o toque do centro do chip de cima (clique em "Skills" abria a Bolsa no teste). Hit honesto =
     o PASSO inteiro da coluna (18 + gap 2 = 20px, bordas casadas, partição limpa) + ±2px lateral.
     Desabilitado NÃO estende (chip morto não pode engolir o toque do vizinho). */
  .bt-actions { gap: 6px; }                     /* 30/07: fixed centralizado (regra base); a base vem de --acoes-bottom */
  /* o tamanho vem de --acoes-h (:root), o mesmo que a dica "escolha uma ação" usa para se ancorar */
  .bt-actions .bt-btn::before { border-width: 7px; }
  .bt-actions .bt-btn::after { content: ""; position: absolute; left: -2px; right: -2px; top: 50%; transform: translateY(-50%); height: 20px; }
  .bt-actions .bt-btn:disabled::after { display: none; }
  /* arena à direita: dica/skills ancoradas no TOPO (nunca centradas) */
  .bt-arena { grid-area: arena; align-self: stretch; align-content: start; }
  /* 30/07: o text-align:left daqui era o que punha o "escolha uma ação" de lado. Agora a dica é fixed
     e centrada na mesma régua da barra de ações (ver bloco .bt-turn), então não há mais o que alinhar
     aqui — só a base da barra, que no celular é 6px em vez de 8px. */
  :root { --acoes-bottom: 6px; }
  .bt-turn .tip { font-size: 13px; }
  /* SKILLS/BOLSA INLINE (aposenta a folha/bottom-sheet): abrem na arena à direita da lista, ancoradas no topo */
  .bt-skilltabs { min-height: 20px; gap: 3px; }
  .bt-tab { font-size: 10px; height: 22px; padding: 0 6px; } /* altura FIXA (FIX 1) */
  .bt-skilllist { gap: 4px; }
  .bt-skilllist .bt-btn { min-height: 24px; font-size: 9px; padding: 2px 10px; } /* −2px (18/07) */
  .bt-skilllist .bt-btn::before { border-width: 7px; }
  .bt-skilllist .bt-btn .k { font-size: 10px; }
  .bt-skilltabs .bt-btn.back { margin-left: auto; height: 22px; padding: 0 7px; font-size: 12px; } /* SÓ ÍCONE ↩ */

  /* FITA reduzida no celular (sem rótulo/nome): só medalhões, no cantinho, sem colidir com o log da direita */
  .bt-fita { left: 10px; top: 12px; }
  .bt-fita-title, .bt-fita-name { display: none; }
}

/* ═══ FRENTE 1 (15/07, contrato 130) — Tooltip de descrição de skill/consumível ═══ */
/* Linguagem visual do .item-tip: título dourado + corpo + rodapé custo. Ancorado ACIMA do botão com seta. */
.sk-tip { position: fixed; z-index: 200; width: 250px; pointer-events: none; font-family: var(--game-font);
  background: linear-gradient(180deg, #14120f, #0a0908); border: 1px solid #6a6152; border-radius: 7px;
  padding: 11px 12px; box-shadow: 0 12px 30px rgba(0,0,0,.8), 0 0 0 1px #000; line-height: 1.3; }
.sk-tip.hidden { display: none; }
.sk-tip .tt-name { font-size: 14px; font-weight: 700; color: #e8c877; text-shadow: 0 1px 0 #000; line-height: 1.2; }
.sk-tip .tt-div { height: 1px; background: linear-gradient(90deg, transparent, #6f5a2c, transparent); margin: 8px 0; }
.sk-tip .tt-eff { font-size: 12px; color: #cdbd8e; line-height: 1.5; margin: 1px 0; }
.sk-tip .tt-status { font-size: 11px; color: #a892c9; font-style: italic; margin-top: 6px; line-height: 1.4; }
.sk-tip .tt-cost { font-size: 10px; color: #9a8a63; margin-top: 8px; border-top: 1px solid #2a2216; padding-top: 5px; letter-spacing: .3px; }
.sk-tip .sk-arrow { position: absolute; width: 13px; height: 13px; background: #0a0908; }
.sk-tip .sk-arrow.down { bottom: -7px; border-right: 1px solid #6a6152; border-bottom: 1px solid #6a6152; transform: rotate(45deg); }
.sk-tip .sk-arrow.up { top: -7px; border-left: 1px solid #6a6152; border-top: 1px solid #6a6152; transform: rotate(45deg); }
/* retrato DEDICADO de mob na fita (16/07): arquivo em assets/monsters-battle/retratos/<tipo>.png —
   encaixa inteiro no aro (sem o zoom 1.72 do recorte automático .bt-medspr). */
.bt-medret { width: 100%; height: 100%; object-fit: cover; object-position: center; image-rendering: pixelated; }

/* ═══ CAMPO CHEIO + BARRA DE AÇÕES NA BASE (layout do Lucas, 30/07) ══════════════════════════════
   O rodapé de 190px sai: com vida/mana na PLACA AO PÉ de cada personagem, o roster do painel virava
   informação repetida ("não quero também esse status deles divididos"). O campo passa a ocupar a
   altura inteira e as ações viram cinco ícones flutuando na base.
   position: FIXED e não absolute — com absolute, "left: 50%" é metade do CONTAINER, e foi exatamente
   isso que deixou a barra à esquerda nas três tentativas anteriores. Fixed mede a viewport.
   A arena (.bt-arena, grade de skills e dica de turno) continua no fluxo do painel, agora ancorada
   logo acima dos botões — todo o fluxo de ação/mira/skills segue escrevendo nos mesmos seletores. */
.bt-panel { height: auto; min-height: 0; background: none; border-top: 0; top: auto; }
.bt-panel-in { background: none; }
/* ═══ A PELE ÓRFÃ DO RODAPÉ MORTO (Lucas 30/07: "tem uma moldura enorme no fundo da tela") ═════════
   Quando o rodapé de 190px saiu, eu zerei a CAIXA (height:auto, background:none) e esqueci a PELE.
   Os dois ::before continuaram pintando `border: 20px/12px ... fill` — e borda NÃO colapsa junto com a
   altura: a .bt-panel virou 926×10 no pé da tela e mesmo assim desenhava 40px de moldura ornamentada
   esticados por toda a largura. Era isso a faixa que ele viu.
   Trocar um elemento é herdar TODAS as regras do antigo, inclusive as que moram em pseudo. */
.bt-panel::before, .bt-panel-in::before { display: none !important; }
.bt-zone.bt-allies { display: none; }          /* o roster virou placa ao pé de cada herói */
.bt-field { bottom: 0; }                        /* a cena toma o espaço que era do rodapé */
.bt-actions { position: fixed; left: 50%; bottom: var(--acoes-bottom); transform: translateX(-50%);
  z-index: 30; display: flex; flex-direction: row; align-items: center; justify-content: center;
  gap: 7px; width: auto; max-width: none; padding: 0; margin: 0; background: none;
  border: 0; box-shadow: none; grid-area: auto; align-self: auto; align-content: center; }
.bt-center { background: none; }
/* pointer-events: NONE é obrigatório aqui. A arena é uma caixa fixa e larga sobre a cena; sólida, ela
   engolia o clique de mira dos mobs que caíssem debaixo dela (medido: 2 de 5 inimigos ficavam
   inclicáveis e a luta por toque travava). Vazada, o clique atravessa, e só o que tem conteúdo de
   verdade — a grade de skills, a bolsa, os chips — volta a receber toque. */
/* ═══ CARTELA B — o painel abre NO LUGAR da barra (maquete 403, aprovada Lucas 31/07) ═══════════════
   O que ele reclamou: "as skills só voam e estão até desalinhadas". Medido antes de mexer: a arena era
   uma caixa FIXA de 520px, e a grade (max-width 240) nascia encostada à esquerda dela — 140px fora do
   centro da barra. O ↩, com `margin-left:auto` nessa mesma caixa de 520, ia parar a 220px das abas, no
   meio do campo. A regra do ↩ sempre esteve certa; era a CAIXA que estava larga demais.
   Agora a arena ABRAÇA o conteúdo (width:max-content) e nasce na MESMA base da barra de ações — a
   troca é literalmente no lugar. Com a caixa justa, o "fim da linha" das abas passa a ser a borda
   direita da última coluna de ícones, que é o alinhamento que ele desenhou. */
.bt-arena { position: fixed; left: 50%; bottom: var(--acoes-bottom); transform: translateX(-50%);
  z-index: 31; width: max-content; max-width: min(94vw, 430px); pointer-events: none; }
.bt-arena .bt-detail, .bt-arena .bt-skilllist, .bt-arena .bt-btn, .bt-arena .sk-card { pointer-events: auto; }
/* Fundo + moldura só quando há conteúdo — cartela vazia sobre a cena seria uma mancha preta à toa.
   ⚠ O GATILHO É A CLASSE, NÃO `:has()` (Lucas 31/07: "a moldura das skills não está aparecendo").
   Eu tinha escrito `.bt-arena:has(.bt-detail:not(:empty))` e conferido no Chrome headless, onde
   `:has()` existe há muito. No iPhone dele a regra inteira pode ser descartada pelo parser — e regra
   descartada não avisa, ela só não pinta. `#battle.bt-skillmode` é a MESMA condição (o JS liga essa
   classe ao abrir perícia/bolsa e desliga ao fechar) sem depender de suporte a seletor novo. */
#battle.bt-skillmode .bt-arena { padding: 10px 13px 10px; pointer-events: auto; }
#battle.bt-skillmode .bt-arena::before { content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none; border: 22px solid transparent;
  border-image: url("../assets/ui/pixellab/hud_panel.png") 40 fill stretch; }
/* a barra some enquanto a cartela está aberta: as duas ocupam o MESMO retângulo, não podem coexistir */
#battle.bt-skillmode .bt-actions { display: none; }
/* cabeçalho: título CENTRALIZADO (o ↩ saiu daqui, então o texto ocupa o centro de verdade) */
.bt-pncab { display: flex; align-items: center; justify-content: center; padding: 0 1px 5px;
  border-bottom: 1px solid #4b3d22; margin-bottom: 2px; }
.bt-pncab span { font-family: var(--font-title); font-size: 11px; letter-spacing: .14em; color: #e3c98d;
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* a Bolsa não tem abas de escola — ganha uma etiqueta à esquerda pro ↩ ter de onde ser empurrado */
.bt-skilltabs .bolsa-eti { font-family: var(--font-title); font-size: 10px; letter-spacing: .14em; color: #9b8a5f; }
.bt-arena .bt-skilllist { flex-wrap: wrap; }

/* ═══ CAVEIRINHAS DA CORRUPÇÃO (Lucas 30/07) ═══════════════════════════════════════════════════
   "os mobs corrompidos podiam ter a aura animada saindo caveirinhas explodindo como bolhas de sabão".
   A caveira é pixel desenhado em CSS (7×7), a mesma técnica de mapa-de-caracteres do creature-art.js
   — não há arquivo de arte para manter, e ela recolore e redimensiona por variável.
   O CONTORNO é pixel, não filtro: cercar por drop-shadow custava metade do FPS na cena com sete
   corrompidos (medido). Aqui sobra UM filtro, só para o halo roxo — o mesmo halo do nome.
   Escolhas dele: efervescente (várias, miúdas), tamanho C (o menor) e ciclo mais lento. */
.cav-corr { position: absolute; width: var(--p); height: var(--p); background: transparent;
  --p: 1.2px; --c: #d9d2bd; --borda: #07060a; z-index: 5; pointer-events: none;
  box-shadow:
    calc(var(--p)*1) calc(var(--p)*0) 0 var(--c), calc(var(--p)*2) calc(var(--p)*0) 0 var(--c), calc(var(--p)*3) calc(var(--p)*0) 0 var(--c),
    calc(var(--p)*4) calc(var(--p)*0) 0 var(--c), calc(var(--p)*5) calc(var(--p)*0) 0 var(--c), calc(var(--p)*0) calc(var(--p)*1) 0 var(--c),
    calc(var(--p)*1) calc(var(--p)*1) 0 var(--c), calc(var(--p)*2) calc(var(--p)*1) 0 var(--c), calc(var(--p)*3) calc(var(--p)*1) 0 var(--c),
    calc(var(--p)*4) calc(var(--p)*1) 0 var(--c), calc(var(--p)*5) calc(var(--p)*1) 0 var(--c), calc(var(--p)*6) calc(var(--p)*1) 0 var(--c),
    calc(var(--p)*0) calc(var(--p)*2) 0 var(--c), calc(var(--p)*2) calc(var(--p)*2) 0 var(--c), calc(var(--p)*3) calc(var(--p)*2) 0 var(--c),
    calc(var(--p)*4) calc(var(--p)*2) 0 var(--c), calc(var(--p)*6) calc(var(--p)*2) 0 var(--c), calc(var(--p)*0) calc(var(--p)*3) 0 var(--c),
    calc(var(--p)*1) calc(var(--p)*3) 0 var(--c), calc(var(--p)*2) calc(var(--p)*3) 0 var(--c), calc(var(--p)*3) calc(var(--p)*3) 0 var(--c),
    calc(var(--p)*4) calc(var(--p)*3) 0 var(--c), calc(var(--p)*5) calc(var(--p)*3) 0 var(--c), calc(var(--p)*6) calc(var(--p)*3) 0 var(--c),
    calc(var(--p)*1) calc(var(--p)*4) 0 var(--c), calc(var(--p)*2) calc(var(--p)*4) 0 var(--c), calc(var(--p)*3) calc(var(--p)*4) 0 var(--c),
    calc(var(--p)*4) calc(var(--p)*4) 0 var(--c), calc(var(--p)*5) calc(var(--p)*4) 0 var(--c), calc(var(--p)*1) calc(var(--p)*5) 0 var(--c),
    calc(var(--p)*3) calc(var(--p)*5) 0 var(--c), calc(var(--p)*5) calc(var(--p)*5) 0 var(--c), calc(var(--p)*-1) calc(var(--p)*0) 0 var(--borda),
    calc(var(--p)*-1) calc(var(--p)*1) 0 var(--borda), calc(var(--p)*-1) calc(var(--p)*2) 0 var(--borda), calc(var(--p)*-1) calc(var(--p)*3) 0 var(--borda),
    calc(var(--p)*-1) calc(var(--p)*4) 0 var(--borda), calc(var(--p)*0) calc(var(--p)*-1) 0 var(--borda), calc(var(--p)*0) calc(var(--p)*0) 0 var(--borda),
    calc(var(--p)*0) calc(var(--p)*4) 0 var(--borda), calc(var(--p)*0) calc(var(--p)*5) 0 var(--borda), calc(var(--p)*0) calc(var(--p)*6) 0 var(--borda),
    calc(var(--p)*1) calc(var(--p)*-1) 0 var(--borda), calc(var(--p)*1) calc(var(--p)*2) 0 var(--borda), calc(var(--p)*1) calc(var(--p)*6) 0 var(--borda),
    calc(var(--p)*2) calc(var(--p)*-1) 0 var(--borda), calc(var(--p)*2) calc(var(--p)*5) 0 var(--borda), calc(var(--p)*2) calc(var(--p)*6) 0 var(--borda),
    calc(var(--p)*3) calc(var(--p)*-1) 0 var(--borda), calc(var(--p)*3) calc(var(--p)*6) 0 var(--borda), calc(var(--p)*4) calc(var(--p)*-1) 0 var(--borda),
    calc(var(--p)*4) calc(var(--p)*5) 0 var(--borda), calc(var(--p)*4) calc(var(--p)*6) 0 var(--borda), calc(var(--p)*5) calc(var(--p)*-1) 0 var(--borda),
    calc(var(--p)*5) calc(var(--p)*2) 0 var(--borda), calc(var(--p)*5) calc(var(--p)*6) 0 var(--borda), calc(var(--p)*6) calc(var(--p)*-1) 0 var(--borda),
    calc(var(--p)*6) calc(var(--p)*0) 0 var(--borda), calc(var(--p)*6) calc(var(--p)*4) 0 var(--borda), calc(var(--p)*6) calc(var(--p)*5) 0 var(--borda),
    calc(var(--p)*6) calc(var(--p)*6) 0 var(--borda), calc(var(--p)*7) calc(var(--p)*0) 0 var(--borda), calc(var(--p)*7) calc(var(--p)*1) 0 var(--borda),
    calc(var(--p)*7) calc(var(--p)*2) 0 var(--borda), calc(var(--p)*7) calc(var(--p)*3) 0 var(--borda), calc(var(--p)*7) calc(var(--p)*4) 0 var(--borda);
  filter: drop-shadow(0 0 4px rgba(90,40,150,.8));
  animation: cav-sobe var(--dur, 3.4s) linear infinite; animation-delay: var(--atraso, 0s); }
/* Bolha de sabão: sobe bamboleando (linha reta lê como faísca, não como bolha), incha um repuxo e
   arrebenta. O estouro é CURTO de propósito: esticada 2×, a caveira de pixels vira um retângulo
   branco borrado — 1,34× é o máximo que ainda lê como "pop". */
/* SOBE POUCO E ARREBENTA CEDO (martelo Lucas 30/07: "é só subir do pé e ela sumir antes").
   O curso NÃO pode ser fixo: o mob mede de 79px (Basilisco) a 228px (Hidra), e os 46px que eu tinha
   posto davam 58% do corpo no menor e 20% no maior — no menor a caveira ainda chegava ao tronco.
   Agora o curso vem em --curso, escrito por mob em semeiaCaveiras() como fração da altura MEDIDA:
   cada caveira sobe o mesmo tanto em relação ao próprio bicho e some sempre na altura das pernas. */
@keyframes cav-sobe {
  0%   { transform: translate(0, 0) scale(.45); opacity: 0; }
  14%  { transform: translate(2px, calc(var(--curso, 40px) * -0.15)) scale(1); opacity: .95; }
  40%  { transform: translate(-4px, calc(var(--curso, 40px) * -0.41)) scale(1.03); opacity: .82; }
  66%  { transform: translate(4px, calc(var(--curso, 40px) * -0.67)) scale(1); opacity: .62; }
  86%  { transform: translate(-2px, calc(var(--curso, 40px) * -0.89)) scale(1.03); opacity: .4; }
  95%  { transform: translate(1px, calc(var(--curso, 40px) * -0.98)) scale(1.2); opacity: .22; }
  100% { transform: translate(1px, calc(var(--curso, 40px) * -1)) scale(1.32); opacity: 0; }
}
}
@media (prefers-reduced-motion: reduce) { .cav-corr { animation: none; opacity: 0; } }

/* ═══ ÍCONES DE INTERFACE (30/07) ═══════════════════════════════════════════════════════════════
   Os PNG de assets/ui/icons entrando no lugar dos emoji. Nasceram 64×64; na interface entram na
   altura da LINHA DE TEXTO, para o ícone acompanhar a fonte em vez de brigar com ela — daí o 1.15em
   e o vertical-align. `image-rendering: pixelated` porque é pixel art: suavizar borra o desenho. */
/* A ARTE PODE VIR POR VARIÁVEL (--ic), e não só por uma classe própria (30/07). Existem 171 PNGs de
   UI em 12 pastas do acervo; escrever .ic-<nome> para cada um foi o que produziu 6 classes das quais
   só UMA (.ic-ouro) era chamada — arte pronta parada porque ninguém escreveu o CSS dela.
   Com --ic, quem usa passa o caminho na hora: ico("anel","equip") e pronto. As classes antigas
   continuam valendo (vêm depois e sobrescrevem o var). */
.ic-ui { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.2em;
  image-rendering: pixelated; background: center/contain no-repeat; background-image: var(--ic, none); }
.ic-ouro { background-image: url("../assets/ui/icons/ouro.png"); }
.ic-bolsa-ouro { background-image: url("../assets/ui/icons/bolsa_ouro.png"); }
.ic-pocao { background-image: url("../assets/ui/icons/pocao.png"); }
.ic-erva { background-image: url("../assets/ui/icons/erva.png"); }
.ic-comida { background-image: url("../assets/ui/icons/comida.png"); }
.ic-elmo { background-image: url("../assets/ui/icons/elmo.png"); }
