/* ---------- Design tokens ----------
   Preset "flash" fechado via sandbox em /design. Um único tema agora.

   Princípios:
     - 1 fonte (Nunito) em vários pesos
     - 1 stroke (--ww-line-width) em todas as bordas
     - 2 radius: --ww-radius pra painéis, --ww-card-radius pra cards do mundo
     - 1 sombra chapada preta deslocada (--ww-shadow)
     - paleta compacta: canvas, raised, sunken, line, accent, 2 de texto
*/
:root {
  /* Fonte — stack com fallback de emoji sempre presente pra emoji render. */
  --ww-font-body: 'Nunito', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', system-ui, sans-serif;
  --ww-font-display: 'Nunito', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', system-ui, sans-serif;

  /* Superfície */
  --ww-stage-bg: #3fbfa8;           /* palco teal chapado */
  --ww-surface-raised: #eafaf5;     /* painéis, modais, tooltip */
  --ww-surface-sunken: #2fa38c;     /* áreas "dentro" (inputs, tracks) */

  /* Linha */
  --ww-line: #111;
  --ww-line-width: 3px;

  /* Acento */
  --ww-accent: #f2811d;
  --ww-accent-dim: #c05f08;

  /* Neutro — o "acento que não convida". Existe pra um caso só: sinalizar
     interação em algo que o jogador NÃO leva (a carta plantada sob o cursor).
     Cinza claro frio, escolhido pra destacar contra a mesa teal e contra a
     aura colorida das cartas fixas sem competir com o laranja. */
  --ww-neutral: #d5dedb;

  /* Texto */
  --ww-text-primary: #111;
  --ww-text-muted: #3f5f57;

  /* Geometria */
  --ww-radius: 8px;
  --ww-card-radius: 6px;
  --ww-shadow: 0 4px 0 var(--ww-line);

}

/* Palco é desenhado pelo Phaser (TileSprite no worldLayer — responde a
   pan/zoom como mesa). Body fica neutro; stage-bg é fallback visual pra
   qualquer fração de segundo antes do canvas ligar. */
body { background: var(--ww-stage-bg); }
#game canvas { background: transparent; }


/* ═══════════════════════════════════════════════════════════════════════
   PRIMITIVAS FLASH
   ───────────────────────────────────────────────────────────────────────
   Tudo no jogo deve compor estas classes ao invés de estilar do zero.
   Blocos novos (store modal, menu, pause, gameover, confirm) usam estas.
   Blocos legados (.ww-hud__*, .ww-store-modal__*) devem ser migrados pra cá.

   Regras de uso:
     - 1 painel = 1 .ww-panel (nunca reinventar fundo+border+radius+shadow)
     - 1 botão = 1 .ww-button (nunca <button> sem classe, nunca filter/gradient)
     - 1 input = 1 .ww-input (nunca <input> estilo sistema)
     - Tipografia = 1 de (.ww-heading | .ww-label | .ww-body)
     - Estado "quente" = modificador --accent, não chrome adicional
═══════════════════════════════════════════════════════════════════════ */

/* --- Painel: geometria base (carta, card, modal, HUD painel). --- */
.ww-panel {
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  color: var(--ww-text-primary);
  padding: 12px 14px;
}
.ww-panel--accent {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}
.ww-panel--sunken {
  background: var(--ww-surface-sunken);
  box-shadow: none;
}
.ww-panel--flush { padding: 0; }

/* --- Botão: pressable com sombra chapada. Hover sobe, active afunda.
       Nunca usar filter/gradient. Estados via modificador. --- */
.ww-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: var(--ww-font-body);
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--ww-text-primary);
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 8px 16px;
  cursor: pointer;
  user-select: none;
  /* Só transform e box-shadow chapada — zero filter, zero repaint pesado. */
  transition: transform 0.08s ease, box-shadow 0.08s ease, background-color 0.1s linear, color 0.1s linear;
}
.ww-button:hover:not(:disabled) {
  transform: translate(-1px, -1px);
  box-shadow: 0 6px 0 var(--ww-line);
}
.ww-button:active:not(:disabled) {
  transform: translate(0, 2px);
  box-shadow: 0 2px 0 var(--ww-line);
}
.ww-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.ww-button--primary,
.ww-button.is-active {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}
.ww-button--ghost {
  background: transparent;
  box-shadow: none;
}
.ww-button--ghost:hover:not(:disabled) {
  background: var(--ww-surface-raised);
  box-shadow: var(--ww-shadow);
}
.ww-button--small {
  font-size: 12px;
  padding: 6px 12px;
}
.ww-button--icon {
  width: 36px;
  height: 36px;
  padding: 0;
}

/* --- Input: text input com a mesma geometria do painel sunken. --- */
.ww-input {
  font-family: var(--ww-font-body);
  font-size: 14px;
  font-weight: 700;
  color: var(--ww-text-primary);
  background: var(--ww-surface-sunken);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  padding: 8px 12px;
  outline: none;
}
.ww-input::placeholder { color: var(--ww-text-muted); opacity: 0.7; }
.ww-input:focus { background: var(--ww-surface-raised); }

/* --- Tipografia: 3 níveis, Nunito, pesos firmes. --- */
.ww-heading {
  font-family: var(--ww-font-display);
  font-size: calc(24px * var(--a11y-text, 1));
  font-weight: 900;
  line-height: 1.1;
  color: var(--ww-text-primary);
  letter-spacing: -0.3px;
}
.ww-heading--hero { font-size: calc(40px * var(--a11y-text, 1)); }
.ww-heading--small { font-size: calc(18px * var(--a11y-text, 1)); }

.ww-label {
  font-size: calc(10px * var(--a11y-text, 1));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--ww-text-muted);
  line-height: 1;
}

.ww-body {
  font-family: var(--ww-font-body);
  font-size: calc(13px * var(--a11y-text, 1));
  font-weight: 600;
  line-height: 1.45;
  color: var(--ww-text-primary);
}
.ww-body--muted { color: var(--ww-text-muted); }

/* --- Badge: sticker pequeno (tag, price, variant). --- */
.ww-badge {
  display: inline-block;
  font-family: var(--ww-font-body);
  font-size: calc(10px * var(--a11y-text, 1));
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 8px;
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 4px);
  background: var(--ww-surface-raised);
  color: var(--ww-text-primary);
  line-height: 1.4;
}
.ww-badge--accent { background: var(--ww-accent); color: var(--ww-surface-raised); }

/* --- Tooltip: [data-tip] — NUNCA usar title= (tooltip nativo do sistema). ---
   Primitiva única: qualquer elemento com data-tip="texto" ganha a bolha no
   hover e no foco de teclado, com a MESMA geometria do painel (stroke preto,
   sombra chapada). Puro CSS via ::after — sem JS, sem nó extra; trocar o
   texto em runtime é trocar o atributo. Delay curto no hover pra não piscar
   bolha em passagem de mouse. Sobe acima de tudo que é UI (o balão não pode
   nascer atrás do painel vizinho). */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(2px);
  font-family: var(--ww-font-body);
  font-size: 11px;
  font-weight: 800;
  line-height: 1.3;
  white-space: nowrap;
  padding: 4px 10px;
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 4px);
  background: var(--ww-surface-raised);
  color: var(--ww-text-primary);
  box-shadow: 0 2px 0 var(--ww-line);
  opacity: 0;
  pointer-events: none;
  z-index: 2147483300;
  transition: opacity 0.12s ease, transform 0.12s ease;
  transition-delay: 0s;
}
[data-tip]:hover::after,
[data-tip]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  transition-delay: 0.35s;
}
/* Variante pra quem vive na borda de cima da tela (a bolha desceria pra fora):
   abre pra BAIXO. */
[data-tip][data-tip-below]::after {
  bottom: auto;
  top: calc(100% + 8px);
  transform: translateX(-50%) translateY(-2px);
}
[data-tip][data-tip-below]:hover::after,
[data-tip][data-tip-below]:focus-visible::after {
  transform: translateX(-50%) translateY(0);
}
/* Variante pra quem vive na borda ESQUERDA (founder, 2026-08-18: o botão de
   voz no canto cortava o texto): a bolha alinha o lado esquerdo com o do
   dono, e cresce pra dentro da tela. A irmã da direita, pelo mesmo motivo. */
[data-tip][data-tip-start]::after {
  left: 0;
  transform: translateX(0) translateY(2px);
}
[data-tip][data-tip-start]:hover::after,
[data-tip][data-tip-start]:focus-visible::after {
  transform: translateX(0) translateY(0);
}
[data-tip][data-tip-end]::after {
  left: auto;
  right: 0;
  transform: translateX(0) translateY(2px);
}
[data-tip][data-tip-end]:hover::after,
[data-tip][data-tip-end]:focus-visible::after {
  transform: translateX(0) translateY(0);
}

/* --- Divisor horizontal com o stroke preto. --- */
.ww-divider {
  border: none;
  border-top: var(--ww-line-width) solid var(--ww-line);
  margin: 0;
}

/* --- Select custom — NUNCA usar <select> nativo. Trigger é .ww-button. --- */
.ww-select {
  position: relative;
  display: inline-flex;
  flex: 1;
}
.ww-select__trigger {
  flex: 1;
  justify-content: space-between;
  gap: 8px;
  font-weight: 700;
}
.ww-select__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-select__caret {
  font-size: 10px;
  opacity: 0.7;
  transition: transform 0.15s ease;
}
.ww-select.is-open .ww-select__caret { transform: rotate(180deg); }
.ww-select__menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-height: 240px;
  padding: 4px;
  display: none;
  flex-direction: column;
  gap: 2px;
  z-index: 60;
}
.ww-select.is-open .ww-select__menu { display: flex; }
.ww-select__item {
  background: transparent;
  border: none;
  color: var(--ww-text-primary);
  font-family: var(--ww-font-body);
  font-size: 13px;
  font-weight: 700;
  text-align: left;
  padding: 6px 10px;
  cursor: pointer;
  border-radius: calc(var(--ww-radius) - 4px);
}
.ww-select__item:hover { background: var(--ww-surface-sunken); }
.ww-select__item.is-active {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}

/* --- Scrollbar custom (regra global: zero scrollbar do sistema). --- */
.ww-scroll { overflow: auto; scrollbar-gutter: stable; }
.ww-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.ww-scroll::-webkit-scrollbar-track {
  background: var(--ww-surface-sunken);
  border-radius: var(--ww-radius);
}
.ww-scroll::-webkit-scrollbar-thumb {
  background: var(--ww-line);
  border-radius: var(--ww-radius);
  border: 2px solid var(--ww-surface-sunken);
}


/* ═══════════════════════════════════════════════════════════════════════
   Atalho legado: .ww-btn ainda é referenciado em blocos antigos.
   Aponta pra .ww-button. Remover depois da migração total.
═══════════════════════════════════════════════════════════════════════ */
.ww-btn {
  font-family: var(--ww-font-body);
  font-size: 14px;
  font-weight: 800;
  color: var(--ww-text-primary);
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 8px 16px;
  cursor: pointer;
  transition: transform 0.08s ease, box-shadow 0.08s ease, background-color 0.1s linear, color 0.1s linear;
}
.ww-btn:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 0 6px 0 var(--ww-line); }
.ww-btn:active:not(:disabled) { transform: translate(0, 2px); box-shadow: 0 2px 0 var(--ww-line); }
.ww-btn.active { background: var(--ww-accent); color: var(--ww-surface-raised); }

/* ---------- Scrollbar custom global ----------
   REGRA: nenhum elemento do projeto pode usar scrollbar padrão do sistema.
   Todo container com overflow deve ter a classe `ww-scroll` OU seu próprio
   ::-webkit-scrollbar definido. Scroll nativo é proibido por padrão visual.
   Veja: memory/feedback_no_system_scroll.md */
.ww-scroll {
  scrollbar-width: thin;                 /* Firefox */
  scrollbar-color: #3a2a1c transparent;  /* Firefox: thumb track */
}
.ww-scroll::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
.ww-scroll::-webkit-scrollbar-track {
  background: transparent;
}
.ww-scroll::-webkit-scrollbar-thumb {
  background: #3a2a1c;
  border-radius: 4px;
}
.ww-scroll::-webkit-scrollbar-thumb:hover {
  background: #5a4228;
}
.ww-scroll::-webkit-scrollbar-corner {
  background: transparent;
}

#card-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  font-family: var(--ww-font-body);
  z-index: 10;
}

#card-overlay__world {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform-origin: 0 0;
  will-change: transform;
}

/*
 * Carta = 2 níveis:
 *   .ww-card        → posição no mundo + sombra fixa no chão (não sobe com o pulo).
 *   .ww-card__inner → visual da carta; recebe o transform do ciclo de passo
 *                     (squash/stretch/pulo) sem afetar a sombra.
 */
.ww-card {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: center center;
  user-select: none;
  will-change: transform;
}
.ww-card::after {
  /* Sem sombra radial no piso — o preset flash não usa sombra difusa.
     A sombra da carta vem do box-shadow chapado no __inner. Mantemos
     o ::after como pseudo vazio pra não quebrar regras de step/drag. */
  content: '';
  position: absolute;
  left: 8%;
  right: 8%;
  bottom: -6px;
  height: 10px;
  background: transparent;
  z-index: -1;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.ww-card__inner {
  position: absolute;
  inset: 0;
  border-radius: var(--ww-card-radius);
  /* Sombra chapada preta pra baixo (look flash-era). */
  box-shadow: 0 4px 0 var(--ww-line);
  /* ── OS TEMAS DE VISÃO (founder, 2026-08-18) ──────────────────────────
     Cada detalhe da pintura lê um token `--a11y-card-*` com o VALOR ATUAL
     de default: sem tema, nenhum pixel muda; com tema (core/
     vision-themes.js via a11y-prefs), o CSS inteiro acompanha sem o
     renderer saber. O contrato completo é a lista VISION_TOKENS, cobrada
     por `scripts/f20-vision-tokens.mjs` (token sem consumidor aqui =
     controle morto = build falha). */
  border: calc(var(--ww-line-width) * var(--a11y-card-border-w, 1)) solid
          var(--a11y-card-border, var(--ww-line));
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 8px 6px;
  box-sizing: border-box;
  color: var(--a11y-card-ink, var(--ww-text-primary));
  overflow: hidden;
  transform-origin: center bottom;
  will-change: transform;
  transition: box-shadow 0.12s ease, background 0.2s ease, border-color 0.12s ease;
  /* Cor da carta vem por --ww-card-color inline (CardDOMRenderer). Fundo
     default é chapado; a variante `.ww-card__inner--battery` sobrescreve
     com gradient que visualiza bateria. O tema de visão vence os dois; o
     padrão de fundo (`--a11y-card-pattern`) é o canal reservado pro
     grafismo por categoria — hoje `none`, esperando a arte. */
  background: var(--a11y-card-bg, var(--ww-card-color, #888));
  background-image: var(--a11y-card-pattern, none);
}

/* Cartas com bateria: o fundo VIRA o indicador de energia.
   - pct = 1  → carta totalmente colorida (cor do tipo).
   - pct = 0  → carta totalmente cinza "descarregada".
   - parcial → cinza em cima, cor embaixo, com corte duro (não blend) em
     pct*100% de baixo. Parece uma barra vertical ocupando a carta inteira.
   --ww-battery-pct é setada inline pelo renderer. --ww-card-empty é a
     cor "desligada" — cinza médio, neutro. */
.ww-card__inner--battery {
  --ww-card-empty: #9a958c;
  background: linear-gradient(
    to top,
    var(--ww-card-color, #888) 0%,
    var(--ww-card-color, #888) calc(var(--ww-battery-pct, 1) * 100%),
    var(--ww-card-empty) calc(var(--ww-battery-pct, 1) * 100%),
    var(--ww-card-empty) 100%
  );
}

/* Aura decorativa pra cartas imóveis. Sempre presente no DOM (como irmã
   do __inner), mas só pinta quando a carta tem .ww-card--immovable. Mesma
   forma da carta + margem extra em volta, preenchida com a cor da carta
   em opacidade baixa. A tag "immovable" fica no topo, colada na borda
   superior da aura. Zero pointer-events — nunca atrapalha click/drag. */
.ww-card__aura {
  display: none;
  position: absolute;
  inset: calc(-1 * var(--ww-aura-inset, 10px));
  border-radius: calc(var(--ww-card-radius) + var(--ww-aura-inset, 10px));
  background: var(--ww-card-color, #888);
  opacity: 0.35;
  pointer-events: none;
  z-index: -1;
}
.ww-card--immovable .ww-card__aura {
  display: block;
}
/* Tags da aura — badges pequenos contornados, empilhados lado a lado
   logo acima da carta (fora do bounds visual dela, dentro da faixa que
   a aura criou com inset negativo). Convenção extensível: no futuro
   outras tags (ex: "static", "fragile") entram como irmãs e se alinham
   horizontalmente via flex. Sempre pointer-events:none. */
.ww-card__aura-tags {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  gap: 4px;
  pointer-events: none;
}
.ww-card__aura-label {
  font-family: var(--ww-font-body);
  font-size: calc(7px * var(--a11y-card-text, 1));
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--ww-text-primary);
  background: var(--ww-surface-raised);
  border: 1.5px solid var(--ww-line);
  border-radius: 3px;
  padding: 1px 5px;
  white-space: nowrap;
  line-height: 1.2;
}

/* Blur de cartas imóveis — FIX real.
   Causa: o pai `#card-overlay__world` leva `scale(zoom)` fracionário. Cartas
   "vivas" tinham `animation` no inner (ww-idle-breath) que força camada GPU e
   re-rasterização a cada frame, ficando nítidas. Cartas imóveis não tinham
   nada no inner — o browser rasterizava o conteúdo no tamanho natural e
   ESCALAVA O BITMAP, deixando texto/sprite borrados.
   Fix: aplicamos uma animation permanente e invisível (scale 1→1) só pra
   forçar camada GPU. Em cartas alive o `:not(.ww-card--alive)` desabilita
   esta (pra não competir com idle-breath). */
.ww-card:not(.ww-card--alive) .ww-card__inner {
  animation: ww-card-static-layer 10s linear infinite;
}
@keyframes ww-card-static-layer {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.0001); }
}

.ww-card.dragging .ww-card__inner {
  /* Drag: sombra chapada fica mais pronunciada pra dar lift visual. */
  box-shadow: 0 10px 0 var(--ww-line);
}
.ww-card.dragging::after {
  opacity: 0;
}

/* ── HOVER: qual carta o clique vai pegar ────────────────────────────────
   O VISUAL-MANUAL pede estado de hover em todo elemento interativo, e na
   mesa isso deixou de ser cortesia quando a pilha virou leque: as cartas se
   cobrem quase inteiras e de cada uma só aparece uma faixa. A classe é posta
   pelo CardDOMRenderer na carta que o MESMO hit-test do clique devolveria,
   então a faixa que acende é exatamente a área que o clique pega — o
   destaque não promete uma carta e entrega outra.

   Composição: borda em accent + anel chapado em volta + véu claro por cima
   da cor da carta. Sem `filter` (regra de ouro 6) e sem keyframe: são duas
   propriedades trocadas numa transição de 120ms, então passar o mouse por
   uma mesa cheia não repinta nada além da carta que entra e da que sai. */
.ww-card--hover .ww-card__inner {
  border-color: var(--ww-accent);
  box-shadow: 0 4px 0 var(--ww-line), 0 0 0 3px var(--ww-accent);
}
/* O "brilha um pouco". Véu branco CHAPADO (nunca gradient — regra 3) num
   pseudo, pra não mexer no `background` do inner, que já é o indicador de
   bateria em algumas cartas. As 222 cores de carta do repo vão de quase
   branco a quase preto: 18% de branco clareia todas sem apagar nenhuma. */
.ww-card--hover .ww-card__inner::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgba(255, 255, 255, 0.18);
  pointer-events: none;
  z-index: 2;
}
/* A carta na mão já tem o próprio destaque (sombra de lift). Somar o hover
   deixaria a mesa com dois realces disputando a atenção. */
.ww-card.dragging.ww-card--hover .ww-card__inner {
  box-shadow: 0 10px 0 var(--ww-line);
}

/* ── CONTORNO DE POSSIBILIDADE (T23) ─────────────────────────────────────
   Enquanto o jogador carrega uma carta, tudo na mesa que formaria receita com
   ela ganha este anel. Quem escolhe as cartas é `fx/RecipeHintOutline.js`
   perguntando ao motor; aqui mora só o desenho.

   TRÊS DECISÕES, e cada uma responde a um pedido explícito:

   SUTIL, E POR SUBTRAÇÃO. O hover tem borda em accent, anel de 3px e véu de
   18%. Este tem anel de 2px e VÉU NENHUM. A diferença não é de tom, é de
   quantidade de sinal: o hover diz "o clique pega esta", que é uma promessa
   sobre o próximo gesto; o contorno diz "aqui caberia", que é informação de
   fundo. Duas coisas de peso diferente têm que pesar diferente na tela.

   HIERARQUIA DECLARADA NO SELETOR. O `:not(.ww-card--hover)` não é economia
   de pintura: é a regra "hover ganha" escrita onde ela se lê, em vez de
   deixada por conta da ordem das declarações no arquivo. A carta sob o
   ponteiro mostra o realce do ponteiro, e só ele.

   COLORBLIND POR LUMINÂNCIA, sem depender de matiz. O anel é um par
   claro-sobre-escuro: 2px de branco a 70% com 1px de preto por fora. Sobre
   carta clara quem separa é o traço escuro; sobre carta escura, o claro. As
   222 cores de carta do repo vão de quase branco a quase preto, e nenhuma
   apaga os dois ao mesmo tempo. Nenhum matiz participa da leitura — o anel
   funciona igual em qualquer daltonismo e em preto e branco.

   E ele NÃO mexe em `border-color`: a borda tracejada cinza da carta fixa
   (T19) continua inteira por baixo do anel, sem disputa. Também não anima e
   não transiciona — carta empilhada precisa poder acender (receita acontece em
   baralho desde o T20), e a lei da inércia do T22 desliga animação e transição
   em pilha. Um anel estático atravessa a lei sem furá-la. */
.ww-card--recipe-hint:not(.ww-card--hover) .ww-card__inner {
  box-shadow:
    0 4px 0 var(--ww-line),
    0 0 0 2px rgba(255, 255, 255, 0.70),
    0 0 0 3px rgba(0, 0, 0, 0.55);
}
/* A carta na mão já é o objeto do gesto — ela não é alvo de si mesma, e o
   motor nem a inclui no conjunto. A regra existe pela mesma razão que o
   `:not(--hover)`: deixar a hierarquia escrita em vez de confiada. */
.ww-card.dragging.ww-card--recipe-hint .ww-card__inner {
  box-shadow: 0 10px 0 var(--ww-line);
}

/* ── O ANEL DE FOCO DO TECLADO (T36) ──────────────────────────────────────
   Onde as setas estão. Segue o molde do contorno de possibilidade logo acima
   e pela mesma razão: par claro-sobre-escuro, ZERO matiz, sem animação e sem
   transição. Funciona igual em qualquer daltonismo, em preto e branco, e
   atravessa a lei de inércia do T22 — carta empilhada pode receber foco, e um
   anel estático não é animação de corpo.

   É mais GROSSO que o do recipe-hint (3px de claro contra 2px), e tem que ser:
   os dois podem estar na tela ao mesmo tempo, e quem depende deste precisa
   distinguir "onde eu estou" de "onde eu poderia encaixar" sem hesitar. A
   diferença é de espessura, não de cor, porque cor é justamente o que não se
   pode assumir aqui.

   O foco VENCE o hover: quem está navegando de teclado tem a mão longe do
   mouse, e o ponteiro parado sobre uma carta qualquer não pode roubar a
   marca do lugar onde o jogador realmente está. */
.ww-card--kbfocus .ww-card__inner {
  box-shadow:
    0 4px 0 var(--ww-line),
    0 0 0 3px rgba(255, 255, 255, 0.92),
    0 0 0 5px rgba(0, 0, 0, 0.75) !important;
}

/* TODA a pilha se marca quando o foco está num baralho, com a MESMA força da
   carta solta — e isto é um conserto, não um detalhe de gosto.

   A primeira versão marcava só a carta base e dava às vizinhas um anel mais
   fraco. Na tela isso simplesmente não existia: num leque as cartas se cobrem
   quase inteiras, então o anel da carta de baixo fica atrás das de cima, e o
   das vizinhas era fino demais pra aparecer na única faixa visível de cada
   uma. O foco ficava invisível justamente onde o jogador mais precisa dele —
   numa pilha, que é onde as cartas se escondem umas nas outras.

   Agora a pilha inteira acende igual: a unidade de navegação é o baralho, e o
   que se vê na tela passa a ser o que o motor entende. */
.ww-card--kbfocus-deck .ww-card__inner {
  box-shadow:
    0 4px 0 var(--ww-line),
    0 0 0 3px rgba(255, 255, 255, 0.92),
    0 0 0 5px rgba(0, 0, 0, 0.75) !important;
}

/* ── CONTRASTE REFORÇADO (T36) ────────────────────────────────────────────
   O jogo já é legível sem cor nenhuma — a informação mora na geometria das
   faixas (T17), no cinza listrado da morte (T18) e em contraste medido sob
   cada daltonismo. Este ajuste não conserta o que faltava: ele dá MARGEM a
   mais a quem quiser, engrossando o traço que já carregava a leitura.

   Por isso ele mexe em ESPESSURA e OPACIDADE, nunca em matiz: trocar a paleta
   desfaria o trabalho de contraste que já foi medido, em troca de uma cor que
   ninguém mediu. */
.a11y-contrast {
  --ww-line-width: 4px;
}
.a11y-contrast .ww-card__inner {
  border-width: 4px;
}
.a11y-contrast .ww-card--kbfocus .ww-card__inner {
  box-shadow:
    0 4px 0 var(--ww-line),
    0 0 0 4px rgba(255, 255, 255, 1),
    0 0 0 7px rgba(0, 0, 0, 0.9) !important;
}
.a11y-contrast .ww-card--recipe-hint:not(.ww-card--hover) .ww-card__inner {
  box-shadow:
    0 4px 0 var(--ww-line),
    0 0 0 3px rgba(255, 255, 255, 0.92),
    0 0 0 5px rgba(0, 0, 0, 0.8);
}
/* O texto da carta e dos painéis ganha peso, que é o ganho de legibilidade
   mais barato que existe e o único que não custa espaço. */
.a11y-contrast .ww-card__title,
.a11y-contrast .ww-label,
.a11y-contrast .ww-body {
  font-weight: 800;
}

/* ── PADRÕES NO LUGAR DE COR (founder, 2026-08-18) ────────────────────────
   "A gente deveria poder trocar cores por padrões visuais — listras,
   bolinhas, traços." Cada TIPO VISUAL de carta (o mesmo índice do glifo,
   `data-viztype` escrito pelo CardDOMRenderer) ganha um desenho de fundo
   próprio quando `html.a11y-patterns` está ligada (pref `cardPatterns`).

   As leis do bloco, cobradas por `scripts/f20-vision-tokens.mjs`:
   - NENHUMA DIAGONAL: 45° é o vocabulário de ESTADO (morte lista a -45°,
     esgotado hachura a 45°) — categoria usando a mesma família viraria o
     "sangue vs urucum" de novo, em escala.
   - Cada padrão tem tinta ESCURA E CLARA alternadas: legível sobre qualquer
     luminância de fundo, inclusive nos temas escuro/claro juntos.
   - `person` fica SÓLIDA de propósito: é a categoria mais comum da mesa, e
     o sólido é uma das quatro famílias distinguíveis (parecer Aurora) —
     fundo limpo É a marca dela.
   - Famílias: vertical (place largo, element fino), horizontal (item),
     bolinhas (food graúda, creature miúda), xadrez (threat — o perigo leva
     o desenho mais chamativo da mesa). */
html.a11y-patterns .ww-card[data-viztype="place"] .ww-card__inner {
  background-image: repeating-linear-gradient(90deg,
    rgba(0, 0, 0, 0.10) 0 4px, rgba(255, 255, 255, 0.12) 4px 8px, transparent 8px 16px);
}
html.a11y-patterns .ww-card[data-viztype="element"] .ww-card__inner {
  background-image: repeating-linear-gradient(90deg,
    rgba(0, 0, 0, 0.12) 0 2px, rgba(255, 255, 255, 0.12) 2px 4px, transparent 4px 8px);
}
html.a11y-patterns .ww-card[data-viztype="item"] .ww-card__inner {
  background-image: repeating-linear-gradient(0deg,
    rgba(0, 0, 0, 0.10) 0 4px, rgba(255, 255, 255, 0.12) 4px 8px, transparent 8px 16px);
}
html.a11y-patterns .ww-card[data-viztype="food"] .ww-card__inner {
  background-image: radial-gradient(circle,
    rgba(0, 0, 0, 0.12) 0 3px, rgba(255, 255, 255, 0.14) 3px 4.5px, transparent 4.5px);
  background-size: 14px 14px;
}
html.a11y-patterns .ww-card[data-viztype="creature"] .ww-card__inner {
  background-image: radial-gradient(circle,
    rgba(0, 0, 0, 0.12) 0 1.5px, rgba(255, 255, 255, 0.14) 1.5px 2.5px, transparent 2.5px);
  background-size: 8px 8px;
}
html.a11y-patterns .ww-card[data-viztype="threat"] .ww-card__inner {
  background-image: repeating-conic-gradient(
    rgba(0, 0, 0, 0.12) 0% 25%, rgba(255, 255, 255, 0.10) 25% 50%);
  background-size: 12px 12px;
}
/* Os tipos do pack farm (`?pack=farm`): mesma lei, famílias já usadas com
   outra densidade — machine é horizontal fino (família do item), plant é
   bolinha média (família da comida/bicho). */
html.a11y-patterns .ww-card[data-viztype="machine"] .ww-card__inner {
  background-image: repeating-linear-gradient(0deg,
    rgba(0, 0, 0, 0.12) 0 2px, rgba(255, 255, 255, 0.12) 2px 4px, transparent 4px 8px);
}
html.a11y-patterns .ww-card[data-viztype="plant"] .ww-card__inner {
  background-image: radial-gradient(circle,
    rgba(0, 0, 0, 0.12) 0 2.2px, rgba(255, 255, 255, 0.14) 2.2px 3.4px, transparent 3.4px);
  background-size: 11px 11px;
}

/* ── A COR INDEXADA (founder, 2026-08-18) ─────────────────────────────────
   Com os padrões ligados, a cor da carta passa a SIGNIFICAR: mesma
   categoria, mesma cor — padrão e cor carregando a mesma informação por
   dois canais. Os valores vêm de `CARD_INDEX_COLORS` (vision-themes.js) via
   tokens `--a11y-index-*` que o a11y-prefs escreve no <html>; aqui é só
   encanamento. O `!important` é necessário e deliberado: `--ww-card-color`
   nasce INLINE no elemento (CardDOMRenderer), e só uma declaração important
   vence inline — sem ela a indexação seria um controle morto.
   Sobrescrever `--ww-card-color` (e não só o background) é o que leva a cor
   indexada também ao anel de vocação e à aura, que leem a mesma variável.
   Nota de coexistência: o tema de cores (escuras/claras) vence a cor
   indexada no FUNDO (--a11y-card-bg tem precedência na cadeia do
   background) — com os dois ligados, o padrão carrega a categoria sozinho,
   que é o desenho certo. */
html.a11y-patterns .ww-card[data-viztype="threat"]   { --ww-card-color: var(--a11y-index-threat) !important; }
html.a11y-patterns .ww-card[data-viztype="machine"]  { --ww-card-color: var(--a11y-index-machine) !important; }
html.a11y-patterns .ww-card[data-viztype="place"]    { --ww-card-color: var(--a11y-index-place) !important; }
html.a11y-patterns .ww-card[data-viztype="creature"] { --ww-card-color: var(--a11y-index-creature) !important; }
html.a11y-patterns .ww-card[data-viztype="plant"]    { --ww-card-color: var(--a11y-index-plant) !important; }
html.a11y-patterns .ww-card[data-viztype="person"]   { --ww-card-color: var(--a11y-index-person) !important; }
html.a11y-patterns .ww-card[data-viztype="item"]     { --ww-card-color: var(--a11y-index-item) !important; }
html.a11y-patterns .ww-card[data-viztype="food"]     { --ww-card-color: var(--a11y-index-food) !important; }
html.a11y-patterns .ww-card[data-viztype="element"]  { --ww-card-color: var(--a11y-index-element) !important; }

/* ── HOVER DE CARTA FIXA: o realce que diz "isto não vem" ─────────────────
   A aldeia, a roça, a loja respondem ao ponteiro — elas SÃO alvo, o clique
   nelas faz coisa — mas responder em laranja seria mentir: o laranja é a cor
   do que vem pra mão, e uma carta plantada não vem. Então o mesmo desenho
   (borda + anel + véu) trocado pro cinza neutro, que já é a família da borda
   tracejada que essas cartas usam desde sempre.

   O véu também recua (18% → 10%): acender menos é parte do recado. O que se
   mantém idêntico é a REGRA — quem acende é a carta que o clique pegaria,
   nunca a vizinha. */
.ww-card--immovable.ww-card--hover .ww-card__inner {
  border-color: var(--ww-neutral);
  box-shadow: 0 4px 0 var(--ww-line), 0 0 0 3px var(--ww-neutral);
}
/* …menos quando o teclado está com o foco nela. Esta regra vem depois e com a
   mesma especificidade de propósito: o realce de hover de carta fixa é cinza,
   e cinza sobre cinza é exatamente o que fazia o anel de foco sumir na aldeia
   e na clareira. Quem navega de teclado tem a mão longe do mouse — o ponteiro
   parado sobre uma carta não pode apagar a marca de onde o jogador está. */
.ww-card--immovable.ww-card--hover.ww-card--kbfocus .ww-card__inner {
  box-shadow:
    0 4px 0 var(--ww-line),
    0 0 0 3px rgba(255, 255, 255, 0.92),
    0 0 0 5px rgba(0, 0, 0, 0.75) !important;
}
.ww-card--immovable.ww-card--hover .ww-card__inner::before {
  background: rgba(255, 255, 255, 0.10);
}

.ww-card--immovable .ww-card__inner {
  /* Borda dashed (não sólida) sinaliza "imóvel". Mantém largura do preset. */
  border: var(--ww-line-width) dashed var(--ww-line);
  cursor: pointer;
}
.ww-card--clickable { cursor: pointer; }

/* Unplaced = ainda não colocada (comprada). Diferenciação via cor accent (em vez
   de pulse de shadow animado — antipadrão). Pulso sutil de scale composited. */
.ww-card--unplaced .ww-card__inner {
  border-color: var(--ww-accent);
  animation: ww-unplaced-pulse 1.2s ease-in-out infinite;
}
@keyframes ww-unplaced-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}

/* ── O SALTO ──────────────────────────────────────────────────────────────
   Uma carta que anda pula, e o pulo é o que a faz andar: a escala vem do
   motor (`core/hop.js`), frame a frame, pela MESMA curva que decide quanto
   ela avançou. Por isso aqui não há keyframe nenhum — teríamos dois relógios
   independentes, e a carta cresceria fora do ritmo do próprio passo.

   O que o CSS faz é abrir espaço: enquanto o salto está em curso, nenhuma
   animação de CSS pode tocar o inner. Animação vence declaração inline na
   cascata, então a respiração de idle ou a camada estática engoliriam o
   transform do salto sem deixar rastro.

   `transform-origin: center bottom` (herdado do `.ww-card__inner`) mantém o
   pé da carta ancorado: ela cresce pra cima ao subir e se amassa contra o
   chão ao pousar. A sombra chapada do preset escala junto com a carta — é
   assim que a profundidade se lê num visual sem sombra difusa: quem está no
   ar está maior e mais destacado da mesa. */
.ww-card--stepping .ww-card__inner {
  animation: none;
}

/* ── A LEI DA INÉRCIA, LADO DECLARATIVO (T22) ────────────────────────────────

   Carta empilhada não anima. Uma regra, e ela vale sobre TODAS as animações de
   corpo de uma vez: respiração de idle, pulsar de carta por plantar, thud de
   pouso, soco de receita, tremor de recusa, camada estática — as declaradas
   hoje e as que alguém declarar amanhã sem ler este arquivo.

   É o lado CSS da lei da inércia (`core/dying-phase.js`). O lado JS
   (`render/card-body.js`) pega as
   animações imperativas, esta regra pega as declarativas, e as duas dizem
   exatamente a mesma coisa de propósito: uma porta que se pode contornar não é
   uma porta. `scripts/t22-inertia-gate.mjs` falha se esta regra sumir.

   `!important` porque é lei, e TRÊS classes no seletor pelo mesmo motivo: as
   animações que ela desliga têm duas, e uma delas (o chacoalhar da carta
   recém-comprada) já vinha com `!important` próprio. Em empate de
   especificidade quem vence é quem está mais embaixo no arquivo — e a lei tem
   que valer sobre o que veio antes E sobre o que vier depois, então ela ganha
   por especificidade e não por posição. Medido: sem a terceira classe, a carta
   comprada chacoalhava dentro do leque.

   O que NÃO está aqui, e por quê: `transform`. Até o T22 era porque o corpo
   carregava a escala congelada do apodrecimento e zerá-la aqui devolveria a
   carta podre ao tamanho cheio ao ser empilhada. Desde o T23 não há escala de
   estado nenhuma, e o motivo virou outro: o resíduo do salto. Uma carta que
   entra na pilha no meio de um passo pode ter um `transform` inline do T14, e
   quem o limpa é a porta (`setBodyTransform` escreve string vazia em carta
   empilhada) — não uma regra de CSS que teria de vencer o inline. */
.ww-card.ww-card--in-deck .ww-card__inner {
  animation: none !important;
  transition: none !important;
}

/* Respiração sutil pra cartas "vivas" que não estão andando e não são estáticas.
   Idle sai de cena assim que o step-cycle entra (step override via cascade).
   Cartas em deck não respiram — a pilha inteira ficava oscilando fora de
   fase, quebrando a leitura de "é um conjunto agora". O `:not(--in-deck)` é
   redundante desde a regra acima e fica como documentação da intenção. */
.ww-card--alive:not(.ww-card--stepping):not(.dragging):not(.ww-card--in-deck) .ww-card__inner {
  animation: ww-idle-breath 1.8s ease-in-out infinite;
}
@keyframes ww-idle-breath {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.015); }
}

/* ── AQUI MORAVA O ENCOLHIMENTO DO APODRECIMENTO (removido no T23) ────────
   `.ww-card--decaying .ww-card__inner { transform: scale(var(--decay-scale)) }`
   fazia a carta minguar de 1,00 até 0,55 conforme a validade escorria, e essa
   única declaração puxou atrás de si três tópicos de conserto: a briga de
   cascata com a respiração de idle (T21), o congelamento na pilha e o acerto
   em tween ao sair (T22).

   O founder vetou o comportamento em si: neste jogo carta não muda de tamanho.
   Então a regra saiu inteira, e com ela a variável `--decay-scale`, que não é
   mais escrita por ninguém. O apodrecimento continua legível pelo cinza
   listrado (T18) e pelos indicadores de validade — dois canais que não mexem
   na geometria da carta e, portanto, não brigam com a cascata de ninguém.

   Este comentário fica no lugar da regra de propósito: quem for procurar por
   que a carta não encolhe mais acha a resposta onde o comportamento morava. */

/* ── MORTE: O CINZA LISTRADO (T18) ───────────────────────────────────────
   A carta que está acabando não desbota — ela perde a cor e ganha listras. O
   fade que existia aqui (opacity caindo com o decay) saiu: carta translúcida
   contradiz a regra-raiz do juice deste jogo, que é "cartas são objetos
   físicos numa mesa". Objeto não fica transparente; apodrece.

   Aqui mora só a GEOMETRIA. O véu cinza e a densidade das faixas são inline,
   escritos pelo `CardDOMRenderer._syncDying` em seis degraus — que é o lugar
   único desses números, e o comentário de lá explica a distinção entre este
   estado e o pisca colorido da reação (T17).

   `z-index: 3` põe o estado acima do conteúdo e do véu de hover (2), e ABAIXO
   do pisca da reação (4). A ordem importa: uma carta moribunda que entra numa
   reação precisa piscar por cima do próprio definhamento, senão o jogador
   deixa de ver que ela ainda está em jogo.

   Zero animação, zero transição: é estado, e estado parado é o que se lê. */
.ww-card__dying {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}

/* ═══════════════════════════════════════════════════════════════════════
   ASPECTOS DE ESTADO (T30) — `.ww-card--look-<nome>`

   Um alfabeto curto de "como a carta FICA" enquanto um estado durar. Quem pede
   é o status effect, pelo campo `look`; quem pinta é aqui. O renderer só
   traduz o nome numa classe — não conhece esgotamento, nem terra, nem cor.

   ── drained: sem o que dar ────────────────────────────────────────────────

   Preto e branco chapado. É a leitura do esgotamento pedida pelo founder, e é
   a leitura certa: um lugar esgotado não está estragado nem doente, está SEM —
   e a cor é justamente o que ele deixou de ter.

   Distinto da PODRIDÃO por construção, e a distinção é a regra deste bloco:

     podridão (T18)          esgotamento (T30)
     cinza LISTRADO          cinza LISO
     véu por cima            filtro no conteúdo
     progressivo             liga e desliga
     "isto vai morrer"       "isto volta amanhã"

   Se o esgotamento também listrasse, duas notícias opostas chegariam com a
   mesma cara — e a segunda é o convite pra sair de cima da terra, que é a
   única decisão de longo prazo que o jogador toma sobre território.

   ── Por que o filtro NÃO vai no `__inner` inteiro ────────────────────────

   Porque o pisca de reação (T17) mora lá dentro e a identidade dele É a cor:
   um `filter` no pai apagaria a assinatura da receita justamente na carta
   sobre a qual ela está acontecendo. Então o filtro vai nos FILHOS de
   conteúdo, e a camada de reação fica de fora — mesma razão pela qual o véu do
   apodrecimento é uma cor chapada por cima em vez de um `filter`.

   O fundo do `__inner` não é filtrado pelo mesmo motivo — filtro no `__inner`
   pega os filhos junto. Ele é trocado por um cinza direto, o que também sai
   mais barato: é composição, não repaint de camada.

   (E a troca é do `background`, não da variável `--ww-card-color`: o renderer
   escreve essa variável INLINE em cada carta, e estilo inline ganha de regra
   de classe. Uma sobrescrita da variável aqui simplesmente não teria efeito.)

   Tamanho não muda, opacidade não muda, nada anima: lei T23 e
   MANUAL-DE-INTERFACE. Estado se lê parado.
═══════════════════════════════════════════════════════════════════════ */
.ww-card--look-drained .ww-card__inner,
.ww-card--look-drained .ww-card__inner--battery {
  background: #8e8b85;
  /* ── O HACHURADO DA ACESSIBILIDADE (founder, 2026-08-06) ───────────────
     Cinza é COR, e quem não distingue cores não lê "esta carta está
     esgotada" só pela dessaturação — sobretudo em acromatopsia, onde a mesa
     inteira já é cinza. A textura resolve porque não depende de matiz nenhum:
     é padrão contra liso, e isso se vê em qualquer visão.

     Diagonais de 45°, 6px de passo, pretas a 7%: forte o bastante pra
     aparecer no zoom out, fraca o bastante pra não competir com o nome nem
     com a arte. É reforço do estado, não decoração.

     `repeating-linear-gradient` sobre a cor chapada, na mesma declaração:
     um pseudo-elemento precisaria de z-index e brigaria com o selo e com o
     véu de hover, que já disputam esse espaço. */
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, 0.07) 0,
    rgba(0, 0, 0, 0.07) 1.5px,
    transparent 1.5px,
    transparent 6px
  );
}
.ww-card--look-drained .ww-card__inner > *:not(.ww-card__reaction):not(.ww-card__badges) {
  filter: grayscale(1);
}
/* Os SELOS ficam de fora, e isto é a metade que faltava do pedido: o 🍂
   continua, "isso é reforço, não substituição". Um selo dessaturado sobre um
   corpo dessaturado é uma folha cinza em cima de cinza — some justamente na
   carta em que ele mais precisa ser lido. A dessaturação é da IDENTIDADE da
   carta (o que ela é); os selos são o que está ACONTECENDO com ela, e o que
   acontece continua colorido. */
.ww-card--look-drained .ww-card__aura {
  filter: grayscale(1);
}

/* ── O BALÃO DE MUDANÇA DE PROPRIEDADE (T18) ─────────────────────────────
   Sobe da carta quando alguma coisa mexe num atributo dela: ícone do atributo
   + número com sinal. Dois níveis, e cada um com uma tarefa:

     `.ww-prop-floaty`       — só posição no mundo. Vive dentro de
                               `#card-overlay__world`, então acompanha pan e
                               zoom pela mesma matriz das cartas.
     `.ww-prop-floaty__box`  — só animação. Separar é o que permite ao balão
                               ter transform de entrada sem que ele apague o
                               translate que o coloca sobre a carta.

   A entrada é ELÁSTICA de verdade — três overshoots decrescentes, e não um
   `back.out` de uma pancada só. A diferença importa porque o balão é pequeno
   e rápido: com uma oscilação só ele lê como "apareceu", com três ele lê como
   "saltou da carta", que é o que dá a sensação de que a carta reagiu.

   Só `transform` e `opacity` na curva — regra de ouro 6 do VISUAL-MANUAL. */
.ww-prop-floaty {
  position: absolute;
  left: 0;
  top: 0;
  /* O TAMANHO DO BALÃO mora aqui, e tudo abaixo é medido em `em` (T20).
     A ocasião troca esta única linha e a pastilha inteira acompanha — texto,
     ícone, respiro e moldura. Antes o número era 12px fixo e o `font-size` da
     ocasião não chegava nele: o balão da virada de dia ficava do mesmo tamanho
     do da receita, e a diferença de escala que o founder pediu não existia. */
  font-size: 12px;
  /* O pai (`#card-overlay__world`) tem largura zero — é só um ponto de origem
     pra matriz da câmera. Sem `max-content` o balão herdaria essa largura e
     cada pastilha quebraria numa linha por caractere. */
  width: max-content;
  /* O z que decide MORA NA CAMADA (`#card-floaty-layer`), não aqui.
     Este número ordena os balões ENTRE SI dentro dela, e é só isso que ele
     pode fazer — foi confundir as duas coisas que criou o bug de 2026-08-06:
     enquanto o balão vivia dentro do mundo das cartas, este `z-index` parecia
     ser a regra geral e era apenas a regra local de um contexto preso. */
  z-index: 1;
  pointer-events: none;
  will-change: transform;
}

/* ── O CONSERTO DO SOLUÇO (T20) ──────────────────────────────────────────
   A versão do T18 punha `translateY` e `scale` nos MESMOS keyframes, com um
   `cubic-bezier` aplicado a cada segmento. Cada overshoot da escala arrastava
   o deslocamento vertical junto: o balão subia 6px, quase parava, subia mais
   2, parava de novo. Na tela isso lê como soluço, e não como salto.

   O conserto é estrutural, não de números. Dois elementos, uma animação cada:

     `__box`  SUBIDA — `translateY` + `opacity`, monotônico do início ao fim.
     `__pop`  ELASTICIDADE — só `scale`, e `linear` entre keyframes porque a
              curva elástica JÁ ESTÁ nos valores. Somar um easing por segmento
              a keyframes que já descrevem a oscilação é o que dobrava a
              elasticidade e produzia o tranco.

   `--ww-floaty-life` vem do JS e é a duração da ocasião. */
.ww-prop-floaty__box {
  translate: -50% -100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: ww-prop-floaty-rise var(--ww-floaty-life, 1150ms) cubic-bezier(.22, .61, .36, 1) forwards;
}

.ww-prop-floaty__pop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.17em;
  transform-origin: 50% 100%;
  animation: ww-prop-floaty-pop 420ms linear forwards;
}

@keyframes ww-prop-floaty-rise {
  0%   { opacity: 0; transform: translateY(4px); }
  10%  { opacity: 1; transform: translateY(-6px); }
  75%  { opacity: 1; transform: translateY(-24px); }
  100% { opacity: 0; transform: translateY(-34px); }
}

/* Três overshoots decrescentes — 1.22, 1.07, 1.02 —, que é a elasticidade que
   o T18 pediu e nunca chegou a entregar limpa. */
@keyframes ww-prop-floaty-pop {
  0%   { transform: scale(0.55); }
  22%  { transform: scale(1.22); }
  42%  { transform: scale(0.94); }
  60%  { transform: scale(1.07); }
  78%  { transform: scale(0.98); }
  100% { transform: scale(1); }
}

/* ── A VIRADA DE DIA ──────────────────────────────────────────────────────
   Natureza visual DIFERENTE, e a diferença é o movimento: este DESCE de cima
   da carta, assenta e FICA parado. O da receita sobe e vai embora. Um jogador
   que viu os dois uma vez não confunde nunca mais, mesmo sem ler o número —
   e é essa a regra do founder.

   O tamanho e a moldura reforçam; quem carrega é o assentar. */
.ww-prop-floaty.is-day .ww-prop-floaty__box {
  animation-name: ww-prop-floaty-settle;
  animation-timing-function: cubic-bezier(.16, .84, .28, 1);
}

.ww-prop-floaty.is-day {
  font-size: 15.5px;
}

.ww-prop-floaty.is-day .ww-prop-floaty__pop {
  gap: 0.25em;
  animation-duration: 560ms;
}

.ww-prop-floaty.is-day .ww-prop-floaty__row {
  padding: 3px 9px 3px 7px;
  border-width: calc(var(--ww-line-width) + 1px);
  box-shadow: 0 3px 0 var(--ww-line);
}

/* Movimento CONTÍNUO (founder, 2026-08-05): o balão da virada não assenta
   mais — nasce, sobe devagar a vida inteira e faz fadeout ainda subindo.
   "Subir e parar" travava o olho no meio da onda e ninguém lia; o drift
   constante deixa cada balão legível enquanto o stagger atravessa a mesa. */
@keyframes ww-prop-floaty-settle {
  0%   { opacity: 0; transform: translateY(2px); }
  10%  { opacity: 1; transform: translateY(-4px); }
  78%  { opacity: 1; transform: translateY(-22px); }
  100% { opacity: 0; transform: translateY(-32px); }
}

/* O efeito de receita passa: mesma linguagem do padrão, mais curto e um pouco
   menor. O jogador já está olhando pra pilha e a reação da carta (T17) já lhe
   disse que aconteceu alguma coisa. */
.ww-prop-floaty.is-recipe {
  font-size: 11px;
}

.ww-prop-floaty.is-recipe .ww-prop-floaty__pop {
  animation-duration: 340ms;
}

/* Cada linha compõe `.ww-badge` de perto sem herdá-la: mesma pastilha clara,
   mesmo stroke preto, mesmo raio. Não usa a classe direto porque badge é
   inline-block com letra-espaçada de rótulo, e aqui o conteúdo é um par
   ícone+número que precisa de baseline comum. */
.ww-prop-floaty__row {
  display: flex;
  align-items: center;
  gap: 0.25em;
  padding: 0.08em 0.5em 0.08em 0.33em;
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 4px);
  background: var(--ww-surface-raised);
  color: var(--ww-text-primary);
  box-shadow: 0 2px 0 var(--ww-line);
}

/* Estado = troca de cor, nada de chrome extra (VISUAL-MANUAL §1). Notícia ruim
   é a pastilha em accent; boa é a pastilha clara de sempre. E não é SÓ a cor
   que separa as duas: o sinal do número vem antes e diz o mesmo, que é o que
   mantém a leitura de pé sem enxergar vermelho. */
.ww-prop-floaty__row.is-bad {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}

.ww-prop-floaty__icon {
  width: 1.08em;
  height: 1.08em;
  display: block;
  fill: currentColor;
  flex: none;
}

.ww-prop-floaty__n {
  font-family: var(--ww-font-body);
  font-size: 1em;
  font-weight: 900;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* O NOME PEQUENO (T37). Menor e mais leve que o número de propósito: quem já
   sabe o que é fome lê "−12" e segue; quem não sabe encontra a palavra logo em
   seguida, sem que ela dispute a primeira leitura. Mesma cor da linha (herda o
   `currentColor` da pastilha), então a inversão do `is-bad` leva o nome junto e
   não sobra texto escuro sobre accent. */
.ww-prop-floaty__name {
  font-family: var(--ww-font-body);
  font-size: 0.82em;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* "e mais N": o balão não cresce sem fim. Quatro mudanças no mesmo golpe
   mostram as três que mais pesam e contam que houve outras. */
.ww-prop-floaty__more {
  font-family: var(--ww-font-body);
  font-size: 0.75em;
  font-weight: 900;
  color: var(--ww-text-muted);
  line-height: 1;
}

/* ---------- Dev panel ---------- */
/* ═══════════════════════════════════════════════════════════════════════
   Dev panel — layout; pintura pelas primitivas.
   Ferramenta interna (Ctrl+Shift+D), não é vista por jogador final.
═══════════════════════════════════════════════════════════════════════ */

.ww-dev-panel {
  position: fixed;
  top: 10px;
  right: 10px;
  width: 340px;
  max-height: calc(100vh - 20px);
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  color: var(--ww-text-primary);
  font-family: var(--ww-font-body);
  font-size: 12px;
  z-index: 500;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  pointer-events: auto;
}
.ww-dev-panel.hidden { display: none; }

.ww-dev-panel__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 12px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
  background: var(--ww-surface-sunken);
}
.ww-dev-panel__title { margin: 0; }

.ww-dev-panel__active {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 0;
  box-shadow: none;
  border-top: none;
  border-left: none;
  border-right: none;
}
.ww-dev-panel__active-label { flex: 0 0 auto; }
.ww-dev-panel__active-name {
  flex: 1;
  font-weight: 900;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ww-dev-panel__section {
  padding: 10px 12px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
  overflow-y: auto;
}
.ww-dev-panel__section:last-child { border-bottom: none; }

.ww-dev-panel__section-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.ww-dev-panel__inline-btn {
  padding: 4px 10px;
}

.ww-dev-panel__list { display: flex; flex-direction: column; gap: 6px; }
.ww-dev-panel__empty {
  font-style: italic;
  padding: 6px 0;
  font-size: 11px;
}

.ww-dev-panel__item {
  display: flex;
  gap: 8px;
  padding: 8px 10px;
  align-items: center;
}
.ww-dev-panel__item.broken { border-color: var(--ww-accent); }
.ww-dev-panel__item-body { flex: 1; min-width: 0; }
.ww-dev-panel__item-name {
  font-weight: 800;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-dev-panel__item-desc {
  font-size: 11px;
  margin-top: 2px;
}
.ww-dev-panel__item-actions { display: flex; gap: 4px; }

.ww-dev-panel__tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.ww-dev-panel__tag {
  font-size: 10px;
  font-weight: 700;
  color: var(--ww-surface-raised);
  background: var(--ww-text-primary);
  padding: 1px 6px;
  border-radius: 3px;
}

.ww-dev-panel__util-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ww-dev-panel__util-grid label {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ww-dev-panel__util-grid .ww-input {
  width: 100%;
  font-size: 13px;
}

.ww-dev-panel__util-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  margin-top: 8px;
}

.ww-dev-panel__util-spawn {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: var(--ww-line-width) solid var(--ww-line);
}
.ww-dev-panel__util-spawn label {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Badge persistente no HUD indicando modo dev/cenário ativo. */
.ww-dev-badge {
  position: fixed;
  top: 10px;
  left: 10px;
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 2px);
  font-family: var(--ww-font-body);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 1px;
  padding: 4px 10px;
  z-index: 400;
  pointer-events: none;
  box-shadow: var(--ww-shadow);
}
.ww-dev-badge__name {
  font-weight: 700;
  margin-left: 6px;
  opacity: 0.9;
}

/* Telegraph de dropzone — dois níveis:
   - `.ww-card--dropzone`        → empilhamento possível (sempre que há target).
                                   Borda pontilhada sutil estática, sem pulso.
   - `.ww-card--dropzone-recipe` → empilhamento dispara receita. Soma-se ao
                                   estilo base com marching ants dourado
                                   e glow pulsante pra chamar atenção.
*/
/* Dropzone base: sinaliza alvo com marching ants EXTERNOS (via ::after com
   inset:-2px). Não engrossamos o border — isso come 2px do conteúdo em
   box-sizing:border-box e fica pior em cartas imóveis (com sprite grande)
   ou em pilhas. Aqui: zero mudança de layout ao entrar em dropzone. */
.ww-card--dropzone .ww-card__inner::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: calc(var(--ww-card-radius) + 2px);
  background:
    linear-gradient(90deg, var(--ww-line) 50%, transparent 50%) top/8px 2px repeat-x,
    linear-gradient(90deg, var(--ww-line) 50%, transparent 50%) bottom/8px 2px repeat-x,
    linear-gradient(0deg,  var(--ww-line) 50%, transparent 50%) left/2px 8px repeat-y,
    linear-gradient(0deg,  var(--ww-line) 50%, transparent 50%) right/2px 8px repeat-y;
  pointer-events: none;
}

/* Dropzone de receita: marching ants accent + pulse de scale (composited). */
.ww-card--dropzone-recipe .ww-card__inner {
  border-color: var(--ww-accent);
  animation: ww-dropzone-pulse 0.6s ease-in-out infinite;
}
.ww-card--dropzone-recipe .ww-card__inner::after {
  background:
    linear-gradient(90deg, var(--ww-accent) 50%, transparent 50%) top/10px 2px repeat-x,
    linear-gradient(90deg, var(--ww-accent) 50%, transparent 50%) bottom/10px 2px repeat-x,
    linear-gradient(0deg,  var(--ww-accent) 50%, transparent 50%) left/2px 10px repeat-y,
    linear-gradient(0deg,  var(--ww-accent) 50%, transparent 50%) right/2px 10px repeat-y;
  animation: ww-dropzone-march 0.4s linear infinite;
}
@keyframes ww-dropzone-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.03); }
}
@keyframes ww-dropzone-march {
  from { background-position: 0 0, 0 100%, 0 0, 100% 0; }
  to   { background-position: 10px 0, -10px 100%, 0 10px, 100% -10px; }
}

/* Pulse na carta-alvo no momento do snap. Confirma o encaixe. */
.ww-card__inner.ww-card--snap-pulse {
  animation: ww-snap-pulse 150ms ease-out;
}
@keyframes ww-snap-pulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}

/* Barra de progresso de recipe — posicionada sobre a carta do topo da pilha
   via transform em `#recipe-progress-bars`. Cor base verde (mesma do ring
   antigo pra manter continuidade visual). */
/* --- Chances de combate durante o drag (T5).
   O compromisso do edital: o jogador VÊ o risco antes de soltar a carta.
   Compõe .ww-panel (pinta) + .ww-label + .ww-badge (já existentes). Este bloco
   só posiciona e organiza — zero fundo, borda ou sombra próprios, conforme a
   regra de ouro nº 10 do VISUAL-MANUAL. --- */
.ww-combat-odds {
  position: absolute;
  left: 0;
  top: 0;
  min-width: 132px;
  padding: 7px 9px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  /* Centraliza no eixo X sobre a carta e ancora a base no ponto dado. */
  transform: translate(-50%, -100%);
  pointer-events: none;
  z-index: 12;
}
.ww-combat-odds__verdict {
  /* O veredito é o que o jogador casual lê. Vai em cor de texto cheia, não
     muted, porque é a informação principal do painel. */
  color: var(--ww-text-primary);
  font-size: 11px;
  letter-spacing: 1.2px;
  text-align: center;
  padding-bottom: 4px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
}
.ww-combat-odds__rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ww-combat-odds__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* Progresso circular de receita, formado no pivô de rotação da pilha — o
   canto onde as cartas do arco convergem. Substituiu a barra reta que ficava
   acima do topo (ver src/render/fx/RecipeProgressArc.js).

   O container precisa de z-index ACIMA das cartas, e o número não é arbitrário:
   `#card-overlay__world` tem `z-index: auto`, então ele NÃO cria contexto de
   empilhamento próprio e as cartas competem direto neste nível com o valor
   cru do zOrder do domínio. E esse valor é alto: `world.js` usa bands na casa
   do milhão (immovable < 1e6, móveis a partir de 1e6, carta em drag a partir
   de 2e6, esta última sem teto — o contador só sobe).

   Daí o número exagerado: precisa ficar acima da band de drag inteira, com
   folga pra uma sessão longa. Com um valor menor o anel some por trás da
   carta em que ele nasce — e some justamente quando fica cheio, que é o único
   instante em que ele recompensa. Indicador de estado se lê por cima do que
   ele mede. */
#recipe-progress-arcs {
  z-index: 2147483000;
}

/* A ALÇA usa o MESMO número, e pelo mesmo motivo escrito acima: ela nasce em
   cima de uma pilha e é justamente a pilha que a taparia. A v1 dela não
   declarava z-index nenhum e ficava por trás das cartas — o founder viu no
   primeiro teste. Indicador (e alvo de gesto) se lê por cima do que ele mede. */
#deck-handles {
  z-index: 2147483000;
}
/* ── A ALÇA DO BARALHO (founder, 2026-08-06) ─────────────────────────────
   A bolinha no pivô que arrasta a pilha inteira. Carta = a carta; alça = a
   pilha. Ver `render/fx/DeckHandle.js` pro porquê.

   Ela é discreta em repouso de propósito: fica sobre a mesa o tempo todo, em
   toda pilha, e um botão opaco em cada uma viraria poluição. O que a torna
   achável é o hover e o tamanho da área de clique (20px de raio contra 11px
   de desenho) — não o peso visual em repouso. */
.ww-deck-handle {
  position: absolute;
  left: 0;
  top: 0;
  /* O tamanho vem do motor (`radiusFor(zoom)`, escrito por `DeckHandle._paint`
     como `--ww-anchor-d`). Aqui havia `30px` fixo — a terceira cópia do raio
     da âncora, contra o 16 do hit-test e o 15 do desenho. Com o número fixo, a
     bolinha não acompanhava o zoom e o alvo sumia na mesa afastada. */
  width: var(--ww-anchor-d, 32px);
  height: var(--ww-anchor-d, 32px);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.62);
  box-shadow:
    inset 0 0 0 2px rgba(0, 0, 0, 0.3),
    0 1px 4px rgba(0, 0, 0, 0.25);

  /* ── `none`, E ISSO NÃO É DESCUIDO (2026-08-06) ──────────────────────
     A v1 desta alça usava `pointer-events: auto` + `cursor: grab`, e ela
     NÃO ARRASTAVA: o input do jogo é do Phaser (`scene.input`, que escuta
     no canvas), e esta camada DOM fica POR CIMA dele. Com `auto`, a alça
     capturava o pointerdown e o handler do jogo nunca era chamado — o
     hover funcionava (é CSS puro) e o arrasto morria em silêncio, que é o
     pior par possível: parecia viva e não respondia.

     Então ela é só PINTURA, e quem responde ao gesto é o hit-test
     matemático de `DeckHandle.deckAt()`, do mesmo jeito que a bolinha de
     receita (T21) sempre fez. O cursor também vem de lá — o canvas é um
     elemento só, e `cursor` de CSS não sabe onde dentro dele está a alça. */
  pointer-events: none;

  /* ── NÃO ANIME O `transform` DAQUI (founder, 2026-08-06) ─────────────────
     Aqui havia `transition: background 140ms ease, transform 140ms ease`, e o
     `transform` nessa lista era A CAUSA do bug mais teimoso deste projeto: a
     alça "seguindo lentamente atrás do mouse, e nunca parando na posição
     dele".

     O motivo é o que uma transição É: a cada `transform` novo que o motor
     escreve, o browser INTERPOLA 140ms até lá. Num arrasto o motor reescreve o
     valor várias vezes por quadro, então a animação nunca termina — ela
     recomeça perseguindo um alvo que já mudou. Uma transição não alcança: ela
     persegue. Era literalmente "fica um pouco atrás sempre".

     Isso sobreviveu a TRÊS correções que mexeram na lógica da posição (o
     `FOLLOW_LERP` do deck, o `pivot(0)` da âncora, o ritmo de repintura) —
     todas certas e todas insuficientes, porque nenhuma tocava aqui. O atraso
     não estava em QUANDO o valor era calculado nem em QUAL era: estava no
     caminho entre escrevê-lo e vê-lo.

     `background` continua animando: cor pode ter inércia, posição de algo que
     você está segurando com o dedo não pode. A regra que fica: **nada que
     acompanhe o ponteiro pode ter `transition` no `transform`.** */
  transition: background 140ms ease;
  will-change: transform;

  /* ── O NÚMERO DE CARTAS DENTRO DA ÂNCORA (founder, 2026-08-06) ─────────
     *"no lugar da âncora era legal mostrar quantas cartas tem, de forma bem
     delicada, no meio da âncora"*.

     **"Bem delicada" é requisito, não adjetivo** — é o que decide o peso, o
     tamanho e a cor abaixo, e é por isso que o número é 700 e não 900 (o do
     balão de prop, que compete por atenção; aqui não deve).

     O `font-size` é FRAÇÃO do diâmetro, nunca px fixo. A alça já teve um
     `30px` fixo que virou a terceira cópia do raio da âncora e fez a bolinha
     não acompanhar o zoom (a nota está logo acima). Derivando de
     `--ww-anchor-d`, o número herda o comportamento da alça seja qual for —
     é o que garante que ele não descole no zoom.

     `pointer-events: none` já está declarado acima e é herdado: o número não
     rouba clique de ninguém. */
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ww-font-body);
  font-size: calc(var(--ww-anchor-d, 32px) * 0.42);
  font-weight: 700;
  /* O número não "dança" ao virar 9 → 10. */
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: rgba(0, 0, 0, 0.55);
  user-select: none;
}

/* O realce entra por classe, escrita pelo hit-test — não por `:hover`, que
   não chega aqui com `pointer-events: none`. */
.ww-deck-handle--hot {
  background: rgba(255, 255, 255, 0.95);
  transform-origin: center;
}

/* ── O CONVITE: um pontilhado que diz "dá pra arrastar aqui" ───────────────
   Ordem do founder, 2026-08-06, textual: *"vale um pontilhado circular quando
   a gente passa com o mouse em cima (…) porque, cara, não dá! Se eu não
   soubesse que já dá para fazer isso, é impossível saber que a gente espera
   que o jogador faça isso."*

   Ele está certo, e o diagnóstico é mais duro que o pedido: a alça é o ÚNICO
   jeito de mover uma pilha, e nada na tela dizia isso. Uma bolinha branca
   parada lê como enfeite ou como indicador de estado — não como pega.

   O anel nasce FORA do desenho (`inset` negativo) porque ele mostra a área de
   PEGA, que é maior que a bolinha (`reach()` soma a folga ao raio). Assim o
   convite não é só "clique aqui": é "este tanto aqui todo é clicável", que é
   justamente a informação que faltava — e a que resolve o zoom afastado.

   `::after` e não um elemento novo: é pintura pura de um estado, e a alça já
   tem `pointer-events: none` (o anel herda, então ele não rouba clique
   nenhum). Nada disto entra no hit-test — quem responde ao gesto continua
   sendo `Anchors.at()`, e este anel só CONTA o que aquele já decide. */
.ww-deck-handle::after {
  content: '';
  position: absolute;
  /* A folga além do desenho, na proporção de `PAD_MOUSE_PX / ANCHOR_RADIUS`.
     Em % do próprio diâmetro pra acompanhar o zoom sozinho, sem uma segunda
     conta em JS que pudesse divergir da primeira. */
  inset: -25%;
  border-radius: 50%;
  border: 2px dashed rgba(255, 255, 255, 0.85);
  /* Um fio escuro por baixo pra que o pontilhado se leia tanto sobre a mesa
     clara quanto sobre uma carta clara — sem isto ele some justamente onde
     mais aparece, que é em cima do leque. */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.22), inset 0 0 0 1px rgba(0, 0, 0, 0.22);
  opacity: 0;
  transform: scale(0.82);
  /* Some rápido, aparece com calma: o convite tem que chegar como sugestão,
     não como alarme. Aqui a transição é BEM-VINDA — isto não acompanha o
     ponteiro, é um estado de hover que liga e desliga (ver a nota no
     `transform` da alça sobre por que lá ela era o bug). */
  transition: opacity 120ms ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

/* Só quando o dedo está por perto. O `--hot` é escrito pelo mesmo hit-test que
   decide o arrasto, então o convite aparece EXATAMENTE onde o gesto funciona —
   nunca prometendo uma pega que não existe. */
.ww-deck-handle--hot::after {
  opacity: 1;
  transform: scale(1);
}

/* Pilha trabalhando já tem o anel de progresso girando no mesmo ponto. Dois
   círculos concêntricos animados ali viram ruído, e o de progresso é o que
   importa naquele instante — ele diz quanto falta. O convite se cala. */
.ww-deck-handle--busy::after {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .ww-deck-handle::after { transition: opacity 120ms ease; transform: scale(1); }
}

/* Pilha trabalhando: a alça fica sólida. É o mesmo pivô onde o anel de
   progresso gira, então os dois se leem como uma coisa só — o anel é o
   quanto falta, a alça é o miolo dele. */
.ww-deck-handle--busy {
  background: rgba(255, 255, 255, 0.9);
  box-shadow:
    inset 0 0 0 1.5px rgba(0, 0, 0, 0.4),
    0 1px 4px rgba(0, 0, 0, 0.3);
}

.ww-recipe-arc {
  position: absolute;
  left: 0;
  top: 0;
  overflow: visible;
  will-change: transform;
}
/* O trilho é o anel "vazio": escuro e opaco pra que o arco de progresso tenha
   contra o que ser lido, tanto sobre a mesa quanto sobre uma carta. */
.ww-recipe-arc__track {
  stroke: var(--ww-line);
  stroke-linecap: round;
  opacity: 0.28;
}
.ww-recipe-arc__fill {
  stroke: var(--ww-accent);
  stroke-linecap: round;
  /* Contorno escuro sob o traço: o mesmo truque das bordas de carta. Sem ele
     o laranja some sobre um sprite claro. */
  filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.55));
  /* Acompanha o tick do motor sem "pular" entre frames — mesma suavização
     que a barra usava na largura. */
  transition: stroke-dashoffset 0.08s linear;
}
/* O fecho: o anel SEGURA cheio por um instante e só então se desfaz pulsando
   pra fora. Sem essa espera o `ease-out` no opacity despenca nos primeiros
   milissegundos e o jogador nunca vê o círculo completo — ele só some, e o
   único momento de recompensa visual do indicador se perde. A barra antiga
   não tinha esse problema porque era sólida e piscava a cor. */
.ww-recipe-arc--flash {
  animation: ww-recipe-arc-flash 320ms ease-out forwards;
}
@keyframes ww-recipe-arc-flash {
  0%   { opacity: 1; }
  45%  { opacity: 1; }
  100% { opacity: 0; }
}
.ww-recipe-arc--flash .ww-recipe-arc__fill {
  stroke: var(--ww-accent);
  transform-box: fill-box;
  transform-origin: center;
  animation: ww-recipe-arc-pop 320ms cubic-bezier(.22, 1.2, .36, 1) forwards;
}
@keyframes ww-recipe-arc-pop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.06); }
  100% { transform: scale(1.3); }
}
/* O trilho some junto — deixá-lo aceso enquanto o arco se expande criaria um
   segundo anel fantasma no lugar do que acabou de completar. */
.ww-recipe-arc--flash .ww-recipe-arc__track {
  opacity: 0;
  transition: opacity 120ms ease-out;
}
.ww-recipe-arc--cancel {
  animation: ww-recipe-arc-cancel 220ms ease-out forwards;
}
@keyframes ww-recipe-arc-cancel {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* ── A BOLINHA DE RECEITA ─────────────────────────────────────────────────
   O miolo do anel: um disco branco translúcido no pivô da pilha, presente só
   enquanto há receita acontecendo ali. Mora na mesma faixa de z do anel (é a
   mesma leitura, no mesmo ponto) e herda a matriz da câmera pelo pai.

   Branco chapado com transparência, e não a cor do acento: o anel já é
   laranja e é ele que carrega o progresso. A bolinha é o convite discreto —
   quem não olhar pra ela não perde nada, quem parar o cursor em cima ganha o
   detalhe. O contorno escuro é o que a mantém legível tanto sobre a mesa teal
   quanto sobre uma carta clara. */
#recipe-dots {
  z-index: 2147483000;
}
.ww-recipe-dot {
  position: absolute;
  left: 0;
  top: 0;
  overflow: visible;
  will-change: transform;
}
.ww-recipe-dot__body {
  fill: rgba(255, 255, 255, 0.62);
  stroke: rgba(17, 17, 17, 0.35);
  stroke-width: 1.5;
  transition: fill 120ms ease-out, stroke-width 120ms ease-out;
}
/* Sob o cursor a bolinha assenta: fica opaca e ganha traço. É o mesmo
   princípio do hover de carta — o alvo confirma que É um alvo antes de o
   painel abrir. */
.ww-recipe-dot--hot .ww-recipe-dot__body {
  fill: rgba(255, 255, 255, 0.95);
  stroke: var(--ww-line);
  stroke-width: 2.5;
}

/* O painel de detalhe da bolinha. Compõe `.ww-tooltip` inteiro (mesma
   pintura, mesma tipografia do tooltip de carta) e só acrescenta o que é
   dele: os títulos de seção, a linha do desfecho e a divisória entre duas
   receitas que dividem o mesmo pivô. */
.ww-recipe-tip {
  max-width: 240px;
}
.ww-recipe-tip .ww-tooltip__meta {
  justify-content: space-between;
}
.ww-recipe-tip__time {
  color: var(--ww-text-primary);
  font-weight: 800;
  letter-spacing: 0.5px;
}
.ww-recipe-tip__section-title {
  color: var(--ww-text-muted);
  font-size: 9px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-top: 6px;
}
.ww-recipe-tip__out {
  font-size: 11px;
  font-weight: 700;
  line-height: 1.35;
  margin-top: 2px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
}
.ww-recipe-tip__vary {
  color: var(--ww-text-muted);
  font-size: 10px;
  font-weight: 700;
  font-style: italic;
  margin-left: 4px;
}
.ww-recipe-tip__sep {
  height: var(--ww-line-width);
  background: var(--ww-line);
  opacity: 0.18;
  margin: 8px -12px;
}

/* ── AQUI MORAVA O SOCO DE RECEITA (removido no T23) ──────────────────────
   `.ww-card--recipe-punch` dava um scale 1 → 1,12 → 1 em toda carta
   participante ao concluir uma combinação. Era o segundo canal contando a
   mesma coisa que a assinatura de reação já conta, e contava pior: o T22
   mostrou que ele batia sempre em carta empilhada e que acendia ou não
   conforme a carta estivesse podre.

   Quem marca "algo aconteceu aqui" agora é um só: o pisca da assinatura, no
   frame exato do resultado (`.ww-card__reaction`, logo abaixo). */

/* ── ASSINATURA DA REAÇÃO (T17) ──────────────────────────────────────────
   A camada do pisca: enquanto uma receita está rolando, todas as cartas dela
   piscam com o MESMO tom e o MESMO padrão de faixas, e reações diferentes na
   mesa têm assinaturas diferentes. Quem escolhe tom e padrão é
   `fx/reaction-signature.js`; quem aplica é `CardDOMRenderer.flashReaction`.

   Aqui mora só a geometria. Nem a cor, nem o padrão, nem a duração, nem a
   intensidade: tudo isso é inline, vindo do módulo da assinatura, que é o
   lugar único desses números.

   Três decisões que valem a linha de comentário:

   - Vive DENTRO do `__inner`, não do `.ww-card`. O inner é quem tem
     `overflow: hidden` e a borda — o pisca sai recortado no formato exato da
     carta, cantos arredondados inclusive, sem repetir o raio aqui.

   - `z-index: 4` põe o pisca acima do véu de hover (que é 2) e de todo o
     conteúdo. A assinatura tem que vencer o realce do mouse: são 340ms, e é
     nesses 340ms que a informação é dada.

   - A única propriedade que a animação toca é `opacity`, e o fundo é estático
     durante o pisca. Isso deixa o navegador promover a camada e animar fora do
     thread principal — é o que faz oito reações simultâneas custarem o mesmo
     que uma. Sem `will-change` fixo: a promoção acontece só enquanto anima, e
     deixá-la ligada custaria memória de textura em toda carta que já reagiu. */
.ww-card__reaction {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  z-index: 4;
}

/* "Thud" ao soltar uma carta. Vem do scale 1.06 (drag) pra 1, passando por
   scaleY 0.92 (o impacto) e voltando com back-easing. Sensação de peso. */
.ww-card__inner.ww-card--thud {
  animation: ww-card-thud 200ms cubic-bezier(.22, 1.4, .32, 1);
}
@keyframes ww-card-thud {
  0%   { transform: scale(1.06); }
  40%  { transform: scale(1.04, 0.92); }
  100% { transform: scale(1, 1); }
}

/* Thud pesado: primeira vez que uma carta immovable é posicionada. Mais
   afundado, oscilação maior, duração estendida — sensação de "pousou firme". */
.ww-card__inner.ww-card--thud-heavy {
  animation: ww-card-thud-heavy 420ms cubic-bezier(.22, 1.4, .32, 1);
}
@keyframes ww-card-thud-heavy {
  0%   { transform: scale(1.10, 1.10); }
  25%  { transform: scale(1.14, 0.82); }
  55%  { transform: scale(0.96, 1.06); }
  78%  { transform: scale(1.02, 0.98); }
  100% { transform: scale(1, 1); }
}

/* Recusa: duplo clique numa pilha presa por uma combinação em curso. Tremor
   curto e horizontal, amplitude de dois pixels — o "não" mais discreto que
   ainda se vê. Sem cor e sem escala de propósito: a pilha não errou nada, ela
   só não está disponível pra ser desmontada agora. */
.ww-card__inner.ww-card--refuse {
  animation: ww-card-refuse 300ms cubic-bezier(.36, .07, .19, .97);
}
@keyframes ww-card-refuse {
  0%, 100% { transform: translateX(0); }
  15%      { transform: translateX(-2px); }
  35%      { transform: translateX(2px); }
  55%      { transform: translateX(-1.5px); }
  75%      { transform: translateX(1px); }
}

/* Fold line — dupla função:
   (1) marca o limite visual do cabeçalho (texto do nome é cortado acima);
   (2) é o alvo de snap: o topo da próxima carta empilhada encosta nela.
   --ww-header-height vem do CardDOMRenderer, ancorada em CARD_HEADER_HEIGHT
   (core/deck.js) — fonte única. Zero animação: é guia permanente, não
   destaque. Posição em coord do __inner (que tem border-box, inset:0),
   então `top: var(--ww-header-height)` mede do topo da carta. */
.ww-card__fold {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--ww-header-height, 30px);
  height: 0;
  border-top: 1px dashed rgba(17, 17, 17, 0.35);
  pointer-events: none;
  z-index: 0;
}

/* Cabeçalho da carta: selo de tipo + título, na faixa acima da fold line.
   Altura fixa = header − border top − padding top; o que não couber é
   cortado (e o renderer ainda reduz a fonte do nome até caber). */
.ww-card__header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2px;
  width: 100%;
  height: calc(var(--ww-header-height, 30px) - var(--ww-line-width) - 8px);
  /* Acima da camada do ícone: o nome nunca fica atrás do desenho. */
  position: relative;
  z-index: 1;
}

/* Selo de TIPO — o que a carta é (gente, lugar, comida, ameaça…).
   Reusa o molde do selo de status do rodapé (`.ww-card__badge`): mesma
   pastilha clara, mesmo anel discreto. Não é pintura nova — é a mesma
   geometria de superfície aplicada no topo (VISUAL-MANUAL §1).

   Por que pastilha e não o glifo solto em preto: a cor da carta vai de
   `#a8ff66` a `#2a2a35`. Preto sobre a cor das ameaças (`#8b3a2f`) dá 2.5:1,
   abaixo do mínimo de 3:1 pra gráfico — e ameaça é justamente o ícone que
   nunca pode falhar de ler. Sobre a pastilha o contraste é o mesmo em todas
   as 222 cores de carta do repo. */
/* 18px e não os 16px de antes: a pastilha passou a carregar a ARTE da carta
   (2026-08-06), que é figura com detalhe interno — diferente da silhueta
   chapada de categoria, que sobrevivia a qualquer tamanho. Dois pixels a
   mais é o que separa "dá pra reconhecer a panela" de "tem algo redondo
   ali", e o cabeçalho os tinha de folga. */
.ww-card__type {
  flex: 0 0 auto;
  width: calc(18px * var(--a11y-card-badge, 1));
  height: calc(18px * var(--a11y-card-badge, 1));
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.ww-card__type-icon {
  width: calc(13px * var(--a11y-card-badge, 1));
  height: calc(13px * var(--a11y-card-badge, 1));
  display: block;
  fill: var(--ww-text-primary);
}

/* ── A ARTE DA CARTA NO SELO DO CABEÇALHO (founder, 2026-08-06) ──────────
   O mesmo desenho do centro, miniaturizado. É o que permite identificar a
   carta no leque em arco, onde só esta faixa aparece.

   `object-fit: contain` e não `cover`: a arte é uma figura sobre fundo
   transparente, e recortá-la pra preencher o círculo cortaria justamente a
   silhueta que dá a leitura. */
.ww-card__type-art {
  width: calc(14px * var(--a11y-card-badge, 1));
  height: calc(14px * var(--a11y-card-badge, 1));
  display: block;
  object-fit: contain;
  /* A arte é desenhada pra fundo claro; a pastilha já é branca, então nada
     de filtro aqui — o que chega é o pixel original. */
  image-rendering: auto;
}

/* O emoji tem métrica de FONTE, não de caixa: `line-height: 1` e o bloco
   centrado são o que impede o glifo de descer abaixo da pastilha (a
   baseline do emoji fica bem abaixo do centro óptico). */
.ww-card__type-emoji {
  font-size: calc(12px * var(--a11y-card-badge, 1));
  line-height: 1;
  display: block;
  text-align: center;
}

/* ── O SELO DE VOCAÇÃO (founder, 2026-08-06) ─────────────────────────────
   Pendurado no canto inferior direito da pastilha de tipo, transbordando
   um pouco pra fora dela. Duas razões pra transbordar em vez de caber
   dentro: a pastilha tem 16px e um selo INTERNO ficaria com ~6px úteis
   (ilegível); e o contorno mordendo a borda é o que faz o olho ler "isto
   pertence àquele ícone" em vez de "há dois ícones aqui".

   `overflow: visible` na pastilha é obrigatório — ela é um círculo, e o
   default recortaria o selo exatamente na diagonal onde ele mais aparece. */
.ww-card__type {
  overflow: visible;
  position: relative;
}

.ww-card__vocation {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: calc(11px * var(--a11y-card-badge, 1));
  height: calc(11px * var(--a11y-card-badge, 1));
  border-radius: 50%;
  background: var(--ww-text-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  /* O anel na cor da carta separa o selo da pastilha branca por baixo. Sem
     ele, os dois círculos claros se fundem numa mancha só no zoom out. */
  box-shadow: 0 0 0 1.5px var(--ww-card-color, rgba(255, 255, 255, 0.92));
  pointer-events: none;
}

/* Invertido em relação à arte: glifo CLARO sobre pastilha escura. É o que dá
   a hierarquia — a arte da carta é a figura principal (colorida, maior), a
   marca de categoria/vocação é a nota de rodapé (chapada, menor). Sem a
   inversão as duas competiriam pelo mesmo peso visual.

   Os dois seletores porque o selo carrega glifo de VOCAÇÃO quando a carta
   tem uma, e o de CATEGORIA quando não tem (desde 2026-08-06, quando a arte
   assumiu a figura). São desenhos de arquivos diferentes com classes
   diferentes, e os dois precisam caber nos mesmos 11px — o de categoria
   nasceu com 13px, medida de quando ELE era a figura. */
.ww-card__vocation-icon,
.ww-card__vocation .ww-card__type-icon {
  width: calc(8px * var(--a11y-card-badge, 1));
  height: calc(8px * var(--a11y-card-badge, 1));
  display: block;
  fill: rgba(255, 255, 255, 0.96);
}

.ww-card__name {
  /* Ocupa o que sobra do cabeçalho depois do selo de tipo. `min-width: 0`
     é obrigatório: sem ele o item flex se recusa a encolher abaixo do
     tamanho do texto e o nome vaza por cima do selo. */
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  /* O multiplicador do tema de visão. O `_autoShrinkName` respeita o piso
     multiplicado — encolher de volta pro tamanho antigo anularia o tema. */
  font-size: calc(11px * var(--a11y-card-text, 1));
  font-weight: 800;
  /* Entrelinha colada: o caso que manda aqui é o nome de DUAS linhas numa
     caixa de 19px. Com 1.1 duas linhas de 9px não cabiam e o nome inteiro
     desabava pro piso de 8px. Com 1.05 cabem — ver `_autoShrinkName`. */
  line-height: 1.05;
  text-align: center;
  letter-spacing: -0.1px;
  color: var(--a11y-card-ink, var(--ww-text-primary));
}

/* Nome que não coube nem no piso da fonte: corta na última linha inteira com
   reticência em vez de decepar a letra. `-webkit-line-clamp` exige a caixa
   -webkit-box, então esta variante troca o display do flex de centralização —
   e não faz falta, porque um nome clampado já ocupa a caixa toda. */
.ww-card__name--clamped {
  display: block;
}
.ww-card__name--clamped > span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Camada do ícone — emoji, sprite ou o que a carta usar como desenho central.
   Ocupa o CORPO da carta: a área abaixo da linha de dobra, que é o quadrado
   de 100×100 que o motor chama de corpo (`CARD_BODY_SIDE`). Centrado nos dois
   eixos dentro dela.

   Antes o ícone era item de flex logo abaixo do cabeçalho, e por isso ficava
   preso ao topo do corpo em vez de no meio dele — medido, o emoji nascia 24px
   acima do centro do corpo e o sprite 16px, em toda carta da mesa.

   Camada absoluta e não item de flex porque o alinhamento tem que ser
   indiferente ao que mais está no corpo (selos de status, rótulo de loja
   fechada): o ícone se centra no corpo, não no que sobra dele. `z-index: 0`
   deixa cabeçalho e selos por cima. */
.ww-card__media {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--ww-header-height, 30px);
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 0;
}

.ww-card__emoji {
  font-size: calc(44px * var(--a11y-card-art, 1));
  line-height: 1;
  font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', system-ui, sans-serif;
}

.ww-card__sprite {
  /* Teto em % do corpo, não em px: o desenho respira igual em qualquer
     tamanho de carta e nunca encosta na dobra nem na borda de baixo. O tema
     de visão amplia até o teto de 100% — passar dele só seria recorte. */
  max-width: min(100%, calc(86% * var(--a11y-card-art, 1)));
  max-height: min(100%, calc(88% * var(--a11y-card-art, 1)));
  object-fit: contain;
  pointer-events: none;
  /* IMPORTANTE: não forçar `image-rendering: pixelated`.
     As artes chegam em alta resolução (244×274, 332×339) e são DOWNSCALED
     pra caber na carta (~60×60). Nearest-neighbor em downscale joga pixels
     fora aleatoriamente e causa aquele aspecto "estourado/serrilhado" que
     víamos (principalmente no charger-node). O default do navegador (smooth)
     lida bem com arte chapada de poucas cores — fica limpo sem artefato.
     Se voltar a querer pixel art nítida, o caminho é exportar em resolução
     baixa (ex: 64×64 ou 128×128) e reativar pixelated SÓ nesse caso. */
  image-rendering: auto;
}

/* Barra inferior de selos de status. NUNCA carrega texto — só ícones
   redondos, alinhados da esquerda pra direita. Detalhes numéricos e
   nomes ficam no tooltip (mouseover da carta). */
.ww-card__badges {
  margin-top: auto;
  display: flex;
  gap: 3px;
  justify-content: flex-start;
  align-items: center;
  flex-wrap: nowrap;
  overflow: hidden;
  min-height: calc(20px * var(--a11y-card-badge, 1));
  padding: 0 2px;
  /* Acima da camada do ícone, como o cabeçalho. */
  position: relative;
  z-index: 1;
}

.ww-card__badge {
  flex: 0 0 auto;
  width: calc(18px * var(--a11y-card-badge, 1));
  height: calc(18px * var(--a11y-card-badge, 1));
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), inset 0 0 0 1px rgba(0, 0, 0, 0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(11px * var(--a11y-card-badge, 1));
  line-height: 1;
  font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', system-ui, sans-serif;
  user-select: none;
  pointer-events: none;
}
/* Selo de OCUPANTE — quem está preso/dentro desta carta. Mesma geometria do
   selo de status (o molde não muda, VISUAL-MANUAL §"só uma geometria"): o que
   muda é o anel escuro, que lê como "fechado" em vez de "ativo".

   Sem animação de propósito. O selo de alerta pulsa porque pede ação imediata
   numa máquina parada; alguém preso é um estado que persiste, e piscar isso na
   mesa o tempo todo viraria ruído em vez de informação. A carta em repouso já
   diz o que precisa dizer. */
.ww-card__badge--occupant {
  /* Maior que o selo de status (18px): este não é um estado entre outros da
     carta — é OUTRA CARTA presa dentro dela, e precisa ser legível como
     pessoa. A 18px o emoji de quem está preso não se distingue de um ícone
     qualquer, e a marca perde justamente o que ela existe pra dizer. */
  width: calc(26px * var(--a11y-card-badge, 1));
  height: calc(26px * var(--a11y-card-badge, 1));
  font-size: calc(16px * var(--a11y-card-badge, 1));
  background: rgba(232, 226, 214, 0.96);
  box-shadow:
    0 1px 3px rgba(0, 0, 0, 0.35),
    inset 0 0 0 2px #3a3a42;
  pointer-events: auto;
}

/* Badge de alerta — pulsa devagar pra chamar atenção que a máquina não está
   funcionando. A bolha [data-tip] revela o motivo. */
.ww-card__badge--alert {
  background: #ffd2d2;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), inset 0 0 0 1px #c0392b;
  pointer-events: auto;
  animation: ww-badge-alert-pulse 1.6s ease-in-out infinite;
}
@keyframes ww-badge-alert-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.15); }
}

/* Bateria antiga (.ww-card__battery / __battery-fill) substituída por
   gradient no fundo do __inner. Ver `.ww-card__inner--battery` acima. */

.ww-hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  font-family: var(--ww-font-body);
  color: var(--ww-text-primary);
  z-index: 20;
}

/* Painel HUD base — mesma geometria da carta (raised + border 3px + radius + shadow chapada).
   Aplicada nos painéis money/clock/bill. Debt herda também mas com fill accent. */
.ww-hud__panel {
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 10px 12px;
}

.ww-hud__label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--ww-text-muted);
  margin-top: 4px;
  line-height: 1;
}

/* Painel unificado de tempo: hora (grande) + dia (médio) + período (pequeno).
   Compõe .ww-panel pra herdar geometria. Panic troca o fill inteiro. */
.ww-hud__time-panel {
  position: absolute;
  top: 14px;
  left: 16px;
  width: 180px;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: background-color 0.15s linear, color 0.15s linear;
}
/* Arco do sol: substitui o relógio HH:MM. Track horizontal com 5 marquinhas
   (dawn / morning / midday / evening / dusk) e um glyph (emoji) que percorre
   da esquerda pra direita seguindo getDayProgress(). O glyph reflete o
   clima atual + a fase do dia (sol, sol nascente, lua, neblina, poeira…).
   Trajetória: x linear, y senoidal (apex no meio). Discreto — arco baixo,
   sem fundo no glyph, leitura por silhueta de emoji. */
.ww-hud__sun-arc {
  position: relative;
  width: 100%;
  height: 22px;
  margin: 0 0 4px;
}
.ww-hud__sun-track {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--ww-line);
  border-radius: 2px;
  opacity: 0.6;
}
.ww-hud__sun-tick {
  position: absolute;
  bottom: -1px;
  width: 2px;
  height: 5px;
  background: var(--ww-line);
  transform: translateX(-50%);
  border-radius: 1px;
  opacity: 0.6;
}
.ww-hud__sun-tick:nth-child(3) {
  height: 7px;
  bottom: -2px;
  opacity: 0.85;
}
.ww-hud__sun {
  --sun-x: 0%;
  --sun-y: 0;
  position: absolute;
  left: var(--sun-x);
  bottom: 0;
  width: 18px;
  height: 18px;
  font-size: 16px;
  line-height: 18px;
  text-align: center;
  font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', system-ui, sans-serif;
  transform: translate(-50%, calc(-1 * var(--sun-y) * 14px));
  transition: transform 0.2s linear, left 0.2s linear;
}
.ww-hud__day-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-top: 6px;
  border-top: 2px solid var(--ww-line);
  margin-top: 2px;
}
.ww-hud__day-icon {
  font-size: 13px;
  line-height: 1;
  font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', system-ui, sans-serif;
}
.ww-hud__day {
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--ww-text-muted);
  line-height: 1;
}
.ww-hud__time-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.ww-hud__time {
  font-size: 11px;
  font-weight: 700;
  color: var(--ww-text-muted);
  line-height: 1;
}

/* Frozen (grace period): time hasn't started yet. Hint slot is visible,
   sun glyph idles. Cleared on the first card drag. */
.ww-hud__frozen-hint {
  display: none;
  margin-top: 4px;
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--ww-accent);
  line-height: 1.2;
  animation: ww-hud-frozen-blink 1.4s ease-in-out infinite;
}
.ww-hud__time-panel.frozen .ww-hud__frozen-hint {
  display: block;
}
@keyframes ww-hud-frozen-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

/* Panic (fim do dia): painel inteiro vira accent. Pulso de scale composited. */
.ww-hud__time-panel.panic {
  background: var(--ww-accent);
}
.ww-hud__time-panel.panic .ww-hud__day,
.ww-hud__time-panel.panic .ww-hud__time {
  color: var(--ww-surface-raised);
}
.ww-hud__time-panel.panic .ww-hud__day-row {
  border-top-color: var(--ww-surface-raised);
}
.ww-hud__time-panel.panic .ww-hud__sun-track,
.ww-hud__time-panel.panic .ww-hud__sun-tick {
  background: var(--ww-surface-raised);
}
.ww-hud__time-panel.panic {
  animation: ww-hud-pulse 0.8s ease-in-out infinite;
  transform-origin: left center;
}
@keyframes ww-hud-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.03); }
}

/* Hint "drag cards..." — fundo-centro, fora do caminho do clock (topo-esq)
   e dos speed buttons (topo-centro). Fica como pílula discreta. */
.ww-hud__hint {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  font-weight: 700;
  color: var(--ww-text-muted);
  background: var(--ww-surface-raised);
  border: 2px solid var(--ww-line);
  border-radius: 999px;
  padding: 6px 14px;
  pointer-events: none;
  white-space: nowrap;
}

.ww-hud__money {
  position: absolute;
  top: 14px;
  right: 16px;
  min-width: 92px;
  font-size: 26px;
  font-weight: 900;
  font-family: var(--ww-font-display);
  color: var(--ww-accent);
  line-height: 1;
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 10px 12px;
  text-align: right;
  transition: background-color 0.15s linear, color 0.15s linear;
}

/* Crédito: aparece à ESQUERDA do money quando o banco já abriu. Mesmo
   molde visual, alinhado pela borda superior pra ler como par. O `right`
   calcula a largura do bloco do money (min-width 92px + padding 10+12 +
   borda 2×2) com folga de 10px entre os dois. */
.ww-hud__credit {
  position: absolute;
  top: 14px;
  right: calc(16px + 92px + 24px + 4px + 10px);
  display: none;
  min-width: 92px;
  font-size: 16px;
  font-weight: 900;
  font-family: var(--ww-font-display);
  color: var(--ww-text-primary);
  line-height: 1;
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 10px 12px;
  text-align: right;
}

/* Partícula de delta do money. Sai do HUD money, verde pra cima, vermelha pra baixo. */
.ww-money-particle {
  position: absolute;
  top: 14px;
  right: 16px;
  font-size: 14px;
  font-weight: 800;
  pointer-events: none;
  font-family: var(--ww-font-body);
  opacity: 0;
  animation-duration: 900ms;
  animation-fill-mode: forwards;
  animation-timing-function: cubic-bezier(.34, 1.56, .64, 1);
}
.ww-money-particle.up {
  color: #2a8f2a;
  animation-name: ww-money-particle-up;
}
.ww-money-particle.down {
  color: var(--ww-accent);
  animation-name: ww-money-particle-down;
}
@keyframes ww-money-particle-up {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.7); }
  15%  { opacity: 1; transform: translate(0, -14px) scale(1.1); }
  70%  { opacity: 1; transform: translate(0, -24px) scale(1); }
  100% { opacity: 0; transform: translate(0, -36px) scale(0.95); }
}
@keyframes ww-money-particle-down {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.7); }
  15%  { opacity: 1; transform: translate(0, 20px) scale(1.1); }
  70%  { opacity: 1; transform: translate(0, 30px) scale(1); }
  100% { opacity: 0; transform: translate(0, 42px) scale(0.95); }
}
/* Shake = só transform (composited). Troca de cor via background/color sem shadow animado. */
.ww-hud__money.shake {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
  animation: ww-money-shake 0.9s cubic-bezier(.36,.07,.19,.97) both;
}
@keyframes ww-money-shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  10%, 30%, 50%, 70%, 90% { transform: translate3d(-6px, 0, 0); }
  20%, 40%, 60%, 80%      { transform: translate3d( 6px, 0, 0); }
}
/* Pulse curto: cada moeda da CoinFountain "absorve" no HUD com este flash. */
.ww-hud__money--coin-pop {
  animation: ww-money-coin-pop 0.18s ease-out both;
}
@keyframes ww-money-coin-pop {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}

.ww-hud__bill {
  position: absolute;
  top: 80px;
  right: 16px;
  width: 172px;
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 10px 12px;
  font-size: 12px;
  color: var(--ww-text-primary);
  transition: background-color 0.2s linear, color 0.2s linear;
}
/* Flash = troca de fill (accent). Curto e não-infinito. Só opacity muda na curva. */
.ww-hud__bill.flash {
  animation: ww-bill-flash 0.9s ease-out both;
}
@keyframes ww-bill-flash {
  0%, 40% { background: var(--ww-accent); color: var(--ww-surface-raised); }
  100%    { background: var(--ww-surface-raised); color: var(--ww-text-primary); }
}

/* Splash central de cobrança — carta "gigante" com mesma geometria. Só transform+opacity na anim. */
.ww-bill-splash {
  position: fixed;
  top: 38%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.7);
  pointer-events: none;
  z-index: 90;
  opacity: 0;
  text-align: center;
  font-family: var(--ww-font-display);
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 20px 32px;
  min-width: 280px;
}
.ww-bill-splash.show {
  animation: ww-splash 1.8s ease-out forwards;
}
@keyframes ww-splash {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
  15%  { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
  25%  {             transform: translate(-50%, -50%) scale(1.0); }
  80%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }
  100% { opacity: 0; transform: translate(-50%, -60%) scale(0.95); }
}
.ww-bill-splash__title {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--ww-surface-raised);
  opacity: 0.85;
}
.ww-bill-splash__amount {
  font-size: 56px;
  font-weight: 900;
  margin: 6px 0 4px;
  color: var(--ww-surface-raised);
  font-family: var(--ww-font-display);
  line-height: 1;
}
.ww-bill-splash__breakdown {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ww-surface-raised);
  opacity: 0.8;
}

.ww-hud__bill-title {
  font-size: 10px;
  font-weight: 800;
  color: var(--ww-text-muted);
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 6px;
}
.ww-hud__bill-line {
  display: flex;
  justify-content: space-between;
  line-height: 1.6;
  font-weight: 600;
}
.ww-hud__bill-total {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
  padding-top: 6px;
  border-top: var(--ww-line-width) solid var(--ww-line);
  font-weight: 800;
  color: var(--ww-text-primary);
}

/* Debt = painel accent por natureza (estado visível). */
.ww-hud__debt {
  position: absolute;
  top: 220px;
  right: 16px;
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.5px;
}
/* Critical = pulse de scale, não de shadow (shadow animado é caro). */
.ww-hud__debt.critical {
  animation: ww-hud-pulse 0.8s ease-in-out infinite;
  transform-origin: right center;
}
.ww-hud__debt-icon {
  margin-right: 4px;
}

/* ── Rodapé de score (packs que declaram `liveScore`) ──
   Canto inferior direito: presente e legível de relance, longe do clock e do
   caminho de drag das cartas. Pintura vem de .ww-panel; aqui só posição e
   tamanho, como manda o manual. */
.ww-hud__score {
  position: absolute;
  bottom: 16px;
  right: 16px;
  min-width: 152px;
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 800;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ww-hud__score-line,
.ww-hud__score-total {
  display: flex;
  justify-content: space-between;
  gap: 14px;
}
.ww-hud__score-line { color: var(--ww-text-muted); }
.ww-hud__score-total {
  font-size: 13px;
  font-weight: 900;
  border-top: var(--ww-line-width) solid var(--ww-line);
  margin-top: 4px;
  padding-top: 5px;
}
/* O presságio é aviso, não estatística: separa-se das linhas de número. */
.ww-hud__score-omen {
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px dashed rgba(17, 17, 17, 0.25);
  font-size: 11px;
  font-weight: 700;
  font-style: italic;
  max-width: 190px;
  line-height: 1.3;
}
/* Presságio ruim tinge o painel inteiro de accent — a regra de estado do
   manual: cor troca, chrome não cresce. */
.ww-hud__score--omen { border-color: var(--ww-line); }

.ww-hud__speed {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  pointer-events: auto;
}
.ww-hud__speed button {
  font-family: var(--ww-font-body);
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--ww-text-primary);
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  padding: 8px 14px;
  min-width: 48px;
  cursor: pointer;
  box-shadow: var(--ww-shadow);
  /* Só anima transform e box-shadow (chapada). Nada de filter. */
  transition: transform 0.08s ease, box-shadow 0.08s ease, background-color 0.1s linear, color 0.1s linear;
}
.ww-hud__speed button:hover {
  transform: translate(-1px, -1px);
  box-shadow: 0 6px 0 var(--ww-line);
}
.ww-hud__speed button:active {
  transform: translate(0, 2px);
  box-shadow: 0 2px 0 var(--ww-line);
}
.ww-hud__speed button.active {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}
/* Two audio toggles side by side: SFX (sound effects) and Music. Each button
   is independent — the layout primitive just stacks them horizontally. */
.ww-hud__audio {
  position: absolute;
  bottom: 12px;
  left: 12px;
  display: flex;
  gap: 6px;
  pointer-events: auto;
}
.ww-hud__mute {
  pointer-events: auto;
}
/* Muted state: muted icon already conveys it, but adding muted text-color
   from the muted token keeps it visually consistent with other "off" buttons. */
.ww-hud__mute--off {
  color: var(--ww-text-muted);
}
/* O risco de "desligado" desenhado em CSS: o traço combinante Unicode
   (U+0338) não compõe sobre emoji na fonte do Windows e vira um quadrado
   vazio ao lado do glifo (founder viu o tofu, 2026-08-19). */
.ww-hud__mute {
  position: relative;
}
.ww-hud__mute--strike {
  filter: grayscale(1) opacity(0.55);
}
.ww-hud__mute--strike::after {
  content: '';
  position: absolute;
  inset: 5px;
  background: linear-gradient(45deg, transparent 45%, var(--ww-line) 45%, var(--ww-line) 55%, transparent 55%);
  pointer-events: none;
}

/* Reset tutorials — discreto (opacity baixa em idle, full no hover).
   Click dispara pulse rápido pra feedback visual. */
/* O volume-mestre: slider curto e "silenciar tudo", no mesmo cacho dos
   canais. Discretos em repouso, como o ↻; inteiros no hover. */
.ww-hud__volume,
.ww-hud__mute-all {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0 6px;
  height: 32px;
  border-radius: 8px;
  background: var(--ww-panel-bg, rgba(20, 16, 12, 0.72));
  border: 1px solid var(--ww-line);
  color: var(--ww-text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 120ms ease;
  pointer-events: auto;
  user-select: none;
}
.ww-hud__volume:hover,
.ww-hud__mute-all:hover,
.ww-hud__volume:focus-within,
.ww-hud__mute-all:focus-within {
  opacity: 1;
}
.ww-hud__volume-range {
  width: 72px;
  height: 4px;
  margin: 0;
  accent-color: var(--ww-accent, #c9a227);
  cursor: pointer;
}
.ww-hud__volume--off .ww-hud__volume-range {
  filter: grayscale(1) opacity(0.5);
}
.ww-hud__mute-all-box {
  width: 14px;
  height: 14px;
  margin: 0;
  accent-color: var(--ww-accent, #c9a227);
  cursor: pointer;
}
.ww-hud__mute-all-label {
  white-space: nowrap;
}

.ww-hud__reset-tutos {
  pointer-events: auto;
  opacity: 0.45;
  transition: opacity 160ms ease;
  font-size: 18px;
  line-height: 1;
}
.ww-hud__reset-tutos:hover {
  opacity: 1;
}
.ww-hud__reset-tutos--pulse {
  animation: ww-reset-tutos-pulse 600ms ease;
}
@keyframes ww-reset-tutos-pulse {
  0%   { transform: rotate(0deg)   scale(1);   background: var(--ww-surface-raised); }
  50%  { transform: rotate(180deg) scale(1.18); background: var(--ww-accent); }
  100% { transform: rotate(360deg) scale(1);   background: var(--ww-surface-raised); }
}

/* Tooltip — mesma geometria da carta, escalada menor. Sem anim de background. */
.ww-tooltip {
  position: absolute;
  background: var(--ww-surface-raised);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: 10px 12px;
  color: var(--ww-text-primary);
  font-family: var(--ww-font-body);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.45;
  max-width: 260px;
  pointer-events: none;
  /* A ORDEM DO Z (founder, 2026-08-05): tooltip > balões de texto > cartas
     andando > cartas paradas. Este é O TOOLTIP — fica por cima de tudo que é
     mesa. Estava em 30 e as cartas competem com o zOrder CRU (casa dos
     milhões): o tooltip nascia ENTERRADO embaixo de qualquer carta e de
     qualquer balão. A escada completa, de cima pra baixo:
       2147483300  [data-tip] (tooltip de UI, fora da mesa)
       2147483250  ESTE (tooltip de carta)
       2147483200  .ww-prop-floaty (tooltip de mudança de prop)
       2147483100  #card-speech-layer (balões de fala)
       2147483000  anéis/bolinhas de receita
       3M+ / 2M+ / 1M+  cartas: arrasto / andando / paradas (bandas do zOrder) */
  z-index: 2147483250;
}
.ww-tooltip__name {
  color: var(--ww-text-primary);
  font-family: var(--ww-font-display);
  font-size: 14px;
  font-weight: 800;
  line-height: 1.1;
  margin-bottom: 2px;
}
.ww-tooltip__meta {
  color: var(--ww-text-muted);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 5px;
}
/* Glifo de tipo dentro da linha de meta do tooltip — o mesmo desenho que a
   carta mostra na mesa. Aqui o fundo já é claro, então dispensa a pastilha:
   vai solto, na cor muted da própria linha. */
.ww-tooltip__type {
  display: inline-flex;
  flex: 0 0 auto;
}
.ww-tooltip__type .ww-card__type-icon {
  width: 13px;
  height: 13px;
  fill: currentColor;
}
/* Linha de história da identidade. Itálico e cor muted pra ler como voz do
   mundo, não como stat — o bloco de props logo abaixo é que é número. */
.ww-tooltip__story {
  color: var(--ww-text-muted);
  font-size: 11px;
  font-style: italic;
  font-weight: 600;
  line-height: 1.35;
  margin-bottom: 6px;
}
.ww-tooltip__props {
  margin: 6px 0;
}
.ww-tooltip__props .row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.ww-tooltip__props .row .k { color: var(--ww-text-muted); font-weight: 700; }
.ww-tooltip__props .row .v { color: var(--ww-text-primary); font-weight: 800; display: inline-flex; gap: 6px; align-items: baseline; }
.ww-tooltip__props .row .v .r { color: var(--ww-text-muted); font-weight: 600; font-size: 10px; opacity: 0.75; }
.ww-tooltip__props .row .v .b { color: var(--ww-text-muted); font-weight: 600; font-size: 10px; opacity: 0.75; }
.ww-tooltip__tags {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
}
.ww-tooltip__tags .tag {
  color: var(--ww-surface-raised);
  background: var(--ww-text-primary);
  font-size: 9px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 6px;
  border-radius: 3px;
}
.ww-tooltip__status {
  margin-top: 6px;
  color: var(--ww-accent);
  font-size: 11px;
  font-weight: 800;
}
.ww-tooltip__cargo {
  margin-top: 8px;
  padding-top: 6px;
  border-top: var(--ww-line-width) solid var(--ww-line);
}
.ww-tooltip__cargo .row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.ww-tooltip__cargo .row .k { color: var(--ww-text-muted); font-size: 11px; font-weight: 700; }
.ww-tooltip__cargo .row .v { color: var(--ww-text-primary); font-weight: 800; font-size: 11px; }
.ww-tooltip__section-title {
  color: var(--ww-text-muted);
  font-size: 9px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 4px;
}

/* ═══════════════════════════════════════════════════════════════════════
   Store modal — só hooks de layout/posição.
   Pintura vem das primitivas .ww-panel, .ww-button, .ww-badge, .ww-scroll,
   .ww-heading, .ww-label, .ww-body. HTML em StorePanelRenderer.js.
═══════════════════════════════════════════════════════════════════════ */

.ww-store-modal__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  z-index: 90;
  pointer-events: auto;
}
.ww-store-modal {
  position: fixed;
  inset: 0;
  z-index: 91;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
}

/* Frame = .ww-panel escalada. Override padding/radius pra encaixar header+body+footer. */
.ww-store-modal__frame {
  width: min(880px, 92vw);
  max-height: 86vh;
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.ww-store-modal__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 16px 20px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
  /* Identidade da loja: fundo tingido na cor do tipo, com textura de hatch
     sutil pra ter personalidade sem ficar só "bloco colorido". --ww-store-color
     é setada inline pelo renderer a partir do store.type.color. */
  background:
    repeating-linear-gradient(
      45deg,
      transparent 0 8px,
      rgba(255, 255, 255, 0.08) 8px 10px
    ),
    var(--ww-store-color, var(--ww-surface-raised));
}
.ww-store-modal__header-emoji {
  font-size: 48px;
  line-height: 1;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 0 var(--ww-line));
}
.ww-store-modal__title-block {
  flex: 1;
  line-height: 1.2;
}
.ww-store-modal__title {
  margin: 0;
  font-size: 26px;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.15);
}
.ww-store-modal__subtitle { margin-top: 6px; }

/* Tabs = .ww-button que ocupa largura igual. --active usa .is-active via modifier. */
.ww-store-modal__tabs {
  display: flex;
  gap: 8px;
  padding: 12px 20px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
}
.ww-store-modal__tab {
  flex: 1;
  text-align: left;
  flex-direction: column;
  gap: 2px;
  padding: 10px 14px;
}
.ww-store-modal__tab--active {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}
.ww-store-modal__tab-label {
  font-size: 14px;
  font-weight: 800;
}
.ww-store-modal__tab-sub {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  opacity: 0.75;
}

.ww-store-modal__body {
  flex: 1;
  padding: 20px;
}

.ww-store-modal__offers-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px;
}

/* Card de oferta = .ww-panel escalada. Override pra virar clicável centralizado.
   Hover é DRAMÁTICO — levanta mais, sombra cresce, rotação minúscula pra
   sinalizar "pegar pra si". Click afunda forte. Zero filter/glow. */
.ww-store-modal__card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  min-height: 180px;
  padding: 18px 12px 14px;
  cursor: pointer;
  transition: transform 0.14s cubic-bezier(.3,1.4,.5,1),
              box-shadow 0.14s ease,
              background-color 0.15s linear;
}
.ww-store-modal__card:hover:not(:disabled) {
  transform: translate(-2px, -6px) rotate(-1deg) scale(1.03);
  box-shadow: 0 12px 0 var(--ww-line);
  z-index: 2;
}
.ww-store-modal__card:active:not(:disabled) {
  transform: translate(1px, 3px) scale(0.97);
  box-shadow: 0 2px 0 var(--ww-line);
  transition-duration: 0.04s;
}
.ww-store-modal__card--poor {
  opacity: 0.4;
  cursor: not-allowed;
}
.ww-store-modal__card:disabled:hover { transform: none; box-shadow: var(--ww-shadow); }

/* Wrapper de tamanho fixo pro slot de ícone — garante grid alinhado mesmo
   quando o sprite tem aspect ratio diferente. Renderer emite UM filho:
   ou .ww-store-modal__card-icon (emoji) ou .ww-store-modal__card-icon-sprite
   (img). Em caso de img com erro, onerror troca a img pelo div emoji. */
.ww-store-modal__card-icon-wrap {
  width: 64px;
  height: 64px;
  margin: 4px auto 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ww-store-modal__card-icon {
  font-size: 42px;
  line-height: 1;
}
.ww-store-modal__card-icon-sprite {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  image-rendering: auto;
}
.ww-store-modal__card-name {
  font-size: 14px;
  font-weight: 900;
  color: var(--ww-text-primary);
  line-height: 1.2;
}
.ww-store-modal__card-desc {
  font-size: 11px;
  flex: 1;
}
.ww-store-modal__card-price {
  position: absolute;
  top: -10px;
  right: 10px;
}

.ww-store-modal__catalog {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Row de catálogo = .ww-panel--sunken escalada (visual "dentro"). */
.ww-store-modal__row {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
}
.ww-store-modal__row--poor { opacity: 0.5; }
/* Slot de ícone do catalog — mesmo princípio do offer card, tamanho menor
   pra linha densa. Renderer emite emoji OU sprite, nunca os dois. */
.ww-store-modal__row-icon-wrap {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.ww-store-modal__row-icon {
  font-size: 28px;
  line-height: 1;
}
.ww-store-modal__row-icon-sprite {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  image-rendering: auto;
}
.ww-store-modal__row-body { flex: 1; min-width: 0; }
.ww-store-modal__row-name {
  font-size: 14px;
  font-weight: 800;
  color: var(--ww-text-primary);
}
.ww-store-modal__row-desc {
  font-size: 11px;
  margin-top: 2px;
}
.ww-store-modal__row-buy { min-width: 64px; }

.ww-store-modal__empty {
  text-align: center;
  padding: 40px 0;
}

.ww-store-modal__footer {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 20px;
  border-top: var(--ww-line-width) solid var(--ww-line);
  background: var(--ww-surface-sunken);
  transition: background-color 0.18s ease;
}
.ww-store-modal__footer--flash {
  animation: ww-footer-flash 0.5s ease-out;
}
@keyframes ww-footer-flash {
  0%, 100% { background: var(--ww-surface-sunken); }
  30%      { background: var(--ww-accent); }
}
.ww-store-modal__wallet {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  position: relative;
}
.ww-store-modal__wallet strong {
  font-size: 22px;
  font-weight: 900;
  color: var(--ww-accent);
  display: inline-block;
  transform-origin: center center;
}
.ww-wallet-flyoff {
  position: absolute;
  left: 100%;
  top: 50%;
  margin-left: 10px;
  transform: translateY(-50%);
  color: var(--ww-accent);
  font-family: var(--ww-font-display);
  font-size: 24px;
  font-weight: 900;
  pointer-events: none;
  white-space: nowrap;
  animation: ww-wallet-flyoff 0.9s ease-out forwards;
  z-index: 5;
}
@keyframes ww-wallet-flyoff {
  0%   { transform: translate(0, -50%) scale(0.6); opacity: 0; }
  20%  { transform: translate(8px, -80%) scale(1.25); opacity: 1; }
  100% { transform: translate(24px, -250%) scale(1); opacity: 0; }
}

.ww-store-modal__offers-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
}
.ww-store-modal__offers-info { flex: 1; }
.ww-store-modal__offers-info-title {
  font-family: var(--ww-font-display);
  font-size: 22px;
  font-weight: 900;
  color: var(--ww-accent);
  letter-spacing: 0.5px;
  line-height: 1;
  /* Leve wiggle perpétuo pra puxar o olhar sem ser gritante. */
  animation: ww-offers-wiggle 3s ease-in-out infinite;
  transform-origin: left center;
}
.ww-store-modal__offers-info-sub {
  margin-top: 4px;
  font-family: var(--ww-font-body);
  font-size: 12px;
  font-weight: 600;
  font-style: italic;
  color: var(--ww-text-muted);
}
@keyframes ww-offers-wiggle {
  0%, 100% { transform: rotate(-1.5deg); }
  50%      { transform: rotate(1.5deg); }
}
.ww-store-modal__reroll { gap: 8px; }
.ww-store-modal__reroll-icon { font-size: 16px; }

/* Variant badge = sticker no canto da card. Cor por variante define o tom. */
.ww-store-modal__variant {
  position: absolute;
  top: -10px;
  left: 10px;
}
.ww-store-modal__variant--buff     { background: #3a8f3c; color: var(--ww-surface-raised); }
.ww-store-modal__variant--tradeoff { background: #c48a2a; color: var(--ww-text-primary); }
.ww-store-modal__variant--bargain  { background: #8f3c5a; color: var(--ww-surface-raised); }

/* Borda da card acompanha variant (fallback pra accent). */
.ww-store-modal__card--buff     { border-color: #3a8f3c; }
.ww-store-modal__card--tradeoff { border-color: #c48a2a; }
.ww-store-modal__card--bargain  { border-color: #8f3c5a; }

/* Stats inline na card de oferta. */
.ww-store-modal__card-stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 3px 6px;
  margin-top: 2px;
}
.ww-store-modal__stat {
  font-size: 10px;
  font-weight: 800;
  padding: 1px 6px;
  border-radius: 3px;
  border: 2px solid var(--ww-line);
  line-height: 1.3;
}
.ww-store-modal__stat--up   { background: #3a8f3c; color: var(--ww-surface-raised); }
.ww-store-modal__stat--down { background: var(--ww-accent); color: var(--ww-surface-raised); }

/* Tooltip do hover interno — compõe .ww-panel. */
.ww-store-tooltip {
  position: fixed;
  z-index: 95;
  pointer-events: none;
  max-width: 280px;
  font-size: 12px;
}
.ww-store-tooltip__header {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
}
.ww-store-tooltip__icon-wrap {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.ww-store-tooltip__emoji {
  font-size: 28px;
  line-height: 1;
}
.ww-store-tooltip__emoji-sprite {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  image-rendering: auto;
}
.ww-store-tooltip__name {
  font-size: 14px;
  font-weight: 900;
  color: var(--ww-text-primary);
  line-height: 1.2;
}
.ww-store-tooltip__type {
  font-size: 10px;
  font-weight: 700;
  color: var(--ww-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-top: 2px;
}
.ww-store-tooltip__desc {
  color: var(--ww-text-primary);
  font-size: 12px;
  line-height: 1.45;
  margin-bottom: 8px;
}
.ww-store-tooltip__stats {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 6px;
  border-top: var(--ww-line-width) solid var(--ww-line);
}
.ww-store-tooltip__stat {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  color: var(--ww-text-primary);
  font-weight: 700;
}
.ww-store-tooltip__stat--up   { color: #2a8f2a; }
.ww-store-tooltip__stat--down { color: var(--ww-accent); }
.ww-store-tooltip__delta {
  color: var(--ww-text-muted);
  font-size: 10px;
  font-weight: 600;
}
.ww-store-tooltip__variant {
  margin-top: 8px;
  padding: 6px 8px;
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 2px);
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  letter-spacing: 0.4px;
}
.ww-store-tooltip__variant--buff     { background: #3a8f3c; color: var(--ww-surface-raised); }
.ww-store-tooltip__variant--tradeoff { background: #c48a2a; color: var(--ww-text-primary); }
.ww-store-tooltip__variant--bargain  { background: #8f3c5a; color: var(--ww-surface-raised); }
.ww-store-tooltip__hint {
  margin-top: 8px;
  color: var(--ww-text-muted);
  font-size: 11px;
  font-weight: 700;
  text-align: center;
}

/* ---------- Feedback de compra (CSS puro) ---------- */

/* Pulse no botão clicado. Finito (600ms), só scale — composited. */
.ww-store-modal__card,
.ww-store-modal__row-buy,
.ww-store-modal__reroll {
  position: relative;
}
.ww-buy-pulse {
  animation: ww-buy-pulse 0.55s cubic-bezier(.3,1.8,.6,1);
}
@keyframes ww-buy-pulse {
  0%   { transform: scale(1) rotate(0deg); }
  20%  { transform: scale(1.18) rotate(-2deg); }
  50%  { transform: scale(0.92) rotate(1deg); }
  75%  { transform: scale(1.05) rotate(-0.5deg); }
  100% { transform: scale(1) rotate(0deg); }
}

/* "-$X" que sobe do centro do botão. Transform+opacity só. */
.ww-buy-flyoff {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  color: var(--ww-accent);
  font-weight: 900;
  font-size: 22px;
  font-family: var(--ww-font-body);
  white-space: nowrap;
  animation: ww-buy-flyoff 0.9s ease-out forwards;
  z-index: 10;
}
@keyframes ww-buy-flyoff {
  0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 0; }
  20%  { transform: translate(-50%, -80%) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -220%) scale(1); opacity: 0; }
}

/* Carta recém-comprada no mundo: shake curto sobrescrevendo idle-breath. */
.ww-card--just-purchased .ww-card__inner {
  animation: ww-just-purchased-shake 0.5s ease-in-out !important;
}
@keyframes ww-just-purchased-shake {
  0%, 100% { transform: translateX(0); }
  15%      { transform: translateX(-4px) rotate(-1deg); }
  30%      { transform: translateX(4px)  rotate(1deg); }
  45%      { transform: translateX(-3px) rotate(-0.8deg); }
  60%      { transform: translateX(3px)  rotate(0.8deg); }
  75%      { transform: translateX(-2px); }
  90%      { transform: translateX(2px); }
}

/* ═══════════════════════════════════════════════════════════════════════
   Game over — layout; pintura pelas primitivas.
═══════════════════════════════════════════════════════════════════════ */

.ww-gameover {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  font-family: var(--ww-font-body);
  pointer-events: auto;
  animation: ww-gameover-fade 0.5s ease-out;
}
@keyframes ww-gameover-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.ww-gameover__inner {
  max-width: 520px;
  width: 92%;
  max-height: 88vh;
  padding: 32px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  animation: ww-gameover-slide 0.45s ease-out 0.1s both;
  /* ww-scroll liga overflow:auto nos dois eixos; aqui só queremos rolagem
     vertical — qualquer valor longo (título com letter-spacing, stat row)
     não pode forçar scrollbar horizontal. */
  overflow-x: hidden;
  box-sizing: border-box;
}
.ww-gameover__inner > * { max-width: 100%; }
.ww-gameover__stat > span { min-width: 0; overflow-wrap: anywhere; }
@keyframes ww-gameover-slide {
  from { transform: translateY(20px) scale(0.95); opacity: 0; }
  to   { transform: translateY(0) scale(1);       opacity: 1; }
}
.ww-gameover__icon {
  font-size: 56px;
  line-height: 1;
}
.ww-gameover__title {
  margin: 0;
  color: var(--ww-accent);
  font-size: 34px;
  letter-spacing: 0.5px;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.ww-gameover__subtitle {
  margin: 0;
  font-size: 14px;
}
.ww-gameover__epitaph {
  margin: 0 0 8px 0;
  font-style: italic;
  font-size: 13px;
  color: var(--ww-text-muted);
}
.ww-gameover__stats {
  width: 100%;
  padding: 14px 16px;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ww-gameover__stats-title {
  text-align: center;
  margin-bottom: 6px;
}
.ww-gameover__stat {
  display: flex;
  justify-content: space-between;
  padding: 4px 0;
  font-size: 13px;
  font-weight: 700;
}
.ww-gameover__stat.emphasis {
  font-size: 15px;
  font-weight: 900;
  color: var(--ww-accent);
  border-top: var(--ww-line-width) solid var(--ww-line);
  margin-top: 6px;
  padding-top: 8px;
}
.ww-gameover__btn {
  margin-top: 8px;
  font-size: 15px;
  padding: 12px 32px;
}

/* A frase que resume a run: nome da aldeia e quanto ela durou. Fica entre o
   título e o subtítulo, e é o que o jogador lê primeiro depois do impacto. */
.ww-gameover__headline {
  margin: 0;
  font-size: 17px;
  overflow-wrap: anywhere;
}

/* A chamada dos nomes. Nome à esquerda, o que aconteceu à direita — a mesma
   geometria da stat row, porque é a mesma leitura: rótulo e valor. */
.ww-gameover__roll-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 4px 0;
  border-bottom: 1px dashed rgba(17, 17, 17, 0.18);
}
.ww-gameover__roll-line:last-child { border-bottom: none; }
.ww-gameover__roll-name {
  font-size: 14px;
  font-weight: 900;
  min-width: 0;
  overflow-wrap: anywhere;
}
.ww-gameover__roll-detail {
  font-size: 12px;
  font-weight: 700;
  color: var(--ww-text-muted);
  text-align: right;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* ═══════════════════════════════════════════════════════════════════════
   Rank notifications — layout; pintura pelas primitivas.
═══════════════════════════════════════════════════════════════════════ */

.ww-rank-toasts {
  position: fixed;
  top: 80px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  z-index: 80;
  pointer-events: none;
  font-family: var(--ww-font-body);
}
/* Toast = .ww-panel pill-shaped. Só opacity+transform na transição. */
.ww-rank-toast {
  padding: 10px 18px;
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.5px;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.ww-rank-toast.big {
  font-size: 15px;
  padding: 14px 26px;
}
.ww-rank-toast.show {
  opacity: 1;
  transform: translateY(0);
}

/* Trophy popup = .ww-panel--accent escalado. */
.ww-rank-trophy {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 20px;
  opacity: 0;
  transform: translateY(-16px) scale(0.9);
  transition: opacity 0.4s ease, transform 0.4s cubic-bezier(.34,1.56,.64,1);
}
.ww-rank-trophy.show {
  opacity: 1;
  transform: translateY(0) scale(1);
}
.ww-rank-trophy__icon {
  font-size: 44px;
  line-height: 1;
  animation: ww-trophy-bob 1.4s ease-in-out infinite;
}
@keyframes ww-trophy-bob {
  0%, 100% { transform: translateY(0) rotate(-4deg); }
  50%      { transform: translateY(-4px) rotate(4deg); }
}
.ww-rank-trophy__title {
  margin: 0;
  color: var(--ww-surface-raised);
  letter-spacing: 1.5px;
}
.ww-rank-trophy__sub {
  margin-top: 4px;
  color: var(--ww-surface-raised);
  opacity: 0.85;
}

.ww-confetti {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 75;
}
.ww-confetti__piece {
  position: absolute;
  top: -20px;
  width: 10px;
  height: 14px;
  border-radius: 2px;
  animation: ww-confetti-fall var(--dur, 2s) var(--delay, 0s) ease-in forwards;
}
@keyframes ww-confetti-fall {
  0% {
    transform: translate(0, 0) rotate(0);
    opacity: 1;
  }
  80% {
    opacity: 1;
  }
  100% {
    transform: translate(var(--dx, 0), 115vh) rotate(var(--rot, 360deg));
    opacity: 0;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Pause + confirm — layout; pintura pelas primitivas.
   REGRA CRÍTICA: .ww-pause (o VÉU) tem pointer-events:none e z-index:15
   (abaixo do HUD z:20) — é o que mantém os botões de speed clicáveis com a
   pausa aberta. O PAINEL (.ww-pause__inner) reativa pointer-events por
   inteiro: botão novo ali dentro já nasce clicável. A versão anterior exigia
   reativar botão a botão, e o segundo botão esquecido ("Menu principal",
   2026-08-19) nasceu morto na tela — clique passava reto por ele.
═══════════════════════════════════════════════════════════════════════ */

.ww-pause {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 15;
  pointer-events: none;
  font-family: var(--ww-font-body);
  animation: ww-pause-in 0.2s ease-out;
}

/* Quando a mesa está encoberta (pause/loja), painéis do HUD ganham destaque
   — escala levemente maior + sombra mais chapada — pra subirem como foco
   visual. Dinheiro e relógio ficam naturalmente à frente ao invés de se
   perderem atrás do overlay cinza. */
body.is-pause-open .ww-hud__panel,
body.is-pause-open .ww-hud__time-panel,
body.is-pause-open .ww-hud__money,
body.is-pause-open .ww-hud__bill,
body.is-pause-open .ww-hud__debt {
  transform: scale(1.05);
  box-shadow: 0 6px 0 var(--ww-line);
  transition: transform 0.18s ease-out, box-shadow 0.18s ease-out;
}
@keyframes ww-pause-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.ww-pause__inner {
  text-align: center;
  padding: 24px 32px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  /* O painel inteiro é clicável (o none é só do véu) — ver a REGRA CRÍTICA. */
  pointer-events: auto;
}
.ww-pause__icon {
  font-size: 56px;
  line-height: 1;
  margin-bottom: 4px;
}
.ww-pause__title {
  margin: 0;
  letter-spacing: 4px;
}
.ww-pause__hint {
  margin: 0;
  font-size: 12px;
  letter-spacing: 0.5px;
}
.ww-pause__end-run {
  pointer-events: auto;
  margin-top: 12px;
}
/* O caminho pro painel de acessibilidade no meio do jogo (founder,
   2026-08-18) — antes só existia o F1, que ninguém descobre. */
.ww-pause__a11y {
  pointer-events: auto;
  margin-top: 12px;
}
/* Bloco de idioma na pausa: rótulo em cima, segmento embaixo. */
.ww-pause__locale {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
  pointer-events: auto;
}

.ww-pause-confirm {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 20;
  pointer-events: auto;
  font-family: var(--ww-font-body);
  animation: ww-pause-in 0.15s ease-out;
}
/* Modifier pra casos em que o confirm precisa sobrepor o main menu (z 200). */
.ww-pause-confirm--over-mainmenu {
  z-index: 210;
}
.ww-pause-confirm__inner {
  max-width: 420px;
  padding: 24px 28px;
}
.ww-pause-confirm__title {
  margin: 0 0 10px 0;
}
.ww-pause-confirm__body {
  margin: 0 0 20px 0;
}
.ww-pause-confirm__actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}

/* ═══════════════════════════════════════════════════════════════════════
   Main menu — layout; pintura vem das primitivas.
═══════════════════════════════════════════════════════════════════════ */

.ww-mainmenu {
  position: fixed;
  inset: 0;
  /* Transparente: o background (mesa xadrez + campo de árvores) é renderizado
     pela MenuBackgroundScene no canvas Phaser ATRÁS deste DOM. Pintar aqui
     tampa as árvores. body já tem var(--ww-stage-bg) como fallback caso o
     canvas falhe. */
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  font-family: var(--ww-font-body);
  pointer-events: auto;
  padding: 24px;
  overflow: hidden;
}
.ww-mainmenu__inner {
  display: grid;
  grid-template-columns: minmax(360px, 1fr) minmax(280px, 360px);
  gap: 20px;
  max-width: 920px;
  width: 100%;
  max-height: 100%;
  min-height: 0;
}
.ww-mainmenu__fixed {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 28px 32px;
  text-align: left;
  overflow: auto;
}
.ww-mainmenu__title {
  margin: 0;
  color: var(--ww-accent);
}
/* Variante PNG do título — usa a arte da carta `title-1` quando o
   SpriteManifest tem o sprite. Largura ocupa o painel inteiro,
   height auto preserva a proporção original da arte. image-rendering
   smooth porque a arte é raster sendo downscaled. */
.ww-mainmenu__title-img {
  display: block;
  width: 100%;
  height: auto;
  margin: 0 0 4px;
  user-select: none;
  -webkit-user-drag: none;
}
.ww-mainmenu__subtitle {
  margin: 0 0 4px;
}

/* Intro = painel sunken dentro do frame. */
.ww-mainmenu__intro {
  background: var(--ww-surface-sunken);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: var(--ww-radius);
  padding: 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ww-mainmenu__intro p { margin: 0; }
.ww-mainmenu__intro strong { font-weight: 900; }

.ww-mainmenu__continue {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ww-mainmenu__separator {
  text-align: center;
  margin: 4px 0;
  opacity: 0.8;
}

.ww-mainmenu__name-row {
  display: flex;
  gap: 8px;
}
.ww-mainmenu__name-input {
  flex: 1;
  font-size: 15px;
}

/* Painel direito: agrupa controles (continue/nome/new run) e leaderboard
   compacto na base. Antes era só leaderboard, mas a home foi reorganizada
   pra deixar o painel esquerdo livre pro título PNG + intro. */
.ww-mainmenu__side {
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}
.ww-mainmenu__leaderboard-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
  margin-top: 6px;
}
.ww-mainmenu__leaderboard-title {
  margin: 0;
  text-align: center;
}
.ww-mainmenu__local {
  display: inline-block;
  margin-left: 6px;
  vertical-align: middle;
}
/* Selo de build: atravessa as duas colunas do grid do menu e fica discreto no
   rodapé. Serve pra conferir qual bundle está no ar. É um botão (o hover/foco
   abre o changelog), então o cru do botão nativo sai aqui. */
.ww-mainmenu__build {
  text-align: center;
  opacity: 0.45;
  padding-top: 2px;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
}
.ww-mainmenu__build:hover,
.ww-mainmenu__build:focus-visible {
  opacity: 0.9;
}
/* O changelog embaixo do selo: painel ancorado que abre pra CIMA do rodapé.
   Discreto fechado, legível aberto — a lista rola quando crescer. */
.ww-mainmenu__build-wrap {
  position: relative;
  display: inline-block;
}
.ww-mainmenu__changelog {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
}
/* `display: flex` na classe ATROPELA o `hidden` do UA stylesheet — sem esta
   linha o painel nasce aberto e nenhum toggle fecha (bug real, 2026-08-05). */
.ww-mainmenu__changelog[hidden] {
  display: none;
}
.ww-mainmenu__changelog {
  width: min(420px, 82vw);
  max-height: 300px;
  display: flex;
  flex-direction: column;
  padding: 12px 14px;
  z-index: 40;
  text-align: left;
}
.ww-mainmenu__changelog-title {
  margin-bottom: 6px;
}
.ww-mainmenu__changelog-scroll {
  overflow-y: auto;
  min-height: 0;
}
.ww-mainmenu__changelog-entry + .ww-mainmenu__changelog-entry {
  margin-top: 10px;
}
.ww-mainmenu__changelog-version {
  opacity: 0.7;
  margin-bottom: 3px;
}
.ww-mainmenu__changelog-list {
  margin: 0;
  padding-left: 18px;
}
.ww-mainmenu__changelog-item {
  margin: 2px 0;
}
/* Rodapé do menu: seletor de língua à esquerda do selo de build, os dois na
   mesma faixa que atravessa as duas colunas do grid. */
.ww-mainmenu__footer {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding-top: 2px;
  pointer-events: auto;
}
/* O botão de download é um <a> vestido de .ww-button (é download de verdade,
   não ação de página) — o hook só mata o traje de link do navegador. */
.ww-mainmenu__download {
  text-decoration: none;
}
.ww-mainmenu__leaderboard-scroll {
  flex: 1 1 auto;
  min-height: 0;
  padding-right: 4px;
}
.ww-mainmenu__empty {
  text-align: center;
  padding: 14px 0;
}
.ww-mainmenu__leaderboard-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ww-mainmenu__leaderboard-row {
  display: grid;
  grid-template-columns: 36px 1fr auto auto;
  gap: 10px;
  padding: 8px 10px;
  align-items: center;
  cursor: help;
  background: var(--ww-surface-sunken);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 2px);
  font-size: 13px;
  font-weight: 700;
  color: var(--ww-text-primary);
}
.ww-mainmenu__leaderboard-row:hover {
  background: var(--ww-surface-raised);
}
.ww-mainmenu__rank {
  color: var(--ww-text-muted);
  font-weight: 900;
}
.ww-mainmenu__farmer {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-mainmenu__score {
  color: var(--ww-accent);
  font-weight: 900;
}
.ww-mainmenu__days {
  color: var(--ww-text-muted);
  font-size: 11px;
}

/* Cenários de validação — lista FIXA no pé do painel esquerdo (2026-08-17).
   A primeira versão era popup no rodapé e foi vetada pelo founder no dia.
   Parecer da Leah (UX): mesma superfície do painel, separada por borda,
   hierarquia menor que o texto de abertura; linha no MESMO molde das linhas
   do ranking (sunken + borda + radius menor), legenda curta inline. A seção
   inteira só renderiza fora de produção (ou ?dev=1) — ver MainMenuRenderer. */
.ww-mainmenu__playgrounds {
  margin-top: auto;
  padding-top: 12px;
  border-top: var(--ww-line-width) solid var(--ww-line);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ww-mainmenu__playgrounds-label {
  opacity: 0.75;
}
.ww-mainmenu__playground-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ww-mainmenu__playground-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: var(--ww-surface-sunken);
  border: var(--ww-line-width) solid var(--ww-line);
  border-radius: calc(var(--ww-radius) - 2px);
  color: var(--ww-text-primary);
  font: inherit;
  font-size: 12.5px;
  font-weight: 800;
  text-align: left;
  cursor: pointer;
  min-width: 0;
}
.ww-mainmenu__playground-row:hover,
.ww-mainmenu__playground-row:focus-visible {
  background: var(--ww-surface-raised);
  outline: none;
}
.ww-mainmenu__playground-icon {
  flex: none;
  width: 20px;
  text-align: center;
}
.ww-mainmenu__playground-name {
  white-space: nowrap;
}
.ww-mainmenu__playground-hint {
  margin-left: auto;
  padding-left: 10px;
  font-size: 11px;
  font-weight: 700;
  opacity: 0.65;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Botões do menu têm layout vertical (title + meta). Compostos sobre .ww-button. */
.ww-mainmenu__btn {
  flex-direction: column;
  gap: 2px;
  padding: 14px 20px;
}
.ww-mainmenu__btn-title {
  font-size: 16px;
  font-weight: 900;
}
.ww-mainmenu__btn-meta {
  font-size: 11px;
  font-weight: 700;
  opacity: 0.7;
}

/* Responsive: em tela estreita, leaderboard vai embaixo. */
@media (max-width: 820px) {
  .ww-mainmenu__inner {
    grid-template-columns: 1fr;
    max-width: 540px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Weather overlay — DESATIVADO VISUALMENTE (ficou feio).
   ───────────────────────────────────────────────────────────────────────
   O sistema (WeatherManager em core/weather.js, evento WEATHER_CHANGED,
   ctx.weather/ctx.weatherIntensity) continua rodando e ativo — só a
   camada visual está suprimida. Pra reabilitar, descomentar o bloco
   abaixo e reiterar no visual (possivelmente trocar a abordagem de
   textura de ruído tiled + mix-blend por algo mais atmosférico).
═══════════════════════════════════════════════════════════════════════ */

.ww-weather { display: none; }

/* ═══════════════════════════════════════════════════════════════════════
   A CAMADA DE CÉU — vinheta + a cor da hora
   ───────────────────────────────────────────────────────────────────────
   Direção do founder, 2026-08-06: *"um efeito que simulasse a cor do céu
   naquela hora (…) à noite a gente coloca ele roxo, aquele efeito que
   aplica cor em tudo, bem sutil. Uma camada que fica em cima de tudo e dá
   cor à coisa. A parte que controla o tempo afeta esse cara."*

   Proposta, pesquisa de licença e diagnóstico completos:
   `docs/design/sky-layer/README.md`. A curva é `render/sky-curve.js`;
   quem liga ao relógio é `renderers/SkyLayerRenderer.js`.
   Prova: `node scripts/f15-sky-curve.mjs`.

   ── Por que ela ocupa o lugar do weather morto ────────────────────────
   O bloco acima é a tentativa ANTERIOR desta mesma ideia, desativada por
   ficar feia. O diagnóstico do que deu errado, e que esta camada corrige:

     • usava `multiply` a até 85% — só ESCURECE. O resultado não era
       neblina, era sujeira cinza cobrindo o jogo.
     • ruído tileado de 75px como camada principal, em deriva lenta — o
       olho segue o movimento e lê PADRÃO, não grão.
     • cor chapada e uniforme na tela inteira.

   O erro é o mesmo nos três: **a atmosfera foi feita de textura, não de
   luz.** Aqui a tinta usa `mix-blend-mode: color`, que impõe o MATIZ e
   PRESERVA a luminosidade — o verde da mesa vira roxo à noite com o mesmo
   brilho, então nada deixa de ser legível.

   O sistema de clima (`core/weather.js`) continua vivo e intocado; só a
   pintura dele segue desligada.
═══════════════════════════════════════════════════════════════════════ */

#sky-layer {
  /* ═══ OS VALORES QUE O FOUNDER APROVOU NA BANCADA (2026-08-06) ═══════
     Ele ajustou na bancada, achou o ponto, e estes são os números do
     veredito dele. NÃO são chute de implementação — a primeira versão
     desta camada subiu com os defaults da proposta (miolo 38%, força ~0.95
     por hora, vinheta 1) e o resultado foi recusado na tela: *"você fez um
     efeito que é horrível, uma versão horrível do que a gente tinha
     provado"*. Estava certo: aqueles números eram meus, não dele.

     Mudar qualquer um destes é mudar o veredito do founder — não se faz
     sem ele. Ficam AQUI, num lugar só, porque antes viviam como fallback
     repetido em 18 `var()` diferentes: divergir era questão de tempo. */

  /* O miolo limpo. A cor só começa daqui pra fora, e é o que garante que o
     centro — onde o jogo acontece — nunca é pintado. */
  --ceu-miolo: 27%;

  /* A vinheta quase zerada: um fio de escuro nas bordas, e só. Quem dá o
     clima é a COR, não o escurecimento — era a crítica original (*"isso é um
     tampão da visão, não um efeito"*), e ele resolveu no controle mantendo
     este valor no piso. A pulsação segue ligada e respira sobre ela. */
  --ceu-vinheta: 0.05;

  /* O grão, no valor que ele deixou — dither contra o banding, abaixo do
     limiar em que vira textura perceptível. */
  --ceu-grao: 0.035;

  position: fixed;
  inset: 0;
  /* Acima das cartas e dos balões do mundo, ABAIXO do HUD (20) e de tudo
     que é UI: o céu tinge o JOGO, nunca a interface. */
  z-index: 12;
  pointer-events: none;
  /* O blend dos filhos para AQUI. Sem isto ele vaza pro fundo do body e
     pinta coisa que não é mesa. */
  isolation: isolate;
}

.ww-sky__layer {
  position: absolute;
  inset: 0;
  will-change: opacity;
}

/* ── A LEI DO MIOLO ────────────────────────────────────────────────────
   Direção do founder: *"a área do meio da tela tem que ficar bastante
   visível (…) isso aí é um efeito, não é um tampão da visão."*

   Nenhuma camada pinta no centro: todas são recortadas pela mesma máscara,
   e até `--ceu-miolo` o valor é literalmente zero.

   E a rampa ACELERA. Com duas paradas ela sobe quase reto, e o resultado
   foi o segundo veredito do founder: *"parece chapado com um buraco no
   meio"*, com a borda quase invisível. Cinco paradas fazem a cor MORAR na
   borda — que é o que dá sensação de moldura em vez de véu.

   As paradas ancoram em `--ceu-miolo` por `calc`, então mexer o raio
   protegido move a rampa inteira junto, sem descolar. */
.ww-sky__tint,
.ww-sky__tint2 {
  background: radial-gradient(
    ellipse 130% 100% at 50% 118%,
    var(--ceu-horizonte, #2e1c52) 0%,
    var(--ceu-zenite, #160c2e) 72%
  );
  /* O blend que TINGE sem escurecer. É a virada desta camada — a versão
     anterior usava soft-light/multiply e o founder cortou: *"não dá pra
     ler, isso é um tampão da visão, não um efeito"*. */
  mix-blend-mode: color;
  -webkit-mask-image: radial-gradient(
    ellipse 76% 84% at 50% 50%,
    transparent 0%,
    transparent var(--ceu-miolo, 38%),
    rgba(0,0,0,.08) calc(var(--ceu-miolo, 38%) + 14%),
    rgba(0,0,0,.30) calc(var(--ceu-miolo, 38%) + 30%),
    rgba(0,0,0,.66) calc(var(--ceu-miolo, 38%) + 46%),
    black 100%
  );
  mask-image: radial-gradient(
    ellipse 76% 84% at 50% 50%,
    transparent 0%,
    transparent var(--ceu-miolo, 38%),
    rgba(0,0,0,.08) calc(var(--ceu-miolo, 38%) + 14%),
    rgba(0,0,0,.30) calc(var(--ceu-miolo, 38%) + 30%),
    rgba(0,0,0,.66) calc(var(--ceu-miolo, 38%) + 46%),
    black 100%
  );
}
.ww-sky__tint  { opacity: var(--ceu-tinta-op, 0); }

/* O SEGUNDO PASSE — o que dá alcance à força.
   `opacity` satura em 1: chegando lá, aumentar não mudava mais nada, e foi
   o relato do founder (*"está quase invisível não importa o quanto eu
   aumente"*). Um segundo elemento com o MESMO blend compõe por cima, então
   a força passa de 1 e continua valendo. */
.ww-sky__tint2 { opacity: var(--ceu-tinta2-op, 0); }

/* Um fio de escuro só nas horas frias, pra noite não ficar apenas "roxa e
   clara". Teto baixo de propósito: é tempero, não cortina. */
.ww-sky__shade {
  background: var(--ceu-zenite, #160c2e);
  mix-blend-mode: multiply;
  opacity: var(--ceu-sombra-op, 0);
  -webkit-mask-image: radial-gradient(
    ellipse 76% 84% at 50% 50%,
    transparent 0%,
    transparent var(--ceu-miolo, 38%),
    rgba(0,0,0,.10) calc(var(--ceu-miolo, 38%) + 20%),
    rgba(0,0,0,.44) calc(var(--ceu-miolo, 38%) + 40%),
    black 100%
  );
  mask-image: radial-gradient(
    ellipse 76% 84% at 50% 50%,
    transparent 0%,
    transparent var(--ceu-miolo, 38%),
    rgba(0,0,0,.10) calc(var(--ceu-miolo, 38%) + 20%),
    rgba(0,0,0,.44) calc(var(--ceu-miolo, 38%) + 40%),
    black 100%
  );
}

/* ── A VINHETA ─────────────────────────────────────────────────────────
   Gradiente procedural e NÃO imagem: acompanha qualquer proporção de tela
   (16:9, 21:9, retrato no celular) sem deformar em elipse errada e sem
   banding de ampliação. Nenhuma PNG cumpre isso — o levantamento está no
   README §1.

   Cinco paradas e não duas: com duas, a transição forma um ANEL visível,
   que é o erro clássico da vinheta amadora. */
.ww-sky__vignette {
  background: radial-gradient(
    ellipse 76% 84% at 50% 50%,
    transparent 0%,
    transparent var(--ceu-miolo, 38%),
    rgba(0,0,0,.05) calc(var(--ceu-miolo, 38%) + 16%),
    rgba(0,0,0,.20) calc(var(--ceu-miolo, 38%) + 32%),
    rgba(0,0,0,.46) calc(var(--ceu-miolo, 38%) + 48%),
    rgba(0,0,0,.72) 100%
  );
  opacity: var(--ceu-vinheta, 1);
}

/* ── O GRÃO — não é enfeite ────────────────────────────────────────────
   Um gradiente de 8 bits esticado numa tela grande FAZ BANDING: faixas
   concêntricas visíveis nas transições suaves. É inevitável na matemática
   do gradiente, e o grão tileado é o dither que quebra isso.

   4% é o teto; acima de ~6% deixa de ser dither e vira textura
   perceptível — que é exatamente o erro do weather antigo. E PARADO de
   propósito: grão que se move vira padrão que o olho segue.

   Screaming Brain Studios, CC0 (domínio público, uso comercial livre, sem
   atribuição exigida) — licença verificada no License.txt do pacote, não
   presumida da página. */
.ww-sky__grain {
  background-image: url('../../public/textures/grain-128.png');
  background-repeat: repeat;
  background-size: 128px 128px;
  mix-blend-mode: overlay;
  opacity: var(--ceu-grao, .04);
}

/* ── A PULSAÇÃO ────────────────────────────────────────────────────────
   Ciclo longo e amplitude minúscula: **o efeito tem que ser SENTIDO,
   nunca notado.** Se o olho percebe a pulsação, ela está forte demais.

   Anima `transform: scale`, não `background`: escala é composta na GPU;
   redesenhar gradiente por quadro não é. Custo real ≈ zero.

   (E note que aqui a transição É bem-vinda, ao contrário do que vale pra
   alça da pilha: isto não acompanha o ponteiro. Ver `f12`.) */
@keyframes ww-sky-breathe {
  0%, 100% { transform: scale(1);     opacity: var(--ceu-vinheta, 1); }
  50%      { transform: scale(1.035); opacity: calc(var(--ceu-vinheta, 1) * .88); }
}
#sky-layer[data-pulse="on"] .ww-sky__vignette {
  animation: ww-sky-breathe 13s ease-in-out infinite;
}

/* Pulsação de tela inteira é gatilho conhecido de sensibilidade
   vestibular. Quem pediu menos movimento, recebe menos movimento. */
@media (prefers-reduced-motion: reduce) {
  #sky-layer[data-pulse="on"] .ww-sky__vignette { animation: none; }
}

/* ── O PAINEL DE AJUSTE DO CÉU (`/?sky=1`) ─────────────────────────────────
   Ferramenta, não UI de jogo: só existe com o parâmetro na URL, e por isso
   pode ser direto no visual. z acima de tudo (é ferramenta), e do lado
   direito pra não cobrir o menu nem a mão de cartas.

   Ele escreve nas MESMAS vars que o renderer — o que se ajusta aqui é
   literalmente o que o jogo faz. Ver `fx/SkyTuner.js`. */
.ww-sky-tuner {
  position: fixed;
  top: 12px;
  right: 12px;
  width: 250px;
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  z-index: 600;
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(18, 18, 22, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #e8e8ea;
  font-family: var(--ww-font-body, system-ui, sans-serif);
  font-size: 12px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}
.ww-sky-tuner__titulo {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-size: 10px;
  opacity: 0.65;
  margin-bottom: 10px;
}
.ww-sky-tuner__linha { display: block; margin-bottom: 12px; }
.ww-sky-tuner__rotulo {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 4px;
}
.ww-sky-tuner__rotulo b { font-variant-numeric: tabular-nums; opacity: 0.9; }
.ww-sky-tuner input[type="range"] { width: 100%; accent-color: #c9a227; }
.ww-sky-tuner__pulso,
.ww-sky-tuner__copiar {
  width: 100%;
  margin-top: 6px;
  padding: 8px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.06);
  color: inherit;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.ww-sky-tuner__copiar { background: #8a6d1f; border-color: #c9a227; margin-top: 10px; }
.ww-sky-tuner__pulso:hover,
.ww-sky-tuner__copiar:hover { filter: brightness(1.15); }
.ww-sky-tuner__saida {
  width: 100%;
  margin-top: 8px;
  font-family: ui-monospace, monospace;
  font-size: 10px;
  background: rgba(0, 0, 0, 0.5);
  color: #d8d8dc;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 6px;
  padding: 6px;
}

/* AS FOLHAS AO VENTO — os três canvases do 3D falso (LeavesLayerRenderer).
   Fixos na viewport (as folhas voam por cima da mesa, não coladas nela),
   sem toque, e na escada do z entre os anéis de receita (…3000) e os balões
   de fala (…3100): folha desfocada passa por cima de carta, nunca por cima
   do que o jogador está lendo. O blur por camada é inline (muda com o
   preset); aqui mora só o que não muda. */
.ww-leaf-layer {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 2147483050;
}

/* O ajuste da luz coada (`/?gobo=1`) herda a pintura do painel do céu; se os
   dois abrirem juntos, este fica à esquerda. A grade é o seletor de gobos. */
.ww-gobo-tuner { right: auto; left: 12px; }
.ww-gobo-tuner__grade {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 4px;
  margin-bottom: 12px;
}
.ww-gobo-tuner__gobo {
  padding: 5px 2px;
  font-size: 10px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.06);
  color: #e8e8ea;
  cursor: pointer;
}
.ww-gobo-tuner__gobo:hover { background: rgba(255, 255, 255, 0.14); }
.ww-gobo-tuner__gobo[data-on] {
  background: rgba(255, 200, 80, 0.25);
  border-color: rgba(255, 200, 80, 0.7);
  font-weight: 700;
}

/*
.ww-weather {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 12;
  background-image: url('../../public/textures/noise-75.png');
  background-repeat: repeat;
  background-size: 75px 75px;
  opacity: calc(var(--ww-weather-intensity, 0) * var(--ww-weather-max, 0));
  transition: opacity 0.8s ease;
  mix-blend-mode: multiply;
  will-change: background-position, opacity;
}
@keyframes ww-weather-drift {
  from { background-position: 0 0; }
  to   { background-position: 300px 120px; }
}
.ww-weather--haze {
  --ww-weather-max: 0.45;
  background-color: rgba(230, 180, 90, 0.3);
  animation: ww-weather-drift 60s linear infinite;
}
.ww-weather--fog-light {
  --ww-weather-max: 0.55;
  background-color: rgba(220, 220, 230, 0.55);
  animation: ww-weather-drift 45s linear infinite;
}
.ww-weather--fog-dense {
  --ww-weather-max: 0.85;
  background-color: rgba(180, 190, 205, 0.75);
  animation: ww-weather-drift 30s linear infinite;
  filter: blur(1.5px);
}
.ww-weather--dust {
  --ww-weather-max: 0.55;
  background-color: rgba(200, 150, 80, 0.55);
  animation: ww-weather-drift 18s linear infinite;
}
*/

/* A luz coada (dappled light) virou textura no chão do mundo Phaser —
   TileSprite no worldLayer, ver DappledLightRenderer. Nada dela vive em CSS. */

/* ═══════════════════════════════════════════════════════════════════════
   GUIDE — Oto, personagem do canto inferior direito
   Fora do mundo: position fixed, sem pan/zoom.
═══════════════════════════════════════════════════════════════════════ */
.ww-guide {
  position: fixed;
  /* Posição vem do JS (left/top + transform translate(-50%,-50%) pra centralizar).
     CSS legado de right/bottom removido — tudo passa pelo pose dinâmico agora.
     Sem transition aqui: pose muda discretamente entre steps do act, e o tilt
     do mouse-proximity precisa ser instantâneo a cada frame (já é interpolado
     em JS via TILT_LERP). Transições suaves entre poses ficam pra Fase 2. */
  left: 0;
  top: 0;
  pointer-events: none;
  z-index: 50;
  font-family: var(--ww-font-body);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  will-change: transform;
}
.ww-guide__figure {
  /* Width/height definidos via JS de acordo com o aspect-ratio do FRAME
     (não da sheet inteira). Background-image + background-position fazem
     o "recorte" do frame ativo — ver GuideRenderer._mountFigure.
     Transform aqui (scale/rotation do pose) é animado via inline transition
     setado pelo JS — não definir transition aqui pra não conflitar. */
  pointer-events: auto;
  position: relative;
  transform-origin: 50% 100%;   /* base do personagem — escala pra cima sem afundar */
  will-change: transform;
}
/* Fallback sem sprite: emoji solto, sem moldura. Oto não é carta. */
.ww-guide__emoji {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  font-size: 96px;
  line-height: 1;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}
.ww-guide__bubble {
  position: absolute;
  right: 110px;
  bottom: 110px;
  max-width: 260px;
  min-width: 120px;
  padding: 10px 12px;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 200ms ease, transform 200ms ease;
}
.ww-guide__bubble.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.ww-guide__bubble.is-fading {
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity 280ms ease, transform 280ms ease;
}
.ww-guide__bubble::after {
  /* Triângulo apontando pro Oto (canto inferior direito do balão). */
  content: '';
  position: absolute;
  right: -10px;
  bottom: 14px;
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
  border-left: 12px solid var(--ww-surface-raised);
  filter: drop-shadow(2px 0 0 var(--ww-line));
}
.ww-guide__bubble-text {
  white-space: pre-wrap;
  line-height: 1.35;
}
.ww-guide--muted .ww-guide__figure { opacity: 0.45; }
.ww-guide--muted .ww-guide__bubble { display: none; }

/* Botões "no tutos please" + "next" no balão do Oto durante o tour.
   Compõe sobre .ww-button já existente. pointer-events: auto pra
   reverter o pai (.ww-guide__bubble é pointer-events:none por padrão). */
.ww-guide__bubble-actions {
  margin-top: 10px;
  display: flex;
  justify-content: space-between;
  gap: 8px;
  pointer-events: auto;
}
.ww-guide__bubble-tour-btn {
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════
   TOUR OVERLAY — escurece a tela e destaca um alvo (HUD ou carta).
   Buraco: box-shadow inset gigante pinta só o "fora" do hole.
   Hole em si tem outline accent + cor sunken pra ler como "olha aqui".
═══════════════════════════════════════════════════════════════════════ */
.ww-tour-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 49;   /* abaixo do Oto (z-50) e da Store modal (z varia), acima do HUD (z-20) */
}
.ww-tour-overlay__hole {
  position: absolute;
  /* Sombra inset gigante = pinta o "fora" do hole de preto translúcido.
     Vector enorme garante que cobre qualquer viewport. */
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  border: 3px solid var(--ww-accent);
  border-radius: var(--ww-radius);
  transition: left 200ms ease, top 200ms ease, width 200ms ease, height 200ms ease;
  pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   CARD SPEECH BUBBLE — qualquer carta pode falar via Speech.say()

   T30 — O BALÃO NÃO PODE FICAR ATRÁS DE CARTA NENHUMA.

   Ele morava dentro do `.ww-card`, e o `.ww-card` tem `z-index` (o zOrder do
   domínio) — o que faz dele um contexto de empilhamento. Tudo que estava lá
   dentro, balão inclusive, ficava preso abaixo do teto daquela carta, e
   qualquer carta de zOrder maior passava por cima da fala. Fala é o canal
   principal deste jogo: encoberta, ela simplesmente não aconteceu.

   Agora vive numa camada própria, irmã de `#card-overlay__world`, que recebe o
   mesmo transform da câmera e posiciona cada balão em coordenadas de mundo. O
   número do z-index é o mesmo raciocínio do `#recipe-progress-arcs`: as cartas
   competem com o valor CRU do zOrder, que anda na casa dos milhões — e a fala
   fica um degrau acima até do anel de progresso, porque quando os dois
   coincidem quem tem que ser lido é a frase.

   Centralizado em cima da carta, triângulo apontando pra baixo. A âncora é o
   ponto mais alto da EXTENSÃO REAL da carta, então carta torta num leque tem o
   balão acima dela de verdade — e o balão não gira junto.
═══════════════════════════════════════════════════════════════════════ */
#card-speech-layer {
  z-index: 2147483100;
}

/* A camada dos balões de mudança de prop (founder, 2026-08-06).
   *"Nenhuma bolha de texto tem que ficar atrás de nenhuma carta em nenhum
   momento."*

   Ela nasceu porque o `z-index` do balão não bastava: ele vivia DENTRO de
   `#card-overlay__world`, que tem `transform` (a câmera) e portanto cria
   contexto de empilhamento — o número ficava preso lá dentro, ordenando o
   balão só contra as cartas irmãs. Ver a nota longa em `fx/PropFloaty.js`.

   Agora é irmã do mundo e da fala, e o z volta a significar o que promete.
   Um degrau acima do speech-layer: quando os dois coincidem sobre a mesma
   carta, o número recém-mudado é a informação nova. */
#card-floaty-layer {
  position: absolute;
  z-index: 2147483200;
  pointer-events: none;
}
.ww-card-speech__anchor {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
}
.ww-card-speech__bubble {
  position: absolute;
  left: 0;
  bottom: 12px;
  transform: translate(-50%, 4px);
  /* A janelinha da fala é parte do contrato dos temas de visão (founder,
     2026-08-18): largura, texto e cores leem tokens `--a11y-speech-*`. */
  max-width: calc(200px * var(--a11y-speech-scale, 1));
  min-width: 60px;
  padding: 8px 10px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease, transform 200ms ease;
  white-space: pre-wrap;
  z-index: 50;
  background: var(--a11y-speech-bg, var(--ww-surface-raised));
  color: var(--a11y-speech-ink, var(--ww-text-primary));
}
.ww-card-speech__bubble.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}
.ww-card-speech__bubble.is-fading {
  opacity: 0;
  transform: translate(-50%, -2px);
  transition: opacity 280ms ease, transform 280ms ease;
}
.ww-card-speech__bubble::after {
  /* Triângulo apontando pra baixo (pra carta). */
  content: '';
  position: absolute;
  left: 50%;
  bottom: -10px;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-top: 12px solid var(--a11y-speech-bg, var(--ww-surface-raised));
  filter: drop-shadow(0 2px 0 var(--ww-line));
}
.ww-card-speech__text {
  white-space: pre-wrap;
  line-height: 1.3;
  font-size: calc(13px * var(--a11y-text, 1) * var(--a11y-speech-text, 1));
  text-align: center;
}

/* ═══════════════════════════════════════════════════════════════════════
   STORE — loja "em obras" (openAt > Context.day)
═══════════════════════════════════════════════════════════════════════ */
.ww-card--unopened .ww-card__inner {
  filter: grayscale(0.85) brightness(0.85);
}
.ww-card--unopened .ww-card__inner::after {
  /* listras diagonais "em obras" */
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    45deg,
    rgba(0,0,0,0.0) 0 8px,
    rgba(0,0,0,0.18) 8px 14px
  );
  border-radius: var(--ww-card-radius);
  z-index: 5;
}
/* Faixa diagonal "opens in N days" — atravessa a carta na diagonal,
   estilo fita de etiqueta. Pseudo-bordas pretas no topo/base reforçam
   leitura de "fita aplicada por cima". Pointer-events off — não bloqueia
   click/hover. Largura 200% pra garantir que cubra a carta toda mesmo com
   rotação (as pontas vão pra fora e ficam escondidas pelo overflow:hidden
   do __inner). */
.ww-card__unopened-label {
  position: absolute;
  left: -50%;
  right: -50%;
  top: 50%;
  transform: translateY(-50%) rotate(-22deg);
  transform-origin: center center;
  font-family: var(--ww-font-display);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.3px;
  text-align: center;
  color: var(--ww-surface-raised);
  background: var(--ww-line);
  padding: 4px 0;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  border-top: 1.5px solid rgba(0,0,0,0.35);
  border-bottom: 1.5px solid rgba(0,0,0,0.35);
  box-shadow: 0 2px 0 rgba(0,0,0,0.2);
  z-index: 6;
}
@keyframes ww-card-locked-blip {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0); }
  20% { transform: translate3d(-3px, 0, 0) rotate(-1deg); }
  60% { transform: translate3d(3px, 0, 0) rotate(1deg); }
}
.ww-card--locked-blip .ww-card__inner {
  animation: ww-card-locked-blip 0.45s ease-in-out;
}

/* ═══════════════════════════════════════════════════════════════════════
   NEWSPAPER — carta jornal "stale" (não lida no dia anterior) e modal
═══════════════════════════════════════════════════════════════════════ */
.ww-card--stale .ww-card__inner {
  filter: sepia(0.65) contrast(0.9);
  opacity: 0.78;
}

.ww-newspaper-modal__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(17, 17, 17, 0.5);
  z-index: 90;
}
.ww-newspaper-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
}
.ww-newspaper-modal__frame {
  width: min(640px, 92vw);
  max-height: 86vh;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 22px 26px;
}
.ww-newspaper-modal__masthead {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
  padding-bottom: 10px;
}
.ww-newspaper-modal__lead {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--ww-text-muted);
}
.ww-newspaper-modal__headline {
  font-family: var(--ww-font-display);
  font-weight: 900;
  font-size: 28px;
  line-height: 1.15;
  color: var(--ww-text-primary);
}
.ww-newspaper-modal__sub-headline {
  font-family: var(--ww-font-display);
  font-weight: 800;
  font-size: 18px;
  line-height: 1.2;
  color: var(--ww-text-primary);
}
.ww-newspaper-modal__body {
  line-height: 1.5;
}
.ww-newspaper-modal__rest {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ww-newspaper-modal__secondary {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 0;
  border-top: 1px solid var(--ww-text-muted);
}
.ww-newspaper-modal__effects {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-top: 6px;
}
.ww-newspaper-modal__effect {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--ww-text-secondary, var(--ww-text-primary));
  background: rgba(0, 0, 0, 0.04);
  padding: 3px 8px;
  border-radius: 6px;
}
.ww-newspaper-modal__effect-icon {
  font-size: 15px;
  line-height: 1;
}
.ww-newspaper-modal__market {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--ww-text-muted);
}
.ww-newspaper-modal__swings {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
}
.ww-newspaper-modal__swing-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: 12px;
  padding: 4px 8px;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  background: rgba(0, 0, 0, 0.03);
  border-radius: 4px;
}
.ww-newspaper-modal__swing-label {
  color: var(--ww-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-newspaper-modal__swing-delta {
  font-weight: 700;
  letter-spacing: 0.02em;
}
.ww-newspaper-modal__swing-price {
  color: var(--ww-text-muted);
  min-width: 48px;
  text-align: right;
}
.ww-newspaper-modal__swing--up .ww-newspaper-modal__swing-delta {
  color: #2e7d32;
}
.ww-newspaper-modal__swing--down .ww-newspaper-modal__swing-delta {
  color: #c62828;
}
.ww-newspaper-modal__footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 4px;
}


/* ═══════════════════════════════════════════════════════════════════════
   BOTÃO "?" E CATÁLOGO DE COMBINAÇÕES (T10)
   Renderer: HelpPanelRenderer. Conteúdo derivado do RecipeRegistry.
   ═══════════════════════════════════════════════════════════════════════ */

/* O botão é grande de propósito: é a resposta a "tá bem difícil descobrir",
   e um alvo de 36px no canto não seria encontrado por quem já está perdido. */
.ww-help-button {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 60;
  width: 56px;
  height: 56px;
  padding: 0;
  font-family: var(--ww-font-display);
  font-size: 30px;
  font-weight: 900;
  line-height: 1;
  pointer-events: auto;
}
.ww-help-button.is-active {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}

/* O farm põe a carteira exatamente neste canto. O botão fica, o dinheiro anda
   pro lado — em vez de o botão mudar de lugar entre um pack e outro. */
body.has-help-button .ww-hud__money { right: 84px; }

/* Se o jogador já estava em pausa quando abriu a lista, o overlay PAUSED
   apareceria esmaecido atrás do backdrop — ruído. A pausa dele continua de pé;
   só o aviso some enquanto a lista está na frente. */
body.is-help-open .ww-pause { display: none !important; }

.ww-help-modal__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(17, 17, 17, 0.5);
  z-index: 90;
}
.ww-help-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.ww-help-modal__frame {
  width: min(720px, 94vw);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ww-help-modal__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  border-bottom: var(--ww-line-width) solid var(--ww-line);
  padding-bottom: 10px;
}
.ww-help-modal__title-block {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ww-help-modal__intro { line-height: 1.5; }
.ww-help-modal__body {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding-right: 8px;
}
.ww-help-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ww-help-group__head {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.ww-help-group__hint { line-height: 1.4; }

.ww-help-row {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 9px 11px;
}
.ww-help-row__name {
  font-size: 13px;
  font-weight: 900;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* A fórmula quebra sozinha: os mesmos chips que ficam numa linha no desktop
   empilham no celular sem layout alternativo. */
.ww-help-row__formula {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.ww-help-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.3;
  background: var(--ww-surface-raised);
  border: 2px solid var(--ww-line);
  border-radius: var(--ww-radius);
  padding: 3px 8px;
}
/* O resultado é o único chip em accent: a linha inteira se lê pela cor, sem
   precisar seguir a seta. */
.ww-help-chip--out {
  background: var(--ww-accent);
  color: var(--ww-surface-raised);
}
.ww-help-chip__count { font-weight: 900; }
/* Os glifos do T9 — mesmo desenho do cabeçalho da carta, em 14px. */
.ww-help-icon { display: inline-flex; }
.ww-help-icon svg {
  width: 14px;
  height: 14px;
  display: block;
}
/* Operador + chip andam juntos: quando a fórmula quebra no celular, o "+"
   desce com o ingrediente em vez de ficar pendurado na linha de cima. */
.ww-help-term {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.ww-help-op {
  font-weight: 900;
  font-size: 13px;
}
.ww-help-badge {
  font-size: 9px;
  border-width: 2px;
  padding: 1px 6px;
}

@media (max-width: 640px) {
  .ww-help-button {
    top: 10px;
    right: 10px;
    width: 48px;
    height: 48px;
    font-size: 26px;
  }
  body.has-help-button .ww-hud__money { right: 68px; }
  .ww-help-modal { padding: 10px; }
  .ww-help-modal__frame { max-height: 92vh; }
}


/* ─────────────────────────── SELETOR DE LÍNGUA ───────────────────────────
   Segmento de dois botões (`.ww-button--small`), colados, com o ativo em
   accent. Hook de layout apenas — a pintura toda vem de `.ww-button` e do
   estado `.is-active` que a primitiva já define (VISUAL-MANUAL §3). */
.ww-locale {
  display: inline-flex;
  gap: 2px;
  pointer-events: auto;
}
.ww-locale__opt {
  min-width: 74px;
  justify-content: center;
}
.ww-locale__opt:not(:first-child) {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.ww-locale__opt:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* ── PAINEL DE ACESSIBILIDADE (T36) ───────────────────────────────────────
   Os quatro ajustes num lugar. Só layout: a pintura toda vem das primitivas
   (`.ww-panel`, `.ww-button`, `.ww-heading`, `.ww-label`, `.ww-body`), como o
   VISUAL-MANUAL manda.

   Abre por F1, e por F1 de propósito — Esc já é "solta a carta" no teclado do
   jogo (T36), e pendurar os ajustes nele os trancaria justamente enquanto o
   jogador está com uma carta na mão. */
.ww-a11y {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  /* 230: o painel abre POR CIMA da home (200), do confirm de pausa (210) e do
     toast de update (220) — desde 2026-08-18 ele também é chamado do menu, e
     com 16 abria ATRÁS da home (pego no print de repouso). Abaixo dos painéis
     de dev (400/500), que são ferramenta e cobrem tudo. */
  z-index: 230;
  font-family: var(--ww-font-body);
}

.ww-a11y__inner {
  width: min(440px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  pointer-events: auto;
}

.ww-a11y__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Painel estreito: o segmento desce pra linha de baixo em vez de espremer
     o rótulo ou estourar a borda (founder, 2026-08-18 — passe de respiro). */
  flex-wrap: wrap;
  row-gap: 6px;
}

.ww-a11y__name { flex: 1 1 150px; }

.ww-a11y__seg {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
  /* Os botões têm sombra chapada de 2px — sem este respiro ela deita em cima
     da linha de texto de baixo. */
  padding-bottom: 2px;
}

/* A dica fica perto do controle que ela explica, mas com ar: colada (-6px) a
   sombra dos botões cortava a primeira linha da frase. */
.ww-a11y__hint {
  margin-top: -2px;
  margin-bottom: 2px;
  line-height: 1.4;
}

.ww-a11y__keys {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ww-a11y__close { margin-top: 4px; }

/* Grupos colapsáveis do painel (founder, 2026-08-18: "áreas colapsáveis por
   categoria"). O cabeçalho compõe .ww-button--ghost; a seta é texto, sem
   ícone nativo de <details> (nativo cru é proibido). Nascem ABERTOS — opção
   de acessibilidade escondida por padrão não existe pra quem precisa dela. */
.ww-a11y__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ww-a11y__group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  text-align: left;
  width: 100%;
}
.ww-a11y__group-arrow {
  font-size: calc(11px * var(--a11y-text, 1));
  opacity: 0.7;
}
.ww-a11y__group-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-left: 4px;
}
.ww-a11y__group.is-collapsed .ww-a11y__group-body { display: none; }

/* Os presets (founder, 2026-08-18): grade de perfis acima dos granulares.
   Hook de LAYOUT — a pintura dos botões é do .ww-button que eles compõem. */
.ww-a11y__presets {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 4px;
}
.ww-a11y__preset-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  /* A mesma sombra chapada dos botões, o mesmo respiro. */
  padding-bottom: 2px;
}

/* ────── MENOS MOVIMENTO (a11y-reduce-motion) ──────
   Preferência de acessibilidade, aplicada como classe no <html> por
   `core/a11y-prefs.js`. Três efeitos, todos de decoração — o salto das
   cartas (gameplay, JS) fica:
   1. as folhas ao vento somem (os canvases continuam vivos, só invisíveis);
   2. o tremor de tela é zerado no próprio ScreenShake (lê a classe);
   3. toda transição/animação de CSS encurta a ~zero. */
html.a11y-reduce-motion .ww-leaf-layer { display: none; }
html.a11y-reduce-motion *,
html.a11y-reduce-motion *::before,
html.a11y-reduce-motion *::after {
  transition-duration: 0.01ms !important;
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
}

/* ───────── Toast de atualização do PWA (src/core/pwa.js) ─────────
   Hook de LAYOUT — a pintura inteira é do .ww-panel que ele compõe.
   Canto inferior esquerdo, discreto, acima do overlay de pause (z 15) e do
   HUD (z 20). Entrada por transform (regra de ouro: nada de filter/shadow
   animado); .is-in é aplicada um frame depois do append. */
.ww-pwa-toast {
  position: fixed;
  left: 16px;
  bottom: 16px;
  /* ── ACIMA DA HOME, SEMPRE (founder, 2026-08-06) ──────────────────────
     Relato: *"o botão de atualizar não consigo mais clicar nele, tá em um
     canto que parece que não tem click (…) mas se eu entrar no jogo e
     clicar, ele vai."*

     Era z:30 contra a `.ww-mainmenu`, que é `position: fixed; inset: 0`
     (tela inteira), `z-index: 200` e `pointer-events: auto`. O painel
     invisível da home cobria o toast por completo e engolia o clique.
     Dentro do jogo esse painel não existe — por isso lá funcionava, e é
     exatamente o par de sintomas que o founder descreveu.

     E o lugar onde ele quebrava é o PIOR possível: a home é onde o jogador
     cai, e este botão é o ÚNICO caminho para sair de uma versão velha. Um
     toast que promete atualizar e não responde ao clique é pior que não ter
     toast nenhum — o jogador conclui que o jogo travou.

     220: acima da home (200) e do confirm de pausa (210); abaixo dos
     painéis de dev (400/500), que são ferramenta e devem cobrir tudo. */
  z-index: 220;

  /* ── E ELE PRECISA LIGAR O PRÓPRIO PONTEIRO (founder, 2026-08-06) ──────
     Esta é a causa REAL de o botão não clicar, e o `z-index` acima era só
     metade do conserto.

     Neste projeto os overlays são `pointer-events: none` por padrão (é o que
     deixa o clique atravessar até o canvas do Phaser, que é quem move
     carta), e **cada elemento clicável liga o seu com `auto`** — são 24
     fazendo isso. O toast era o único que NÃO ligava: ele herdava `none` do
     ancestral e ficava decorativo, por mais alto que estivesse na pilha.
     Nenhum ajuste de z consertaria isso.

     É a mesma lição da alça e das bolhas, pela terceira vez: o que decide
     não é só o número — é a estrutura em volta dele. */
  pointer-events: auto;

  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  transform: translateY(calc(100% + 24px));
  transition: transform 220ms ease-out;
}

.ww-pwa-toast.is-in { transform: translateY(0); }

/* ══════════════════════════════════════════════════════════════════════════
   A CUTSCENE (2026-08-18) — a moldura cinematográfica da cena de abertura.
   Runtime em src/render/cutscene/; roteiro em src/content/packs/taba/
   cutscenes/. O root cobre o jogo inteiro (que recua com blur/zoom por
   style inline do cine-overlay) e captura o clique — clique curto adianta,
   segurar pula. Letras: cada uma é um span com entrada própria (cn-*).
   ════════════════════════════════════════════════════════════════════════ */

.cine-root {
  position: fixed; inset: 0;
  z-index: 2147483310; /* acima de TUDO, tooltip incluso — a cena cobre o jogo */
  pointer-events: auto;
}

/* Durante a cena, a UI do jogo NÃO EXISTE (founder, 2026-08-18): HUD,
   balões, bolhas, anéis de receita e as folhas do jogo somem por completo —
   sobra só a mesa e as cartas embaçadas como cenário. A lista cobre as
   camadas IRMÃS do body (z 2147483000+), que ficam fora dos containers que
   o cine-overlay embaça. Elemento novo de UI de topo entra AQUI também. */
body.cine-playing #hud-overlay,
body.cine-playing #fx-overlay,
body.cine-playing #weather-overlay,
body.cine-playing #card-speech-layer,
body.cine-playing #card-floaty-layer,
body.cine-playing #recipe-progress-arcs,
body.cine-playing #recipe-dots,
body.cine-playing .ww-leaf-layer {
  opacity: 0 !important;
  pointer-events: none !important;
}
#hud-overlay, #fx-overlay, #weather-overlay,
#card-speech-layer, #card-floaty-layer,
#recipe-progress-arcs, #recipe-dots, .ww-leaf-layer {
  transition: opacity 0.8s ease;
}
.cine-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.cine-canvas--foliage { filter: blur(11px); }
/* As nuvens: canvas próprio ATRÁS das cartas com blur leve — profundidade de
   campo barata (founder, 2026-08-19); mesmo padrão da folhagem. */
.cine-canvas--clouds { filter: blur(2.5px); }

.cine-bar {
  position: absolute; left: 0; right: 0; height: 0;
  background: #000;
}
.cine-bar--top { top: 0; }
.cine-bar--bottom { bottom: 0; }
.cine-vignette {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(ellipse 72% 62% at 50% 46%, transparent 55%, rgba(4,10,8,0.55) 100%);
}

.cine-title {
  position: absolute; left: 50%; top: 38%; transform: translate(-50%, -50%);
  pointer-events: none; text-align: center;
  font-family: var(--ww-font-display);
  font-weight: 900;
  font-size: clamp(38px, 6vw, 84px);
  line-height: 1.1;
  color: #f3e9d4; letter-spacing: 0.04em;
  text-shadow: 0 2px 18px rgba(0,0,0,0.65), 0 0 60px rgba(255,190,90,0.25);
}
.cine-dialog {
  position: absolute; left: 50%; bottom: calc(9vh + 30px); transform: translateX(-50%);
  width: min(880px, 82vw); text-align: center;
  pointer-events: none;
}
.cine-text {
  font-family: var(--ww-font-body);
  font-weight: 700;
  font-size: clamp(20px, 2.6vw, 30px);
  line-height: 1.5;
  color: #f3ecdc; text-shadow: 0 2px 10px rgba(0,0,0,0.8), 0 0 34px rgba(0,0,0,0.5);
  min-height: 3em;
}

/* a pergunta da quarta parede — dois botões, pintura das primitivas */
.cine-choice {
  display: none; gap: 14px; justify-content: center; margin-top: 18px;
  pointer-events: auto;
}
.cine-choice.is-on { display: flex; }

/* o controle de pular: mora na barra de baixo; o preenchimento é o anel
   de "segurando" (--skip-k vai de 0 a 1 enquanto o dedo está no botão) */
.cine-skip {
  position: absolute; right: 18px; bottom: 10px;
  color: rgba(240, 232, 216, 0.75);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.22) calc(var(--skip-k, 0) * 100%), transparent 0);
  pointer-events: auto;
}

/* ── letras vivas (cine-text.js) ─────────────────────────────────────── */
.cn-line { display: inline-block; white-space: pre-wrap; }
.cn-w { display: inline-block; white-space: nowrap; }
.cn-l { display: inline-block; opacity: 0; will-change: transform, opacity, filter; }
.cn-l.cn-on { opacity: 1; }
.cn-li { display: inline-block; animation: cn-idle var(--idur, 3s) ease-in-out var(--idel, 0s) infinite alternate; }
@keyframes cn-idle {
  from { transform: translateY(-1.4px) rotate(-0.5deg); }
  to   { transform: translateY(1.4px) rotate(0.5deg); }
}
.cn-on[data-cn="brisa"] { animation: cn-brisa 0.5s cubic-bezier(.16,.84,.32,1) both; }
@keyframes cn-brisa {
  0%   { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1.1); filter: blur(5px); }
  70%  { opacity: 1; filter: blur(0.5px); }
  85%  { transform: translate(calc(var(--dx) * -0.06), 0) rotate(calc(var(--rot) * -0.15)); }
  100% { opacity: 1; transform: none; filter: none; }
}
.cn-on[data-cn="brasa"] { animation: cn-brasa 0.55s ease-out both; }
@keyframes cn-brasa {
  0%   { opacity: 0; transform: translateY(14px) scale(1.35); color: #fff3c8;
         text-shadow: 0 0 18px rgba(255,140,40,.95), 0 0 42px rgba(255,90,20,.6); }
  25%  { opacity: 1; color: #ffd9a0;
         text-shadow: 0 0 14px rgba(255,140,40,.85), 0 0 36px rgba(255,90,20,.5); }
  100% { opacity: 1; transform: none; color: inherit; text-shadow: 0 0 0 rgba(255,140,40,0); }
}
.cn-on[data-cn="carimbo"] { animation: cn-carimbo 0.3s cubic-bezier(.34,1.56,.64,1) both; }
@keyframes cn-carimbo {
  0%   { opacity: 0; transform: scale(2.4) rotate(var(--rot)); filter: blur(2px); }
  60%  { opacity: 1; transform: scale(0.92) rotate(calc(var(--rot) * -0.2)); filter: none; }
  100% { opacity: 1; transform: none; }
}
.cn-on[data-cn="sussurro"] { animation: cn-sussurro 0.55s ease-out both; }
@keyframes cn-sussurro {
  0%   { opacity: 0; filter: blur(10px); transform: scale(1.06); letter-spacing: 0.06em; }
  100% { opacity: 0.96; filter: none; transform: none; }
}
.cn-on[data-cn="agua"] { animation: cn-agua 0.5s ease-out both; }
@keyframes cn-agua {
  0%   { opacity: 0; transform: translate(calc(var(--wob) * 1px), 16px); filter: blur(4px); }
  35%  { opacity: 0.9; transform: translate(calc(var(--wob) * -0.7px), 8px); filter: blur(1.5px); }
  70%  { transform: translate(calc(var(--wob) * 0.3px), 2px); }
  100% { opacity: 1; transform: none; filter: none; }
}
.cn-instant { animation: none !important; opacity: 1 !important; transform: none !important; filter: none !important; }

/* ── A PELE DO CURSOR (core/cursor-skin.js) ──────────────────────────────
   O conjunto POR SITUAÇÃO escolhido pelo founder no catálogo (2026-08-19,
   Kenney Cursor Pack CC0, família hand_thin):
     point   → padrão, o jogo inteiro
     open    → sobre carta e sobre o punho do baralho ("dá pra pegar")
     closed  → carregando (`.ww-card.dragging`, que segue o ponteiro)
   O !important vence os `cursor: pointer` locais; a especificidade crescente
   das três regras decide entre elas. Cursor novo por situação entra AQUI. */
html.cursor-skin, html.cursor-skin * {
  cursor: url('/public/cursors/hand-thin-point-32.png') 10 2, auto !important;
}
html.cursor-skin .ww-card, html.cursor-skin .ww-card *,
html.cursor-skin .ww-deck-handle, html.cursor-skin .ww-deck-handle * {
  cursor: url('/public/cursors/hand-thin-open-32.png') 14 10, auto !important;
}
html.cursor-skin .ww-card.dragging, html.cursor-skin .ww-card.dragging * {
  cursor: url('/public/cursors/hand-thin-closed-32.png') 14 12, auto !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   O COMBATE DE DECISÃO (founder, 2026-08-19) — ver DuelPanelRenderer.js.
   Primitivas pintam (.ww-panel/.ww-button); os hooks daqui só posicionam.
   Dock inferior: as duas cartas focadas pela câmera ficam visíveis acima,
   e o véu é pointer-events none — o hover nas cartas continua vivo.
   ════════════════════════════════════════════════════════════════════════ */

.ww-duel__veil {
  position: fixed;
  inset: 0;
  background: rgba(17, 17, 17, 0.42);
  pointer-events: none;
  z-index: 30;
}

.ww-duel {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 18px;
  display: flex;
  justify-content: center;
  pointer-events: none;
  z-index: 40;
}

.ww-duel__frame {
  pointer-events: auto;
  width: min(680px, calc(100vw - 24px));
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ww-duel__head {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
}

.ww-duel__vs {
  color: var(--ww-accent);
}

.ww-duel__prompt {
  text-align: center;
}

.ww-duel__options {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

.ww-duel__option {
  flex: 1 1 0;
  min-width: 150px;
  max-width: 220px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 10px 12px;
  text-align: left;
}

.ww-duel__option-label {
  font-weight: 900;
  font-size: 14px;
  line-height: 1.15;
}

.ww-duel__option-chance {
  font-weight: 900;
  font-size: 20px;
}

/* A leitura da chance vem do QUANTO a barra preenche, não de cor semafórica
   (regra 14): trilho sunken, preenchimento accent, sempre os mesmos dois. */
.ww-duel__option-bar {
  display: block;
  height: 8px;
  border: 2px solid var(--ww-line);
  border-radius: 4px;
  background: var(--ww-surface-sunken);
  overflow: hidden;
}

.ww-duel__option-fill {
  display: block;
  height: 100%;
  background: var(--ww-accent);
}

.ww-duel__verdict {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
}

/* O fracasso fala em accent — a pontuação visual do "isto aconteceu". */
.ww-duel__verdict--fail {
  background: var(--ww-accent);
  color: #fff;
}

.ww-duel__verdict--fail .ww-label {
  color: #fff;
}

.ww-duel__verdict-text {
  margin: 0;
  font-size: 15px;
  font-weight: 800;
}

.ww-duel__continue {
  align-self: center;
  min-width: 180px;
}

@media (max-width: 640px) {
  .ww-duel__options { flex-direction: column; }
  .ww-duel__option { max-width: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   A CENA DO COMBATE DE DECISÃO (founder, 2026-08-19) — duel-cine.js +
   DuelSceneRenderer.js. O pontilhado vive em camada IRMÃ (fora do blur, por
   causa da regra do incidente 2026-08-18: nada animado sob a camada
   embaçada); a cena reusa a moldura da cutscene (.cine-root) e estes hooks
   só posicionam por cima dela.
   ════════════════════════════════════════════════════════════════════════ */

/* A camada do pontilhado: espelha a matriz da câmera por cópia (ver o
   coordenador). Acima do mundo (z 10), abaixo da moldura da cena. */
#duel-ring-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 11;
}

/* "É entre essas duas que está acontecendo": tracejado accent girando
   devagar. `background-position` anima só este elemento pequeno — barato. */
.ww-duel-ring {
  position: absolute;
  border-radius: 18px;
  background:
    linear-gradient(90deg, var(--ww-accent) 55%, transparent 45%) top/16px 3px repeat-x,
    linear-gradient(90deg, var(--ww-accent) 55%, transparent 45%) bottom/16px 3px repeat-x,
    linear-gradient(0deg,  var(--ww-accent) 55%, transparent 45%) left/3px 16px repeat-y,
    linear-gradient(0deg,  var(--ww-accent) 55%, transparent 45%) right/3px 16px repeat-y;
  animation: ww-duel-ants 0.9s linear infinite;
}

@keyframes ww-duel-ants {
  to { background-position: top 16px 0, bottom -16px 0, left 0 -16px, right 0 16px; }
}

/* O hover que abre a ficha REAL: zona invisível sobre o retângulo de tela de
   cada ator 3D (o canvas é pointer-events none — quem sente o mouse é isto). */
.cine-duel-hover {
  position: absolute;
  pointer-events: auto;
  cursor: help;
}

/* O painel de atributos do jogo, dentro da cena: o MESMO .ww-tooltip da mesa,
   ancorado na borda interna da carta. Hook só posiciona e limita. */
.cine-duel-panel {
  display: block;
  position: absolute;
  width: 300px;
  max-height: 76vh;
  overflow: hidden;
  pointer-events: none;
  z-index: 5;
}

/* AS ESCOLHAS NO MEIO — entre as duas cartas (founder: "aí tem mais
   possibilidade de conversa rica"). Coluna central, estreita. */
.cine-duel-choice {
  position: absolute;
  left: 50%;
  top: 46%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(320px, 78vw);
}

/* O botão de abordagem: coluna cheia — sem isto o .ww-button centra os
   filhos pela largura do conteúdo e a barra de chance colapsa num tracinho
   (o "botão bugado" que o founder viu em 2026-08-19). */
.cine-duel-option {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 7px;
  padding: 12px 14px;
  text-align: left;
}

.cine-duel-option__row {
  display: flex;
  width: 100%;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.cine-duel-option__label { font-weight: 900; font-size: 15px; }
.cine-duel-option__chance { font-weight: 900; font-size: 19px; }

/* A leitura da chance é o preenchimento, nunca cor semafórica (regra 14). */
.cine-duel-option__bar {
  display: block;
  width: 100%;
  height: 8px;
  border: 2px solid var(--ww-line);
  border-radius: 4px;
  background: var(--ww-surface-sunken);
  overflow: hidden;
}

.cine-duel-option__fill {
  display: block;
  height: 100%;
  background: var(--ww-accent);
}

/* Centralizado por margem, NUNCA por transform: o hover do .ww-button troca
   o transform por translate(-1px,-1px) e o botão fugia meia largura pra
   direita (founder viu, 2026-08-19). */
.cine-duel-continue {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 12vh;
  margin: 0 auto;
  width: 220px;
}

/* As prévias de consequência da abordagem selecionada: perde à ESQUERDA do
   botão ativo, ganha à DIREITA — o `top` é escrito pelo JS pra ancorar no
   centro do próprio botão (founder: "ao lado do seu respectivo botão").
   Hooks só posicionam (VISUAL-MANUAL). */
.cine-duel-consequence {
  position: absolute;
  transform: translateY(-50%);
  width: 200px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  pointer-events: none;
  transition: top 140ms ease-out;
}

.cine-duel-consequence--lose { right: calc(50% + 186px); }
.cine-duel-consequence--win { left: calc(50% + 186px); }

/* O lado do erro fala em accent — rótulo e texto brancos por cima. */
.cine-duel-consequence--lose,
.cine-duel-consequence--lose .ww-label,
.cine-duel-consequence--lose .cine-duel-consequence__text {
  color: #fff;
}

/* O lado do acerto: rebaixado neutro, texto claro sobre o teal escuro. */
.cine-duel-consequence--win,
.cine-duel-consequence--win .ww-label,
.cine-duel-consequence--win .cine-duel-consequence__text {
  color: #eafaf5;
}

.cine-duel-consequence__text {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
}

@media (max-width: 900px) {
  .cine-duel-consequence { display: none; }
}

@media (max-width: 640px) {
  .cine-duel-panel { display: none; }
  .cine-duel-choice { top: 52%; }
}
