/* ============================================================
   TRINCHERA - Cliente
   Estetica (V0.11): la del cartel de campaña. TRES REGLAS, y solo tres:

     1. NEGRO.       El fondo y los paneles son negro casi puro. No marron,
                     no gris: negro. Lo que se ve encima destaca por eso.
     2. BORDE DORADO. El laton envejecido es LO UNICO que dibuja los bordes.
                     Es el hilo que cose toda la interfaz.
     3. PERGAMINO EN LOS TEXTOS. La prosa nunca flota sobre el fondo: va
                     sobre papel viejo, con tinta oscura. Solo los textos.

   El rojo queda para el peligro y la accion principal. Nada mas lo usa.

   Los valores no son inventados: salen de medir las tres referencias del
   proyecto, que convergen.
     fondo      cartel #070707   ·  web inkietud #050505
     dorado     cartel #ae8269   ·  logo #8c6f56-#aa9372  ·  web #b28a43
     hueso      logo #cbb8a1-#dacab5  ·  web #eee3cc
     pergamino  cartel #b5a693-#978875  ·  web #d8c7a3
     rojo       cartel #682d29   ·  web #9b1c18

   Tipografia: Grenze ('TrincheraBanner', servida en /fonts) manda en los
   rotulos —da el aire de plancha de imprenta gastada— y Fira Sans
   Condensed lleva los datos. Antes Grenze solo se usaba en las cartas y
   la UI iba con fuentes del sistema.
   ============================================================ */

:root {
  /* --- 1. NEGRO: el fondo y los paneles --- */
  --void:#060605;        /* el suelo de todo */
  --pit:#0c0b09;         /* relleno de panel: negro, apenas levantado */
  --pit-hi:#141210;      /* canto superior del panel */
  --soot:#1c1a16;        /* hollin: separadores y rellenos secundarios */

  /* --- 2. DORADO: el unico color que dibuja bordes --- */
  --gold:#b28a43;
  --gold-hi:#d9b56d;     /* filo iluminado del laton */
  --gold-lo:#6b4f22;     /* laton en sombra */
  --gold-ghost:rgba(178,138,67,.22);  /* insinuado, para bordes internos */

  /* --- Texto sobre negro --- */
  --bone:#eee3cc;
  --bone-dim:rgba(238,227,204,.72);
  /* .58 y no .42: a .42 los subtítulos de los botones del menú y la pista del
     login se quedaban en 3,44:1 y 3,36:1, por debajo del 4,5:1 que pide WCAG
     para texto pequeño. Medido sobre los píxeles reales. */
  --bone-faint:rgba(238,227,204,.58);

  /* --- 3. PERGAMINO: solo donde hay texto --- */
  --paper:#cdbb98; --paper-hi:#ddcdae; --paper-lo:#ab9872;
  --paper-ink:#17120b; --paper-ink-dim:#4a3c28;

  /* --- Acentos: rojo = peligro y accion principal. Y punto. --- */
  --blood:#9b1c18; --blood-hi:#d6421e; --blood-lo:#5e100d;

  /* --- Tintes de bando, muy apagados: el color lo pone el borde dorado,
         no el relleno. Sirven para que VER/COLECCION/IA no sean identicos. --- */
  --tint-bot:#2a2233;    /* morado de la Novena Falange */
  --tint-watch:#1d2530;  /* acero */
  --tint-coll:#2a2213;   /* laton en sombra */

  /* --- Alias de compatibilidad ---------------------------------------
     El CSS ya venia con 141 usos de estos nombres. Reapuntarlos mueve la
     UI entera de una vez, sin tocar cada regla. */
  --wood-1:var(--soot); --wood-2:var(--pit); --wood-3:var(--void);
  --metal-1:#141210; --metal-2:#1f1d19; --metal-3:var(--gold-lo); --metal-hi:var(--gold);
  --brass:var(--gold); --brass-hi:var(--gold-hi); --brass-lo:var(--gold-lo);
  --olive:#4c4f2b; --olive-hi:#6f7440;
  --green:var(--gold-lo); --green-hi:var(--gold);
  --red:var(--blood); --red-hi:var(--blood-hi);
  --khaki:var(--gold); --khaki-hi:var(--gold-hi); --khaki-lo:var(--gold-lo);
  --steel:var(--gold-lo); --steel-hi:var(--gold);
  --earth:var(--pit); --mud:var(--pit-hi); --trench:var(--soot);
  --slate:var(--tint-watch); --shroud:var(--tint-bot);
  --field:var(--gold-lo); --field-hi:var(--gold);
  --ink:var(--bone); --ink-dim:var(--bone-dim); --ink-faint:var(--bone-faint);

  /* --- Tipografia --- */
  --font-display:'TrincheraBanner', 'Grenze', Georgia, 'Times New Roman', serif;
  --font-ui:"Roboto Condensed", "Arial Narrow", "Helvetica Neue", "Segoe UI",
            system-ui, sans-serif;

  /* La mano y, sobre todo, la zona de despliegue mandan en pantalla.
     El esbirro se dimensiona por ALTO (min de vh/vw) para garantizar
     >=150px de alto en 1080p sin desbordar en anchos pequenos. */
  --card-w: clamp(92px, 8.6vw, 140px);
  --minion-h: clamp(122px, min(21vh, 11.6vw), 208px);
  --minion-w: calc(var(--minion-h) * 0.74);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  /* iOS SAFARI (V0.17.9). En iOS, la altura contra la que se miden `100%`, `vh`
     y un `position: fixed` es la del viewport GRANDE — el de la barra recogida—,
     no la de lo que se ve. En un iPhone 16 son 852 px contra 659 reales: la app
     se dibuja 190 px más alta que la pantalla y lo de abajo (la mano, el END
     TURN) se queda debajo de la barra del navegador, cortado.

     `dvh` mide lo que se ve de verdad. Va en una declaración APARTE y detrás, a
     propósito: un navegador que no conozca la unidad tira la línea entera y se
     queda con la de arriba, que es el comportamiento de siempre. */
  height: 100dvh;
  overflow: hidden;
  /* Deja el pellizco para ampliar (única salida si algo se sale de pantalla) y
     mata el doble toque, que en partida se dispara al querer jugar una carta. */
  touch-action: manipulation;
  font-family: var(--font-ui);
  color: var(--ink);
  /* REGLA 1: negro. El wallpaper (V0.11) hace de fondo, y está pintado para
     esto: los flancos llevan la acción —banderas, soldados, ruinas— y el CENTRO
     está vacío y oscuro, justo donde se apoyan los paneles. Encima va una viñeta
     que apaga los bordes y baja el contraste general, para que el arte acompañe
     sin pelearse con la interfaz.
     El color de respaldo cierra el hueco si la imagen no carga. */
  background-color: var(--void);
  background-image:
    radial-gradient(135% 110% at 50% 45%,
      rgba(6,6,5,.34) 0%, rgba(6,6,5,.6) 56%, rgba(3,3,2,.92) 100%),
    url("/images/wallpaper.png");
  background-size: cover, cover;
  background-position: center center, center center;
  background-repeat: no-repeat, no-repeat;
  -webkit-font-smoothing: antialiased;
  user-select: none;
}

/* ---------- Grano de película sobre todo (V0.11) ----------
   El cartel de referencia está sucio: sin grano, cualquier paleta oscura se ve
   digital y limpia. Va en un feTurbulence embebido (nada de ficheros nuevos) y
   NO intercepta punteros, así que el arrastre de cartas sigue intacto. */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: 9000;
  pointer-events: none; opacity: .045; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce) { body::after { opacity: .03; } }

/* ---------- Rótulos: Grenze, con relieve de plancha gastada ----------
   Una sola familia para todo lo que grita, y el mismo relieve en todos: negro
   duro abajo (el troquel) y una luz arriba (el canto del metal). */
.game-title, .lobby-title, .go-deck-label,
.menu-btn .mb-title, #go-title, .btn-brass,
.btn-back, .turn-indicator .ti-label, #end-turn-btn, .log-header,
.collection-count, .cr-quien, .credits-gift,
#waiting-title {
  font-family: var(--font-display);
}

/* ---------- PERGAMINO reutilizable (REGLA 3) ----------
   Papel viejo: fibra, manchas de humedad y un canto sucio. Se aplica con
   `.pergamino` o heredando estas capas. La textura es un feTurbulence embebido
   —ningún fichero nuevo— en tono cálido, para que no parezca ruido de TV. */
/* Solo BLOQUES DE PROSA. Una línea suelta sobre papel queda como una pegatina
   torcida: las pistas cortas, subtítulos y contadores se quedan en hueso sobre
   negro. Probado con `.login-hint` y `#waiting-sub` y se veían mal. */
.pergamino, .deck-row .dr-desc, .credits-list li,
#go-reason {
  color: var(--paper-ink);
  background-color: var(--paper);
  background-image:
    /* manchas de humedad, muy tenues */
    radial-gradient(60% 40% at 18% 12%, rgba(120,88,42,.13), transparent 70%),
    radial-gradient(50% 45% at 84% 78%, rgba(96,70,34,.11), transparent 72%),
    /* sombra en los cantos: el papel no es plano */
    linear-gradient(180deg, rgba(60,44,20,.2), transparent 12%, transparent 88%, rgba(60,44,20,.26)),
    /* Fibra del papel, MUY floja a propósito. Antes iba a opacidad .35 con blend
       `luminosity`, y ese ruido de alta frecuencia caía justo a la escala de los
       trazos de la letra: el texto se leía como borroso. Ahora la fibra se nota
       en las zonas vacías del papel y no pelea con las letras. */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.3' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23p)' opacity='.05'/%3E%3C/svg%3E"),
    linear-gradient(170deg, var(--paper-hi), var(--paper) 45%, var(--paper-lo));
  background-blend-mode: normal, normal, normal, soft-light, normal;
  text-shadow: none;
}

/* ---------- Paneles: REGLA 1 (negro) + REGLA 2 (borde dorado) ----------
   El panel es negro y lo único que lo dibuja es un filo de latón: dos hilos,
   uno claro arriba y uno en sombra abajo, que es como se lee un metal. Casi sin
   redondeo (2px): las placas atornilladas no tienen esquinas blandas. */
.panel-metal {
  position: relative;
  /* TEXTURA (V0.11): el wallpaper se repite dentro del panel con
     `background-attachment: fixed`, así que queda ALINEADO con el del fondo. El
     panel deja de ser una plancha negra plana y pasa a leerse como una ventana
     ahumada sobre la misma escena: la textura continúa donde el marco la corta.
     Las capas oscuras van semitransparentes para que asome lo justo. */
  background:
    linear-gradient(180deg, rgba(178,138,67,.05), transparent 18%),
    linear-gradient(180deg, rgba(20,18,16,.7), rgba(12,11,9,.74) 42%, rgba(6,6,5,.8)),
    url("/images/wallpaper.png");
  background-size: auto, auto, cover;
  background-position: center, center, center center;
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-attachment: scroll, scroll, fixed;
  border: 1px solid var(--gold);
  border-radius: 2px;
  box-shadow:
    /* doble filo de latón: brillo por dentro arriba, sombra abajo */
    inset 0 1px 0 rgba(217,181,109,.45),
    inset 0 -1px 0 rgba(107,79,34,.8),
    inset 0 0 0 3px rgba(0,0,0,.55),
    inset 0 0 0 4px var(--gold-ghost),
    inset 0 0 70px rgba(0,0,0,.6),
    0 0 0 1px rgba(0,0,0,.9),
    0 12px 34px rgba(0,0,0,.8);
}
/* remaches en las esquinas */
.panel-metal::before {
  content:""; position:absolute; inset:6px; border-radius:6px;
  pointer-events:none;
  background:
    radial-gradient(circle 3px at 0 0, var(--gold-hi) 34%, var(--gold-lo) 54%, transparent 62%),
    radial-gradient(circle 3px at 100% 0, var(--gold-hi) 34%, var(--gold-lo) 54%, transparent 62%),
    radial-gradient(circle 3px at 0 100%, var(--gold-hi) 34%, var(--gold-lo) 54%, transparent 62%),
    radial-gradient(circle 3px at 100% 100%, var(--gold-hi) 34%, var(--gold-lo) 54%, transparent 62%);
  background-repeat:no-repeat;
}

/* ============================================================
   PANTALLAS
   ============================================================ */
/* `height` además de `inset: 0` por lo de iOS: un fijo con `bottom: 0` se ancla
   al viewport grande. Con la altura puesta manda `top` y el borde de abajo cae
   donde se ve. */
.screen { display: none; position: fixed; inset: 0; }
.screen { height: 100dvh; }
.screen.active { display: flex; align-items: center; justify-content: center; }

/* ---------- Login ---------- */
.login-box, .waiting-box {
  width: min(92vw, 470px);
  padding: 22px 30px 20px;
  text-align: center;
}
.dogtags { color: var(--brass); letter-spacing: 7px; font-size: 15px; opacity:.7; }
/* El estandarte (V0.11) — la imagen manda; el CSS solo la encaja.
   `screen` disuelve el negro horneado del PNG contra el fondo oscuro: un fondo
   de #060606 sube el panel un 2% escaso, imperceptible, y las letras de hueso
   quedan intactas. Recortarlo a mano habría exigido un alfa que el fichero no
   tiene, y pintarle una banda negra detrás delataría el rectángulo en cuanto
   alguien cambiara el color del panel. */
.game-title {
  margin: 2px 0 0; line-height: 0; font-size: 0;
}
.game-title img {
  /* Se dimensiona por ANCHO, con un tope en vh: el estandarte es apaisado
     (845×220), así que en una ventana baja lo que sobra es su altura y hay que
     recortarlo o empuja los botones fuera de la primera pantalla. */
  display: block; width: min(100%, 520px, 48vh); height: auto; margin: 0 auto;
  mix-blend-mode: screen;
  /* Los cantos izquierdo y derecho se difuminan para que el estandarte no acabe
     en un corte recto contra el borde del panel. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
/* Sin `screen` el negro del PNG se vería como un recuadro: se le pone detrás una
   banda del mismo negro para que el corte no cante. */
@supports not (mix-blend-mode: screen) {
  .game-title { background: #060606; border-radius: 2px; }
  .game-title img { mix-blend-mode: normal; }
}
#name-input {
  width: 100%; padding: 13px 14px; font-size: 18px;
  color: var(--ink); text-align: center; letter-spacing: 1px;
  background: linear-gradient(#161009, #241a0e);
  border: 2px solid #0d0a05;
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7), inset 0 0 0 1px rgba(120,116,98,.3);
  border-radius: 6px; outline: none;
  font-family: inherit;
}
#name-input::placeholder { color: var(--ink-faint); }
#name-input:focus { box-shadow: inset 0 2px 6px rgba(0,0,0,.7), 0 0 0 2px rgba(200,150,60,.5); }

/* Botón principal: negro con filo dorado y el rótulo en hueso. Es la acción
   irreversible del cartel, así que el rojo entra solo como brasa interior —lo
   justo para que se distinga de un botón secundario sin volverse un semáforo. */
.btn-brass {
  margin-top: 18px; padding: 13px 40px; cursor: pointer;
  font-family: var(--font-display); font-size: 22px; font-weight: 700; letter-spacing: 4px;
  color: var(--bone); text-shadow: 0 -1px 0 rgba(0,0,0,.8), 0 2px 4px rgba(0,0,0,.9);
  background:
    radial-gradient(120% 180% at 50% 118%, rgba(155,28,24,.62), transparent 62%),
    linear-gradient(180deg, var(--soot), var(--pit) 50%, var(--void));
  border: 1px solid var(--gold);
  border-radius: 2px;
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.5),
    inset 0 -1px 0 rgba(107,79,34,.85),
    inset 0 0 0 2px rgba(0,0,0,.6),
    0 0 0 1px rgba(0,0,0,.9),
    0 6px 18px rgba(0,0,0,.75);
  transition: transform .08s, filter .12s, box-shadow .16s;
}
.btn-brass:hover {
  filter: brightness(1.15); transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.7),
    inset 0 -1px 0 rgba(107,79,34,.85),
    inset 0 0 0 2px rgba(0,0,0,.6),
    0 0 0 1px rgba(0,0,0,.9),
    0 6px 18px rgba(0,0,0,.75), 0 0 22px rgba(178,138,67,.35);
}
.btn-brass:active { transform: translateY(1px); filter: brightness(.92); }
.btn-brass:focus-visible { outline: 2px solid var(--gold-hi); outline-offset: 3px; }
.login-hint { margin-top: 12px; color: var(--ink-faint); font-size: 11px; letter-spacing: 1px; }

/* ============================================================
   FILA DE MAZO + MODAL (V0.11)
   La rejilla de tarjetas ocupaba 314 px en el menú y obligaba a hacer scroll en
   portátiles bajos y en móvil. Se sustituye por filas estilo roadmap del cartel:
   insignia | título + descripción sobre pergamino | arte.
   La banda degrada ESTRECHANDO columnas, no creciendo de alto, así que en móvil
   siguen siendo filas y no se apilan.
   ============================================================ */
.deck-modal-box {
  width: min(94vw, 560px); max-height: 94vh;
  padding: 18px 20px 20px; display: flex; flex-direction: column;
}
.deck-modal-box .lobby-head { margin-bottom: 10px; }
/* VOLVER más grande: es la salida del modal y a 13px se buscaba con lupa,
   sobre todo en móvil, donde además tiene que dar 44px de zona táctil. */
.deck-modal-box .btn-back {
  padding: 11px 20px; font-size: 15px; letter-spacing: 1.5px; min-height: 44px;
}
/* Un color por PREGUNTA, no por paso: verde «a la cola», lila «tu mazo contra la
   IA» y rojo «el mazo DEL RIVAL». Con los dos pasos del asistente en lila no se
   distinguía uno de otro. El rojo es el del enemigo, coherente con que el rojo
   del juego marque el peligro. */
.deck-modal-box.dm-pvp   { --dm-acento: #a8c765; }
.deck-modal-box.dm-bot   { --dm-acento: #b9a3d8; }
.deck-modal-box.dm-rival { --dm-acento: #e05a3a; }
.deck-modal-box .lobby-title {
  color: var(--dm-acento, var(--brass-hi));
  text-shadow: 0 2px 0 #000, 0 0 18px rgba(255,255,255,.12);
  /* Más pequeño que un título de pantalla: al cabecero del modal le quitan sitio
     el botón VOLVER y el contador de paso, y a 26px el rótulo se partía. */
  font-size: clamp(17px, 2.2vw, 22px); letter-spacing: 2px;
}
/* El contador de paso ocupa el hueco que el `::after` del cabecero reserva para
   centrar el título respecto al botón VOLVER. */
.dm-step {
  flex: 0 0 auto; text-align: right; white-space: nowrap;
  font-family: var(--font-display); font-size: 12px; letter-spacing: 1.5px;
  color: var(--gold);
}
/* El "PASO" completo solo donde hay ancho para él. En móvil se queda en "1 / 2",
   que es lo que hace que el rótulo del modal quepa en una línea. */
@media (min-width: 560px) {
  .dm-step { width: 110px; }
  .dm-step::before { content: "PASO "; }
}
.deck-modal-box .lobby-head:has(.dm-step:not(.hidden))::after { display: none; }
/* Recordatorio de TU mazo en el paso 2. Deliberadamente NO parece una lista. */
.dm-context {
  display: flex; align-items: center; gap: 10px; margin: 0 0 10px;
  padding: 7px 10px; border: 1px solid var(--gold-lo); border-radius: 2px;
  background: linear-gradient(180deg, var(--soot), var(--void));
}
.dm-context .dmc-tag {
  font-family: var(--font-display); font-size: 11px; letter-spacing: 2px;
  color: var(--gold); border: 1px solid var(--gold-lo); border-radius: 2px;
  padding: 2px 7px;
}
.dm-context .dmc-name {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display); font-size: 14px; letter-spacing: 1.5px;
  color: var(--bone);
}
.dm-context .dmc-change {
  flex: 0 0 auto; cursor: pointer; font-family: var(--font-ui);
  font-size: 11px; letter-spacing: .5px; text-decoration: underline;
  color: var(--bone-dim); background: none; border: 0; padding: 2px 4px;
}
.dm-context .dmc-change:hover { color: var(--gold-hi); }

.deck-list {
  display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; min-height: 0; padding-right: 4px;
}
/* La lista respira: separada del cabecero y del CTA, y con más aire entre filas.
   Pegada al borde parecía una tabla, no un menú de elección. */
.deck-modal-box .deck-list {
  flex: 1 1 auto; margin: 6px 0 4px; padding: 2px 6px 2px 2px;
}
.deck-modal-box .btn-brass { align-self: center; flex: 0 0 auto; margin-top: 16px; }

.deck-row {
  position: relative; display: grid;
  grid-template-columns: 88px 1fr;
  align-items: stretch; min-height: 88px;
  cursor: pointer; overflow: hidden; text-align: left;
  font-family: var(--font-ui);
  border: 1px solid var(--gold-lo); border-radius: 2px;
  background: linear-gradient(180deg, var(--soot), var(--void));
  box-shadow: inset 0 1px 0 rgba(217,181,109,.2), 0 0 0 1px rgba(0,0,0,.85);
  transition: border-color .14s, box-shadow .16s, transform .08s;
}
.deck-row:hover { border-color: var(--gold); transform: translateY(-1px); }

/* SELECCIÓN BIEN CANTADA. Antes era un filo de latón un poco más claro y no se
   veía. Ahora son CUATRO señales a la vez, en el color del paso:
     · borde de 2px en el color del acento
     · barra gruesa en el canto izquierdo
     · halo exterior
     · chapa «ELEGIDO» arriba a la derecha
   Con esto se distingue de un vistazo y en móvil, que es donde importa. */
.deck-row.selected {
  border: 2px solid var(--dm-acento, var(--gold-hi));
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--dm-acento, var(--gold)) 16%, transparent), transparent 42%),
    linear-gradient(180deg, #221d16, var(--void));
  box-shadow:
    inset 6px 0 0 var(--dm-acento, var(--gold)),
    inset 0 1px 0 rgba(255,255,255,.14),
    0 0 22px color-mix(in srgb, var(--dm-acento, var(--gold)) 45%, transparent),
    0 0 0 1px rgba(0,0,0,.9);
  transform: translateY(-1px);
}
/* La chapa se pinta arriba a la derecha, así que el nombre le reserva sitio: en
   pantallas de 360px o menos se comía las últimas letras ("TERCIO DE LA DAM"). */
.deck-row.selected .dr-name { color: #fff8e8; padding-right: 84px; }
/* Chapa de confirmación: dice qué está pasando, no solo lo colorea. */
.deck-row.selected::after {
  content: "✓ ELEGIDO";
  position: absolute; top: 0; right: 0; z-index: 2;
  font-family: var(--font-display); font-size: 9.5px; letter-spacing: 1.5px;
  color: #16120c; padding: 3px 8px 3px 10px;
  background: var(--dm-acento, var(--gold));
  border-bottom-left-radius: 2px;
  box-shadow: 0 2px 6px rgba(0,0,0,.6);
}
.deck-row:focus-visible { outline: 2px solid var(--gold-hi); outline-offset: 2px; }

/* Sin `color-mix` (navegadores viejos) la selección se queda en borde + barra +
   halo fijo: menos fino, igual de evidente. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .deck-row.selected {
    background: linear-gradient(180deg, #221d16, var(--void));
    box-shadow:
      inset 6px 0 0 var(--dm-acento, var(--gold)),
      inset 0 1px 0 rgba(255,255,255,.14),
      0 0 22px rgba(255,255,255,.22),
      0 0 0 1px rgba(0,0,0,.9);
  }
}

/* Insignia: cuadro negro con hilo de latón a la derecha, separándola del texto. */
.deck-row .dr-badge {
  position: relative; display: flex; align-items: center; justify-content: center;
  background: var(--void);
  border-right: 1px solid var(--gold-lo);
  overflow: hidden;
}
/* Los emblemas de /images/iconos/ ya vienen con su marco: se muestran enteros.
   Van encima del numeral, que solo asoma si la imagen no carga (`onerror` la
   quita del DOM y deja el número a la vista). */
.deck-row .dr-emblem {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; z-index: 1;
}
/* Secta del 619 aún no tiene emblema: usa el retrato de Lefu, que es una carta
   vertical, así que se recorta a la cara en vez de encajarla entera. */
.deck-row .dr-emblem.retrato { object-fit: cover; object-position: 50% 18%; }
.deck-row .dr-num {
  font-family: var(--font-display); font-size: 22px; letter-spacing: 1px;
  color: var(--gold); text-shadow: 0 2px 3px #000;
}

.deck-row .dr-text {
  display: flex; flex-direction: column; justify-content: center;
  min-width: 0; padding: 6px 0 0;
}
.deck-row .dr-name {
  font-family: var(--font-display); font-weight: 700;
  font-size: 16px; letter-spacing: 2.5px; color: var(--bone);
  text-shadow: 0 -1px 0 rgba(0,0,0,.85), 0 2px 4px #000;
  padding: 0 10px 4px;
}
/* La descripción hereda las capas de papel de `.pergamino`. Siempre a dos
   líneas: en una banda horizontal la tercera descuadra la fila entera. */
/* Más grande y más gruesa que antes (era 10.5px con peso 400). Sobre papel, un
   trazo fino a ese tamaño se lee con esfuerzo: el `.mb-sub` del menú, que no
   molesta, es de 10px pero con peso 700. Aquí se sube el cuerpo Y el peso, y se
   suelta el interlineado. */
.deck-row .dr-desc {
  font-size: 12.5px; line-height: 1.4; letter-spacing: .2px; font-weight: 600;
  padding: 6px 10px 7px;
  border-top: 1px solid var(--gold-lo);
  box-shadow: inset 0 1px 0 rgba(255,244,220,.3);
  overflow: hidden;
}
/* El recorte a dos líneas va en el envoltorio de dentro, no en la placa: la
   placa es hija de un flex, y un elemento flex IGNORA `display: -webkit-box` —
   lo blockifica—, así que ahí `-webkit-line-clamp` nunca llegó a hacer nada.
   Lo que recortaba era el `max-height`, y como `overflow` corta en el borde del
   RELLENO y no en el del contenido, el relleno de abajo dejaba asomar media
   tercera línea por debajo del texto. Cuatro de los seis mazos lo enseñaban en
   móvil. Aquí dentro el clamp sí manda, corta entre líneas y pone sus puntos
   suspensivos. */
.deck-row .dr-desc-txt {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* La columna de arte se retiró: con el emblema a 88px ya identifica el mazo, y
   el arte a la derecha solo metía ruido. La fila queda insignia + texto. */

/* Variante compacta: el overlay de revancha, donde no cabe la descripción. */
.deck-list.compact { gap: 6px; max-height: 40vh; }
.deck-list.compact .deck-row { grid-template-columns: 44px 1fr; min-height: 44px; }
.deck-list.compact .dr-desc { display: none; }
.deck-list.compact .dr-text { padding: 0; }
.deck-list.compact .dr-name { font-size: 12px; letter-spacing: 1.5px; padding: 0 8px; }
.deck-list.compact .dr-num { font-size: 14px; }

/* Móvil: SIGUEN siendo filas. Solo se estrechan las columnas laterales. */
@media (max-width: 480px) {
  /* Con el VOLVER más grande, el rótulo se partía en tres líneas. Se achica y se
     retira el hueco que el cabecero reserva para centrarlo: en móvil no hay
     ancho para el lujo de la simetría. */
  /* A 320-390px el cabecero reparte ancho entre VOLVER, rótulo y contador, y
     "MAZO DEL RIVAL" no cabía. Medido en 1440/1280/390/360/320: con esto los
     tres rótulos entran en una línea en todos. */
  .deck-modal-box .lobby-title { font-size: clamp(13px, 4vw, 17px); letter-spacing: 1px; }
  .deck-modal-box .lobby-head::after { display: none; }
  .deck-modal-box .btn-back { padding: 11px 14px; font-size: 13px; }
  .deck-modal-box .dm-step { width: auto; font-size: 10px; }
  .deck-row { grid-template-columns: 68px 1fr; min-height: 68px; }
  .deck-row .dr-name { font-size: 14px; letter-spacing: 1.8px; padding: 0 8px 3px; }
  /* En móvil se baja solo un punto respecto a escritorio (11.5 frente a 12.5) y
     se mantiene el peso 600: es donde juega la mayoría, no el sitio para apretar
     el cuerpo de letra. El `max-height` va CON su relleno y su interlineado, o
     vuelve a asomar media línea. */
  .deck-row .dr-desc { font-size: 11.5px; padding: 5px 8px 6px; }
  .deck-row .dr-num { font-size: 18px; }
}
@media (max-width: 340px) {
  .deck-row { grid-template-columns: 58px 1fr; }
  /* En 320px de ancho no cabe todo: se cae el contador de paso, que es lo menos
     informativo del cabecero —el rótulo ya dice qué estás eligiendo y la barra
     de contexto enseña tu mazo—, y el rótulo recupera el ancho. */
  .deck-modal-box .dm-step { display: none; }
}
/* Ventanas bajas: la fila se aprieta para que la lista de 6 siga cabiendo. */
@media (max-height: 800px) {
  .deck-row { min-height: 56px; }
  .deck-list { gap: 6px; }
}

/* (La rejilla de tarjetas de mazo de la V0.3 —.deck-select / .deck-card— se
   retiró en la V0.11, sustituida por las filas .deck-list / .deck-row.) */
/* En pantallas con sitio, la caja del login se ensancha. Se pide alto además
   de ancho: en un móvil apaisado ensanchar no ayuda. */
@media (min-width: 700px) and (min-height: 640px) {
  .login-box { width: min(92vw, 640px); }
}
/* Pantallas BAJAS (portátiles de 768 px y ventanas no maximizadas): el login
   tiene que caber entero sin barra de scroll interna, o el botón de COLECCIÓN
   queda fuera y el título se pierde al desplazarse. Se recorta lo decorativo
   (chapas, pista de ayuda) y se aprietan mazos y botones; en pantallas altas
   todo sigue igual de holgado. */
/* El umbral sube de 860 a 960 px con el estandarte (V0.11): la imagen ocupa
   ~74 px más que el rótulo de texto al que sustituye, y una ventana de 900 px
   —un 1440×900, que es de los más comunes— pasaba a necesitar scroll para
   llegar a CRÉDITOS. */
@media (max-height: 960px) {
  .login-box { padding: 16px 30px 16px; }
  #screen-login .dogtags,
  #screen-login .login-hint { display: none; }
  #screen-login .game-title img { width: min(100%, 420px, 46vh); }
  #screen-login .login-version { margin-bottom: 10px; }
  #screen-login #name-input { padding: 10px 14px; font-size: 16px; }
  #screen-login .lobby-stats { margin: 8px 0 4px; }
  .menu-actions { margin-top: 8px; gap: 7px; }
  .menu-btn { padding: 7px 12px; }
}

/* Y un escalón más para ventanas bajas (portátil pequeño, ventana a media
   pantalla): se recorta también el hueco bajo la versión. El umbral sube de
   745 a 800 px con el estandarte (V0.11): la imagen ocupa más alto que el
   rótulo de texto que sustituye, y a 768 px el menú dejaba de caber de una vez.
   Medido: CRÉDITOS quedaba 17 px por debajo del borde. */
@media (max-height: 800px) {
  .login-box { padding: 12px 26px 12px; }
  #screen-login .login-version { margin-bottom: 6px; }
  #screen-login .game-title { margin-bottom: 4px; }
  #screen-login .game-title img { width: min(100%, 300px, 34vh); }
  .menu-btn { padding: 5px 12px; }
  .menu-btn .mb-sub { font-size: 9px; }
  .menu-actions { gap: 6px; }
}

/* Móvil / login estrecho o bajo: tarjetas más compactas sin romper el layout. */
@media (max-width: 480px), (max-height: 640px) {
  /* En móvil el estandarte se queda en una banda estrecha: con seis mazos en
     dos columnas, cada píxel de alto que ocupe arriba es uno que hay que
     rascar abajo. */
  #screen-login .game-title img { width: min(100%, 260px, 34vh); }
}

/* ============================================================
   V0.4 — Menú principal + stats en vivo + pantalla VER (lobby)
   ============================================================ */
/* El menú puede crecer (título, mazos, stats, 3 botones): permite scroll
   interno para no desbordar en pantallas bajas. */
.menu-box { max-height: 96vh; overflow-y: auto; }

/* Línea de estadísticas en vivo: 🟢 conectados · ⚔ luchando · 🔎 buscando */
.lobby-stats {
  margin: 16px 0 4px; padding: 7px 12px; border-radius: 8px;
  font-size: 13px; font-weight: 700; letter-spacing: .5px;
  color: var(--ink); text-shadow: 0 1px 2px #000;
  background: linear-gradient(180deg, rgba(0,0,0,.28), rgba(0,0,0,.12));
  border: 1px solid rgba(120,90,50,.4);
}

/* Un color por contador (V0.11): verde lo vivo, rojo lo que está peleando,
   naranja lo que espera. Al llevar color propio, el `hay-rival` que tinta el
   recuadro entero no se los lleva por delante. */
.lobby-stats .ls-on     { color: #a8c765; }
.lobby-stats .ls-fight  { color: #d6421e; }
.lobby-stats .ls-search { color: #e0913a; }
.lobby-stats .ls-sep    { color: var(--bone-faint); }

/* Hay alguien en la cola: el contador del menu se enciende y late. Sin esto,
   dos personas pueden estar mirando el menu a la vez sin enterarse de que basta
   con que una pulse BUSCAR RIVAL. Se apaga solo cuando la cola se vacia.
   El parpadeo sigue siendo VERDE a propósito —es la señal de "hay rival"— y los
   contadores conservan su color dentro. */
/* Con el recuadro tintado de verde, el contador verde se hunde en su propio
   fondo: se aclara solo mientras dura el parpadeo. */
.lobby-stats.hay-rival .ls-on { color: #dff5b4; }
.lobby-stats.hay-rival {
  color: #eaffd6;
  border-color: rgba(150,220,90,.75);
  background: linear-gradient(180deg, rgba(70,120,30,.42), rgba(30,60,12,.25));
  animation: hayRival 1.25s ease-in-out infinite;
}
@keyframes hayRival {
  0%,100% { box-shadow: 0 0 0 0 rgba(150,220,90,0);    filter: brightness(1); }
  50%     { box-shadow: 0 0 14px 2px rgba(150,220,90,.65); filter: brightness(1.22); }
}

/* Cola en la pantalla de espera: dice si estas solo o si deberia emparejar ya. */
.waiting-queue {
  margin-top: 14px; font-size: 12.5px; letter-spacing: .5px;
  color: var(--ink-dim); text-shadow: 0 1px 2px #000;
}
.waiting-queue.hay-rival { color: #d6ffb4; animation: hayRival 1.25s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .lobby-stats.hay-rival, .waiting-queue.hay-rival { animation: none; }
}

/* Botones del menú principal */
/* Tres columnas para las acciones secundarias (VER / COLECCIÓN / CRÉDITOS): con
   cinco mazos en el selector, el menú ya no cabe de una vez en un portátil, y
   darle a cada una su fila lo empujaba aún más abajo. Las principales siguen
   ocupando el ancho entero. */
/* UNA COLUMNA (V0.11). Al soltar el selector de mazo (314 px) sobra alto, y una
   pila de botones a ancho completo es lo que mejor funciona en móvil, que es
   donde juega la mayoría: cada botón es una diana enorme, el rótulo nunca se
   parte en dos líneas y el orden de lectura es uno solo, de arriba abajo. */
.menu-actions {
  display: grid; grid-template-columns: 1fr; gap: 8px; margin-top: 12px;
}
/* Botones del menú: todos son la misma placa de chapa oscura y solo cambia el
   TINTE que los distingue. Antes cada uno traía su propio degradado saturado
   (verde lima, azul, morado) y el menú parecía un panel de mandos; ahora la
   jerarquía la marca el tinte y el filo, no la estridencia. */
.menu-btn {
  position: relative; cursor: pointer; font-family: var(--font-ui);
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 10px 14px; border-radius: 2px;
  color: var(--bone); text-shadow: 0 -1px 0 rgba(0,0,0,.75), 0 2px 3px rgba(0,0,0,.9);
  /* Negro con filo dorado, igual que los paneles: la interfaz entera se cose
     con el mismo hilo de latón. El tinte de bando va debajo, tan apagado que
     solo se nota al comparar dos botones. */
  background:
    linear-gradient(180deg, rgba(178,138,67,.07), transparent 30%),
    linear-gradient(180deg, var(--soot), var(--pit) 55%, var(--void));
  border: 1px solid var(--gold-lo);
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.3),
    inset 0 -1px 0 rgba(0,0,0,.7),
    0 0 0 1px rgba(0,0,0,.85),
    0 5px 16px rgba(0,0,0,.7);
  transition: transform .08s, filter .12s, box-shadow .16s, border-color .16s;
}
.menu-btn .mb-title { font-size: clamp(15px, 1.5vw, 20px); font-weight: 700; letter-spacing: 3px; line-height: 1.05; }
.menu-btn .mb-sub {
  font-size: 10px; font-weight: 700; letter-spacing: 1.2px;
  color: var(--bone-faint); text-shadow: 0 1px 2px #000;
}
/* Al pasar por encima, el filo se enciende: es el latón cogiendo luz. */
.menu-btn:not(:disabled):hover {
  transform: translateY(-1px); border-color: var(--gold);
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.55),
    inset 0 -1px 0 rgba(0,0,0,.7),
    0 0 0 1px rgba(0,0,0,.85),
    0 5px 16px rgba(0,0,0,.7), 0 0 20px rgba(178,138,67,.3);
}
.menu-btn:not(:disabled):active { transform: translateY(1px); filter: brightness(.92); }

/* BUSCAR RIVAL: la acción principal. Filo dorado COMPLETO (no en sombra) y una
   brasa roja desde abajo. Se ve primero por el filo encendido, no por gritar. */
.menu-btn.primary {
  background:
    radial-gradient(130% 190% at 50% 122%, rgba(155,28,24,.7), transparent 60%),
    linear-gradient(180deg, var(--soot), var(--pit) 55%, var(--void));
  border-color: var(--gold);
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.55),
    inset 0 -1px 0 rgba(107,79,34,.9),
    0 0 0 1px rgba(0,0,0,.9),
    0 6px 18px rgba(0,0,0,.75), 0 0 24px rgba(178,138,67,.25);
}
/* El rótulo en VERDE, solo el texto: la chapa sigue negra con su filo dorado.
   Un verde de campaña —oliva encendido— para que se lea sobre negro sin volver
   al verde lima que se retiró del resto de la interfaz. */
.menu-btn.primary .mb-title {
  color: #a8c765;
  text-shadow: 0 -1px 0 rgba(0,0,0,.85), 0 2px 4px rgba(0,0,0,.9), 0 0 14px rgba(168,199,101,.28);
}
.menu-btn.primary .mb-sub { color: rgba(238,227,204,.6); }
/* CONTRA LA IA: el rótulo en el lila de la Novena Falange, el bando de su
   propio tinte, para que los dos botones de empezar partida se distingan de un
   vistazo por el color del texto y no por el relleno. */
.menu-btn.bot .mb-title {
  color: #b9a3d8;
  text-shadow: 0 -1px 0 rgba(0,0,0,.85), 0 2px 4px rgba(0,0,0,.9), 0 0 14px rgba(185,163,216,.26);
}
/* Las secundarias: mismo chasis oscuro, y el bando se reconoce por un TINTE
   tenue, no por el brillo. Aclararlas por encima del panel las hacía parecer
   desactivadas y le robaban la mirada al botón rojo. */
/* Las secundarias: el mismo negro, y el bando solo se insinúa con un tinte por
   debajo. La jerarquía la marca el FILO (dorado entero en la principal, dorado
   en sombra en las demás), no el brillo del relleno. */
.menu-btn.bot { background: linear-gradient(180deg, var(--tint-bot), var(--pit) 58%, var(--void)); }
.menu-btn.watch { background: linear-gradient(180deg, var(--tint-watch), var(--pit) 58%, var(--void)); }
.menu-btn.collection { background: linear-gradient(180deg, var(--tint-coll), var(--pit) 58%, var(--void)); }
.menu-btn.credits { background: linear-gradient(180deg, var(--soot), var(--pit) 58%, var(--void)); }
.menu-btn.campaign { filter: grayscale(.85) brightness(.5); }
.menu-btn:disabled { cursor: not-allowed; }
.menu-btn.campaign .mb-sub { color: var(--bone-faint); opacity: 1; }
.menu-btn:focus-visible { outline: 2px solid var(--khaki-hi); outline-offset: 2px; }

/* Botón volver genérico (cabecera del lobby) */
.btn-back {
  cursor: pointer; font-family: inherit;
  padding: 7px 14px; border-radius: 8px;
  font-size: 13px; font-weight: 800; letter-spacing: 1px;
  color: var(--ink); text-shadow: 0 1px 2px #000;
  background: linear-gradient(180deg, var(--metal-3), var(--metal-2) 60%, var(--metal-1));
  border: 2px solid #17150f;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 3px 8px rgba(0,0,0,.5);
  transition: filter .12s, transform .08s;
}
.btn-back:hover { filter: brightness(1.12); }
.btn-back:active { transform: translateY(1px); }

/* Pantalla VER / lobby */
.lobby-box {
  width: min(94vw, 620px); max-height: 92vh;
  padding: 20px 22px 22px; display: flex; flex-direction: column;
}
.lobby-head { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
.lobby-title {
  flex: 1; text-align: center; letter-spacing: 3px; font-weight: 800;
  font-size: clamp(18px, 3.4vw, 26px); color: var(--brass-hi);
  text-shadow: 0 2px 0 #000, 0 0 14px rgba(200,150,60,.3);
}
.lobby-head .btn-back { flex: 0 0 auto; }
/* Placeholder invisible para centrar el título respecto al botón volver */
.lobby-head::after { content: ""; flex: 0 0 auto; width: 96px; }
.lobby-box .lobby-stats { margin: 10px 0 12px; text-align: center; }

.lobby-list {
  flex: 1 1 auto; overflow-y: auto; min-height: 80px;
  display: flex; flex-direction: column; gap: 8px; padding-right: 4px;
}
.lobby-empty {
  text-align: center; color: var(--ink-faint); letter-spacing: 2px;
  font-size: 14px; padding: 32px 8px;
}
.lobby-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 8px;
  background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(0,0,0,.28));
  border: 1px solid #17130b;
  box-shadow: inset 0 0 0 1px rgba(120,116,98,.18);
}
.lr-players { flex: 1 1 auto; min-width: 0; font-size: 15px; letter-spacing: .5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lr-players b { color: var(--ink); font-weight: 800; }
.lr-players .vs { margin: 0 7px; color: var(--red-hi); font-weight: 800; font-size: 12px; letter-spacing: 1px; }
.lr-meta { flex: 0 0 auto; font-size: 12px; color: var(--ink-dim); letter-spacing: .5px; white-space: nowrap; }
/* Chapa de partida contra la máquina. Va en el lila del bando de la IA, el mismo
   del botón CONTRA LA IA del menú, para que se reconozca sin leerla. */
.lr-ia {
  display: inline-block; margin-left: 9px; vertical-align: 1px;
  font-family: var(--font-display); font-size: 10px; letter-spacing: 1.5px;
  color: #b9a3d8; padding: 1px 6px;
  border: 1px solid rgba(185,163,216,.55); border-radius: 2px;
  background: rgba(69,58,88,.35);
}
.lobby-row.es-ia { border-color: rgba(185,163,216,.28); }
.lr-watch {
  flex: 0 0 auto; cursor: pointer; font-family: var(--font-display);
  padding: 7px 18px; border-radius: 2px;
  font-size: 15px; font-weight: 700; letter-spacing: 2px;
  color: var(--bone); text-shadow: 0 -1px 0 rgba(0,0,0,.6), 0 2px 3px #000;
  background: linear-gradient(180deg, #2c3742, #232c36 58%, #12171c);
  border: 2px solid #0b0907;
  box-shadow: inset 0 1px 0 rgba(255,244,220,.14), inset 0 0 0 1px rgba(74,70,60,.45), 0 3px 8px rgba(0,0,0,.6);
  transition: filter .12s, transform .08s;
}
.lr-watch:hover { filter: brightness(1.12); transform: translateY(-1px); }
.lr-watch:active { transform: translateY(1px); }
.lobby-list::-webkit-scrollbar { width: 8px; }
.lobby-list::-webkit-scrollbar-track { background: rgba(0,0,0,.3); }
.lobby-list::-webkit-scrollbar-thumb { background: var(--metal-2); border-radius: 4px; }

@media (max-width: 520px) {
  .lobby-row { flex-wrap: wrap; gap: 6px; }
  .lr-players { flex: 1 1 100%; }
  .lr-meta { flex: 1 1 auto; }
}

/* Botón "← VOLVER" fijo del espectador (V0.4): visible mientras se especta */
.spectate-back {
  position: fixed; top: 8px; left: 8px; z-index: 262;
  cursor: pointer; font-family: inherit;
  padding: 7px 15px; border-radius: 20px;
  font-size: 13px; font-weight: 800; letter-spacing: 1px;
  color: #f0e6ff; text-shadow: 0 1px 2px #000;
  background: linear-gradient(180deg, #6a4a9a, #3a2560 60%, #241041);
  border: 2px solid #120a20;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 4px 12px rgba(0,0,0,.6);
  transition: filter .12s, transform .08s;
}
.spectate-back:hover { filter: brightness(1.12); }
.spectate-back:active { transform: translateY(1px); }
.spectate-back.hidden { display: none; }

/* ---------- Esperando ---------- */
.waiting-box h2 { letter-spacing: 3px; font-weight: 700; margin-bottom: 8px; }
.waiting-box p { color: var(--ink-dim); font-size: 13px; letter-spacing: 1px; }
.radar {
  width: 92px; height: 92px; margin: 4px auto 22px; border-radius: 50%;
  border: 2px solid rgba(140,200,90,.35);
  background: radial-gradient(circle, rgba(90,150,60,.12), transparent 70%);
  position: relative; overflow: hidden;
}
.radar span {
  position: absolute; inset: 0;
  background: conic-gradient(from 0deg, rgba(150,220,90,.55), transparent 25%);
  animation: sweep 1.8s linear infinite;
}
@keyframes sweep { to { transform: rotate(360deg); } }

/* Cancelar búsqueda (V0.10). Va apagado respecto al latón de "Jugar de nuevo":
   es una salida, no la acción principal de la pantalla, y compite con un radar
   que ya se mueve. Se enciende al pasar por encima. */
.cancel-search {
  margin-top: 20px;
  padding: 9px 20px;
  font-size: 13px;
  letter-spacing: 1px;
  filter: saturate(.35) brightness(.82);
}
.cancel-search:hover:not(:disabled) { filter: none; }
.cancel-search:disabled { opacity: .5; cursor: default; }

/* ============================================================
   PARTIDA - layout
   ============================================================ */
.game-grid {
  display: grid;
  grid-template-columns: 1fr clamp(160px, 15vw, 230px);
  width: 100vw; height: 100vh;
  /* El recorte de iOS se veía AQUÍ: la rejilla de la partida medía 852 px en un
     iPhone 16 con 659 a la vista, así que la fila de la mano caía entera debajo
     de la barra de Safari. */
  height: 100dvh;
  gap: 8px; padding: 8px;
}
.battlefield {
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0; min-height: 0;
  padding: 6px 14px 0;
  border-radius: 2px;
  /* Con el skin `texture.png` detrás, el campo se APARTA: sin marco propio ni
     relleno opaco, para que se vean sus bandas y su filigrana dorada. Queda un
     velo muy tenue que asegura el contraste de los esbirros contra el suelo
     grabado, y nada más. */
  border: 1px solid transparent;
  /* El velo es el mando de volumen del skin: sin él, las bandas de humo naranja
     de `texture.png` salen a plena saturación y los esbirros pelean con el
     fondo. Calibrado midiendo el contraste del texto sobre la banda. */
  background: linear-gradient(rgba(6,6,5,.6), rgba(6,6,5,.5));
  box-shadow: none;
}

/* filas de tablero — dominan el viewport */
.board-row {
  position: relative;
  flex: 3 1 0; min-height: var(--minion-h);
  display: flex; align-items: center; justify-content: center;
  gap: 8px; padding: 6px 10px;
  border-radius: 2px;
  /* Las bandas ya las dibuja el skin, así que la fila deja de pintar la suya:
     solo un velo para que los esbirros no se pierdan sobre el suelo grabado. */
  border: 1px solid transparent;
  background: linear-gradient(rgba(0,0,0,.3), rgba(0,0,0,.16));
  box-shadow: inset 0 0 40px rgba(0,0,0,.45);
}
/* Antes: `--ink-faint` (alfa .42) Y ADEMÁS `opacity: .5`, o sea un 21% efectivo
   sobre el skin del tablero. Se leía a duras penas. Ahora va en hueso a tamaño
   de rótulo, con sombra para despegarlo del suelo grabado de la textura. */
.board-row.empty::after {
  content: "SIN ESBIRROS";
  font-family: var(--font-display);
  color: rgba(238,227,204,.82); letter-spacing: 5px;
  font-size: clamp(15px, 1.8vw, 22px);
  text-shadow: 0 2px 4px #000, 0 0 16px rgba(0,0,0,.9);
}
/* V0.3 — fila abarrotada (>7 esbirros): reduce el tamaño local del esbirro para
   que quepan hasta 10 sin desbordar ni scroll horizontal. Hay que re-declarar
   AMBAS variables aqui: --minion-w se computa en :root a partir del --minion-h de
   :root, asi que redeclararla en la fila la recalcula con el --minion-h reducido. */
.board-row.crowded {
  --minion-h: clamp(84px, 9.4vw, 138px);
  --minion-w: calc(var(--minion-h) * 0.74);
  gap: 5px;
}

/* franja central / turno */
.mid-strip { flex: 0 0 auto; display: flex; align-items: center; gap: 12px; padding: 1px 0; }
.mid-line { flex: 1; height: 2px; background: linear-gradient(90deg, transparent, rgba(180,145,47,.5), transparent); }
.turn-indicator {
  display: flex; flex-direction: column; align-items: center; line-height: 1.05;
  padding: 3px 18px; border-radius: 2px; min-width: 150px;
  background: linear-gradient(180deg, var(--soot), var(--void));
  border: 1px solid var(--gold-lo);
  box-shadow: inset 0 1px 0 rgba(217,181,109,.28), 0 0 0 1px rgba(0,0,0,.9);
}
.turn-indicator .ti-label { font-weight: 800; letter-spacing: 3px; font-size: 15px; }
.turn-indicator .ti-turn { font-size: 10px; color: var(--ink-dim); letter-spacing: 2px; }
/* Tu turno en hueso encendido, el suyo en sangre apagada: la diferencia la marca
   el BRILLO, que se lee de reojo, no el matiz verde/rojo de antes. */
.turn-indicator.yours .ti-label { color: var(--bone); text-shadow: 0 0 12px rgba(229,222,201,.45), 0 2px 3px #000; }
.turn-indicator.theirs .ti-label { color: var(--blood-hi); text-shadow: 0 2px 3px #000; }

/* ============================================================
   Franja de heroe (info jugador)
   ============================================================ */
.hero-strip {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 12px;
  padding: 3px 10px; min-height: 58px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--soot), rgba(0,0,0,.35));
  border: 1px solid rgba(107,79,34,.55);
}
/* El asiento con el turno: el latón se enciende del todo. Es la misma señal que
   usa el resto de la interfaz —filo encendido = activo—, y así el rojo se queda
   solo para el daño y el peligro. */
.hero-strip.active-turn {
  border-color: var(--gold);
  box-shadow: inset 0 1px 0 rgba(217,181,109,.4), 0 0 18px rgba(178,138,67,.35);
}

.hero-portrait {
  position: relative; width: 54px; height: 54px; flex: 0 0 auto;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, #7f7c68, #45433a 60%, #23221b);
  border: 3px solid #17150f;
  box-shadow: inset 0 2px 5px rgba(255,255,255,.25), inset 0 -4px 8px rgba(0,0,0,.6), 0 3px 8px rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center;
  font-size: 26px;
}
.hero-portrait.opp { cursor: default; }
/* Objetivo legal de ataque: anillo rojo pulsante + icono de espadas + escala.
   Muy visible tanto en seleccion por clic como durante el arrastre. */
.hero-portrait.target-legal {
  cursor: crosshair; z-index: 6;
  transform: scale(1.08);
  animation: heroTargetPulse 1s ease-in-out infinite;
}
.hero-portrait.target-legal::after {
  content: "⚔"; position: absolute; inset: 0; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  font-size: 30px; line-height: 1; color: #fff;
  text-shadow: 0 0 6px #000, 0 2px 3px rgba(0,0,0,.9);
  pointer-events: none;
}
@keyframes heroTargetPulse {
  0%,100% {
    box-shadow: 0 0 0 3px var(--red), 0 0 14px rgba(255,90,72,.7),
                inset 0 2px 5px rgba(255,255,255,.25), inset 0 -4px 8px rgba(0,0,0,.6);
  }
  50% {
    box-shadow: 0 0 0 6px var(--red-hi), 0 0 26px rgba(255,90,72,1),
                inset 0 2px 5px rgba(255,255,255,.25), inset 0 -4px 8px rgba(0,0,0,.6);
  }
}
.hp-badge {
  position: absolute; bottom: -6px; right: -8px;
  min-width: 24px; height: 24px; padding: 0 3px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 15px; color: #fff;
  background: radial-gradient(circle at 35% 30%, #ff6d5a, var(--red) 60%, #6e120b);
  border: 2px solid #2a0705; border-radius: 50%;
  text-shadow: 0 1px 2px #000; box-shadow: 0 2px 4px rgba(0,0,0,.5);
}
.hero-meta { display: flex; flex-direction: column; gap: 3px; min-width: 96px; }
.hero-name {
  font-weight: 700; letter-spacing: 1px; font-size: 15px; color: var(--ink);
  max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mana-line { display: flex; align-items: center; gap: 8px; }
.mana-num { font-size: 12px; color: var(--brass-hi); font-weight: 700; letter-spacing: 1px; }

/* Sellos del Ritual (V0.12, mazo LNF Ritual). Van pegados al mana y se ven en
   las dos franjas. Para poner los iconos definitivos basta con darle
   background-image a .sello[data-sello="N"] y bajarle el color al numero: el
   marcado ya trae el data-attribute preparado. */
.sellos { display: flex; gap: 4px; align-items: center; }
.sello {
  width: 14px; height: 14px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 700; line-height: 1;
  background: #1b1710; border: 1px solid var(--gold-lo);
  color: #4a4030; background-size: cover; background-position: center;
}
.sello.on {
  background: radial-gradient(circle at 40% 35%, var(--gold-hi), var(--gold-lo));
  border-color: var(--gold-hi); color: #17130b;
  box-shadow: 0 0 6px rgba(217,181,109,.6);
}
/* Ritual completo: los cinco laten a la vez. Es el aviso de que el dios puede
   bajar en cualquier momento, y lo tiene que ver tambien el rival. */
.sellos.completo .sello.on { animation: sello-ritual 1.6s ease-in-out infinite; }
@keyframes sello-ritual {
  0%, 100% { box-shadow: 0 0 6px rgba(217,181,109,.6); }
  50%      { box-shadow: 0 0 13px rgba(217,181,109,1); }
}

/* bidones de mana */
.mana-pips { display: flex; gap: 3px; flex-wrap: wrap; max-width: 160px; }
.pip {
  width: 9px; height: 13px; border-radius: 2px; position: relative;
  background: #241f14; border: 1px solid #0b0906;
  box-shadow: inset 0 0 2px #000;
}
.pip::before {
  content: ""; position: absolute; top: -3px; left: 2px; width: 4px; height: 3px;
  background: #3a3524; border-radius: 1px 1px 0 0;
}
.pip.full { background: linear-gradient(#c9d24a, #6f7d1c); box-shadow: 0 0 4px rgba(190,210,70,.5); }
.pip.full::before { background: #9aa63a; }
.pip.temp { background: linear-gradient(#f0cf6a, #a8791f); box-shadow: 0 0 5px rgba(235,190,80,.75); }
.pip.temp::before { background: #d9b24a; }

/* zonas: mazo / cementerio / mano rival / fatiga */
.zones { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap;
         justify-content: flex-end; }
.zone {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  min-width: 40px; padding: 3px 4px; border-radius: 5px;
  background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.30));
  border: 1px solid #17130b;
}
.zone .zicon { font-size: 16px; line-height: 1; color: var(--metal-hi); }
.zone .zcount { font-weight: 800; font-size: 15px; color: var(--ink); }
.zone .zlbl { font-size: 8px; letter-spacing: 1px; color: var(--ink-faint); }
.zone.deck .zicon { color: #b0a678; }
.zone.grave .zicon { color: #9a8f7a; }
.zone.fatigue .zcount { color: var(--red-hi); }

/* mano rival: dorsos */
.hand-backs { display: flex; align-items: center; }
.hand-backs .cardback {
  width: 16px; height: 24px; margin-left: -9px; border-radius: 3px;
  background: linear-gradient(150deg, #4a3a1e, #241608);
  border: 1px solid #0d0a05;
  box-shadow: 1px 0 2px rgba(0,0,0,.5), inset 0 0 0 1px rgba(180,145,47,.25);
}
.hand-backs .hb-count {
  margin-left: 6px; font-weight: 800; font-size: 14px; color: var(--ink); align-self: center;
}

/* ============================================================
   Esbirros en tablero
   ============================================================ */
.minion {
  position: relative; flex: 0 0 auto;
  width: var(--minion-w); height: var(--minion-h);
  border-radius: 8px;
  /* V0.4 — fondo de respaldo oscuro tras la imagen (PNG ya transparente):
     el tile queda sólido aunque asomen bordes transparentes del retrato. */
  background-color: #14100a;
  background-repeat: no-repeat;
  /* V0.4 — recorte ceñido al óvalo del retrato (parte superior de la carta):
     zoom + posición para que NO asomen la gema de coste ni los números
     impresos; los únicos números en mesa son los badges superpuestos.
     Porcentual => sirve para los dos tamaños de arte (1062x1482 y 785x1100). */
  background-size: 235%;
  background-position: 50% 15%;
  border: 2px solid #0d0a05;
  box-shadow: inset 0 0 0 1px rgba(120,116,98,.4), 0 4px 8px rgba(0,0,0,.55);
  cursor: default; overflow: hidden;
  transition: transform .1s, box-shadow .12s;
}
.minion .mname {
  position: absolute; left: 0; right: 0; bottom: 0;
  font-size: 10px; letter-spacing: .3px; text-align: center;
  padding: 10px 3px 3px; color: var(--ink);
  background: linear-gradient(transparent, rgba(0,0,0,.85));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* estadisticas */
.mstat {
  position: absolute; bottom: -5px; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 19px; color: #fff; text-shadow: 0 1px 2px #000;
  border-radius: 50%; border: 2px solid #17120a;
}
.mstat.atk {
  left: -5px;
  background: radial-gradient(circle at 35% 30%, #ffe37a, var(--gold) 60%, #7a5c12);
  color: #2a1e05; text-shadow: 0 1px 0 rgba(255,255,255,.4);
}
.mstat.hp {
  right: -5px;
  background: radial-gradient(circle at 35% 30%, #ff8f7c, var(--red) 60%, #6e120b);
}
.mstat.hp.dmg {
  background: radial-gradient(circle at 35% 30%, #ff5a48, #ff2013 60%, #8a0c04);
  box-shadow: 0 0 8px rgba(255,60,40,.8);
}
/* V0.3 — estadistica bufada (por encima del valor impreso) en verde brillante.
   El ataque siempre; la vida solo cuando esta sin daño (dmg manda el rojo). */
.mstat.atk.buffed {
  background: radial-gradient(circle at 35% 30%, #b6ff7a, #7ed957 58%, #2f8a1e);
  color: #0e2405; text-shadow: 0 1px 0 rgba(255,255,255,.45);
  box-shadow: 0 0 9px rgba(126,217,87,.9);
}
.mstat.hp.buffed {
  background: radial-gradient(circle at 35% 30%, #b6ff7a, #7ed957 58%, #2f8a1e);
  color: #0e2405; text-shadow: 0 1px 0 rgba(255,255,255,.45);
  box-shadow: 0 0 9px rgba(126,217,87,.9);
}
.minion .multi {
  position: absolute; top: 2px; right: 2px;
  font-size: 9px; font-weight: 800; color: #2a1e05;
  background: var(--gold); border-radius: 4px; padding: 0 3px;
  border: 1px solid #17120a;
}

/* indicadores de estado */
/* Listo para atacar: filo de hueso. Es la señal que más veces por turno tiene
   que encontrar el ojo, así que se lleva el valor más alto de la paleta. */
/* Hueso CLARO y por fuera, para no confundirse con el oro del Escudo divino,
   que va por dentro y saturado. Los cuatro canales del esbirro siguen intactos:
   box-shadow = interacción, ::before = escudo, outline = provocar. El acero frío
   del Provocar se queda como estaba: es lo único cool de la paleta y justo por
   eso sigue haciendo su trabajo. */
.minion.ready {
  box-shadow: inset 0 0 0 1px rgba(74,70,60,.5), 0 0 0 2px #cfc9ba, 0 0 15px rgba(229,222,201,.5);
  cursor: pointer;
}
.minion.ready:hover { transform: translateY(-4px); }
.minion.selected {
  box-shadow: inset 0 0 0 1px rgba(74,70,60,.5), 0 0 0 3px var(--bone), 0 0 22px rgba(229,222,201,.8);
  transform: translateY(-6px);
}
.minion.stealth { opacity: .55; }
.minion.stealth::after {
  content: ""; position: absolute; inset: 0; border: 2px dashed rgba(210,210,230,.5);
  border-radius: 8px; pointer-events: none;
  background: repeating-linear-gradient(45deg, rgba(180,190,210,.06) 0 5px, transparent 5px 10px);
}
/* ---- TRES CANALES QUE NO SE PISAN -------------------------------------
   Provocar, Escudo divino y los estados de interaccion (listo / seleccionado /
   objetivo legal) coincidian los tres en `box-shadow` de `.minion`, con la
   misma especificidad. Ganaba el ultimo del fichero, asi que tu propio esbirro
   con Provocar NUNCA se veia listo para atacar: el anillo verde existia y lo
   tapaba el marco oliva.

   Ahora cada cosa tiene su propiedad:
     · box-shadow  -> SOLO estados de interaccion (listo, seleccionado, objetivo)
     · ::before    -> Escudo divino (anillo dorado)
     · outline     -> Provocar (anillo de acero, propiedad independiente)
     · .taunt-badge-> Provocar (chapa de escudo, ahora legible de un vistazo)
   Asi un esbirro puede mostrar las tres a la vez sin que ninguna gane. ---- */

/* escudo divino: anillo DORADO por DENTRO, sin tocar box-shadow.
   `inset: 0`, no `-2px`: `.minion` lleva `overflow: hidden` (recorta a proposito
   los circulos de ataque y vida, que asoman medio hundidos), asi que cualquier
   pseudo que se salga del recuadro se pierde entero. Se probo por fuera y el
   anillo desaparecia salvo esquirlas en las esquinas: quedaba peor que antes. */
.minion.divine::before {
  content: ""; position: absolute; inset: 0; border-radius: 7px; pointer-events: none;
  z-index: 2;
  border: 3px solid rgba(255,228,140,.95);
  box-shadow: inset 0 0 10px 2px rgba(255,205,70,.75);
}


/* V0.16 — Bendecido. Halo blanco POR FUERA, no un filo por dentro: el Escudo
   divino ya se pinta con un anillo dorado interior (`.minion.divine::before`), y
   las dos marcas conviven en el mismo esbirro constantemente —medio mazo de la
   Inquisición bendice a lo que ya lleva escudo—, así que tienen que ocupar
   sitios distintos para poder leerse a la vez. */
.minion.bendecido {
  box-shadow: 0 0 0 2px rgba(255,248,232,.9), 0 0 14px 2px rgba(255,236,190,.55);
}
/* Cambio de mano inicial (V0.12). Ocupa la pantalla porque es una decision y no
   un aviso: mientras dure no hay nada mas que hacer. */
.mull-box {
  width: min(96vw, 900px); max-height: 92vh; overflow-y: auto;
  max-height: 92dvh;
  padding: 18px 20px 20px; text-align: center;
}
.mull-box h1 {
  font-family: var(--font-display); font-size: 26px; letter-spacing: 3px;
  color: var(--gold-hi); margin: 0 0 4px;
}
#mull-hint { color: rgba(238,227,204,.72); font-size: 13px; margin: 0 0 14px; }
.mull-cards {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 12px; margin-bottom: 16px;
}
.mull-cards.bloqueada { pointer-events: none; opacity: .78; }
.mull-card {
  position: relative; cursor: pointer;
  /* Mismo lienzo que la coleccion (785x1100) escalado a la caja: sin ancho fijo
     y `overflow:hidden` las caras salen a tamano real y cabe una sola. */
  width: clamp(96px, 17vw, 168px); aspect-ratio: 785 / 1100;
  overflow: hidden; border-radius: 8px;
  background-color: #140e05; background-size: contain;
  background-position: center; background-repeat: no-repeat;
  box-shadow: 0 3px 9px rgba(0,0,0,.55);
  transition: transform .14s, filter .14s;
}
.mull-card .card-face { transform-origin: top left; }
.mull-card:hover { transform: translateY(-5px); }
/* Marcada para cambiar: se apaga y se cruza, para que de un vistazo se vea
   cuales se van y cuales se quedan sin tener que leer nada. */
.mull-card.picked { filter: grayscale(.85) brightness(.55); transform: none; }
.mull-card .mull-mark {
  position: absolute; top: 50%; left: 50%; z-index: 3;
  transform: translate(-50%, -50%) rotate(-14deg);
  font-family: var(--font-display); font-size: 15px; letter-spacing: 2px;
  color: #f0c0b4; background: rgba(90,20,14,.86);
  border: 2px solid var(--red); border-radius: 4px; padding: 3px 10px;
  opacity: 0; transition: opacity .14s; pointer-events: none; white-space: nowrap;
}
.mull-card.picked .mull-mark { opacity: 1; }
/* Irrenunciable (Lefu): ni se marca ni se pincha, y se dice por que. */
.mull-card.fija { cursor: default; box-shadow: 0 0 0 2px var(--gold-hi), 0 3px 9px rgba(0,0,0,.55); }
.mull-card.fija:hover { transform: none; }
.mull-card .mull-lock {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  font-family: var(--font-display); font-size: 9px; letter-spacing: 1px;
  color: #17130b; background: var(--gold-hi);
  padding: 2px 0; text-align: center; pointer-events: none;
}
.mull-actions { display: flex; align-items: center; justify-content: center; gap: 14px; }
.mull-timer {
  font-family: var(--font-display); font-size: 18px; letter-spacing: 1px;
  color: var(--brass-hi); min-width: 44px;
}
.mull-timer.urgente { color: #ff9d8a; }
.mull-wait {
  color: var(--gold-hi); font-size: 13px; letter-spacing: 1px;
  margin: 12px 0 0; animation: mull-latido 1.4s ease-in-out infinite;
}
@keyframes mull-latido { 0%,100% { opacity: .55; } 50% { opacity: 1; } }
@media (max-width: 560px) {
  .mull-box { padding: 12px 10px 14px; }
  .mull-box h1 { font-size: 20px; letter-spacing: 2px; }
  .mull-cards { gap: 8px; }
  .mull-card .mull-mark { font-size: 12px; padding: 2px 7px; }
}

/* Contador de esbirros de cada mesa (V0.12). Discreto en la esquina: informa sin
   competir con las cartas, y solo se enciende cuando el hueco empieza a importar. */
.board-count {
  position: absolute; top: 4px; right: 8px; z-index: 5;
  font-family: var(--font-display); font-size: 11px; letter-spacing: 1px;
  color: rgba(238,227,204,.5);
  background: rgba(10,8,5,.55); border: 1px solid rgba(178,138,67,.28);
  border-radius: 3px; padding: 1px 6px;
  pointer-events: none; user-select: none;
  transition: color .18s, border-color .18s, background .18s;
}
.board-count.casi { color: var(--gold-hi); border-color: var(--gold); }
.board-count.lleno {
  color: #f0c0b4; border-color: var(--red);
  background: rgba(90,20,14,.55);
}
@media (max-width: 560px) {
  .board-count { font-size: 10px; padding: 0 4px; top: 3px; right: 5px; }
}

/* Congelado (V0.12, Howard): velo azul y escarcha. Se lee de un vistazo que ese
   esbirro no va a atacar este turno. */
.minion.congelado { filter: saturate(.5) brightness(1.05); }
.minion.congelado::after {
  content: "\2744"; position: absolute; top: 2px; right: 3px;
  font-size: 13px; color: #bfe6ff; text-shadow: 0 0 4px #4aa8e0, 0 1px 2px #000;
  pointer-events: none; z-index: 4;
}
.minion.congelado::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(160deg, rgba(120,200,255,.30), rgba(60,140,210,.16));
  box-shadow: inset 0 0 0 1px rgba(180,230,255,.55);
  pointer-events: none; z-index: 3;
}

/* Boca abajo (V0.12, Tumuertopepe): entra dormido y no se le puede tocar hasta
   que se da la vuelta. Se oscurece y se marca, porque es una amenaza anunciada:
   el rival TIENE que verla venir para poder prepararse. */
.minion.bocabajo { filter: brightness(.45) saturate(.35); }
.minion.bocabajo::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: repeating-linear-gradient(45deg, rgba(0,0,0,.55) 0 6px, rgba(20,15,8,.55) 6px 12px);
  box-shadow: inset 0 0 0 2px var(--gold-lo);
  pointer-events: none; z-index: 3;
}
.minion.bocabajo::after {
  content: "\25C8"; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%); font-size: 22px; color: var(--gold-hi);
  text-shadow: 0 0 8px #000; pointer-events: none; z-index: 4;
}


/* provocar: anillo de ACERO por `outline`, que no compite con box-shadow.
   Frio y metalico a proposito, para que no se confunda con el oro del escudo
   divino ni con el verde de "puedo atacar". */
.minion.taunt {
  outline: 3px solid #7f93ad;
  outline-offset: -3px;
  border-color: #48586b;
}
/* Chapa de escudo, arriba y centrada. Antes eran 18x20 en oliva sobre tablero
   oscuro y se perdia: se agranda, se le pone silueta clara y un simbolo dentro,
   para reconocerlo sin tener que seleccionar un atacante y descubrirlo. */
.minion.taunt .taunt-badge {
  position: absolute; top: 2px; left: 50%; transform: translateX(-50%);
  width: 24px; height: 27px; z-index: 4;
  background: linear-gradient(180deg, #dfe8f2 0%, #9fb2c8 45%, #5d748c 100%);
  border: 1px solid #1b2530;
  clip-path: polygon(50% 0, 100% 22%, 100% 62%, 50% 100%, 0 62%, 0 22%);
  box-shadow: 0 2px 5px rgba(0,0,0,.75), 0 0 10px rgba(150,190,235,.55);
}
/* La cruz interior: dos barras que rematan la silueta de escudo. */
.minion.taunt .taunt-badge::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(#22303d, #22303d) center/4px 60% no-repeat,
    linear-gradient(#22303d, #22303d) center 42%/62% 4px no-repeat;
}
/* DENTRO del recuadro y arriba en las dos filas. Fuera se la comia el
   `overflow: hidden` (se perdian la punta y la barra horizontal de la cruz, y
   quedaba una pestana plana con forma de T). Dentro se ve entera, no tapa el
   nombre —que va abajo— y no choca con el contador de ataques, que va arriba a
   la derecha. Misma posicion en las dos filas: se aprende una vez. */

/* arrasar (V0.8): filo ígneo en la base, por donde desborda el daño sobrante.
   Va en un hijo propio y no en ::after porque ese pseudo ya lo usa .stealth. */
.trample-edge { display: none; }
.minion.trample .trample-edge {
  display: block;
  position: absolute; left: 6%; right: 6%; bottom: -3px; height: 5px;
  border-radius: 3px; pointer-events: none; z-index: 3;
  background: linear-gradient(90deg, transparent, #ff9b2f 25%, #ffd66a 50%, #ff9b2f 75%, transparent);
  box-shadow: 0 0 9px 2px rgba(255,140,40,.75);
}

/* objetivo legal (esbirro enemigo): borde rojo mas grueso + leve pulso */
.minion.target-legal {
  cursor: crosshair;
  border-color: var(--red-hi);
  box-shadow: inset 0 0 0 2px rgba(255,90,72,.9), 0 0 0 4px var(--red), 0 0 18px rgba(255,90,72,.9);
  animation: minionTargetPulse 1s ease-in-out infinite;
}
.minion.target-legal:hover { transform: translateY(-4px); }

/* Mientras apuntas con un atacante, lo que NO puedes atacar se apaga. Antes
   solo se encendian los objetivos legales, y con la mesa llena el jugador tenia
   que ir probando para descubrir que un Provocar le tapaba el resto. Apagar lo
   ilegal deja la regla a la vista sin leer nada. */
.opp-board.apuntando .minion:not(.target-legal) {
  opacity: .38;
  filter: grayscale(.7);
}
.opp-board.apuntando .minion:not(.target-legal):hover { transform: none; cursor: not-allowed; }
/* El heroe rival, igual: si hay Provocar delante, se apaga su retrato. */
#opp-hero.apuntando .hero-portrait:not(.target-legal) { opacity: .45; filter: grayscale(.6); }
@keyframes minionTargetPulse {
  0%,100% { filter: brightness(1);    box-shadow: inset 0 0 0 2px rgba(255,90,72,.9), 0 0 0 4px var(--red),     0 0 16px rgba(255,90,72,.8); }
  50%     { filter: brightness(1.28); box-shadow: inset 0 0 0 2px rgba(255,120,100,1), 0 0 0 5px var(--red-hi), 0 0 26px rgba(255,90,72,1); }
}
@keyframes pulseTarget {
  0%,100% { filter: brightness(1); }
  50% { filter: brightness(1.25); }
}

/* huecos de insercion en tu tablero */
.islot {
  flex: 0 0 auto; width: 10px; align-self: stretch; border-radius: 6px;
  display: none;
}
.you-board.placing .islot {
  display: block; width: clamp(44px, 5vw, 72px);
  background:
    linear-gradient(rgba(150,235,95,.14), rgba(120,200,70,.06)),
    repeating-linear-gradient(45deg, rgba(150,235,95,.32) 0 8px, rgba(120,200,70,.10) 8px 16px);
  border: 3px dashed rgba(160,240,100,.85);
  box-shadow: inset 0 0 10px rgba(150,235,95,.35);
  cursor: pointer;
}
.you-board.placing .islot:hover {
  background: rgba(150,235,95,.45);
  box-shadow: 0 0 16px rgba(150,235,95,.7), inset 0 0 10px rgba(255,255,255,.4);
}

/* ============================================================
   Mano propia
   ============================================================ */
.hand-row {
  position: relative;
  flex: 0 0 auto; min-height: calc(var(--card-w) * 1.30);
  margin-bottom: -6px; /* la mano puede solapar ligeramente el borde inferior */
  display: flex; align-items: flex-end; justify-content: center;
  gap: 8px; padding: 4px 6px 0; overflow: visible;
  z-index: 5;
}
.card {
  position: relative; flex: 0 0 auto;
  width: var(--card-w); height: calc(var(--card-w) * 1.42);
  border-radius: 7px;
  background-size: cover; background-position: center;
  /* V0.4 — los PNG ya traen fondo transparente: nada de rectángulo negro.
     La sombra la da un drop-shadow que sigue la silueta de la carta. */
  background-color: transparent;
  filter: drop-shadow(0 5px 7px rgba(0,0,0,.6));
  transition: transform .12s, filter .12s;
  cursor: default;
  will-change: transform;
}
.card:hover { transform: translateY(-10px) scale(1.04); z-index: 20; filter: drop-shadow(0 12px 16px rgba(0,0,0,.7)); }
/* Las TRES señales del combate, ahora en la paleta y sin pisarse (V0.11):
     · puedes JUGARLA (mano)      → caqui, luz de latón
     · puede ATACAR (mesa)        → hueso, filo encendido
     · es OBJETIVO legal          → sangre
   Antes las dos primeras eran el mismo verde lima y había que mirar dos veces
   para saber cuál te estaba hablando. */
.card.playable {
  cursor: pointer;
  filter: drop-shadow(0 0 3px var(--khaki)) drop-shadow(0 0 9px rgba(166,144,77,.5)) drop-shadow(0 5px 7px rgba(0,0,0,.55));
}
.card.playable:hover {
  filter: drop-shadow(0 0 4px var(--khaki-hi)) drop-shadow(0 0 14px rgba(203,176,105,.75)) drop-shadow(0 12px 16px rgba(0,0,0,.7));
}
.card.selected {
  transform: translateY(-18px) scale(1.06); z-index: 25;
  filter: drop-shadow(0 0 5px var(--khaki-hi)) drop-shadow(0 0 18px rgba(203,176,105,.85)) drop-shadow(0 12px 18px rgba(0,0,0,.7));
}
.card.unplayable { filter: brightness(.72) saturate(.85) drop-shadow(0 5px 7px rgba(0,0,0,.5)); }

/* badge de coste actual (siempre) */
.card .cost-badge {
  position: absolute; top: -6px; left: -6px; z-index: 3;
  min-width: 26px; height: 26px; padding: 0 3px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 16px; color: #fff; text-shadow: 0 1px 2px #000;
  border-radius: 50%; border: 2px solid #0a1830;
  background: radial-gradient(circle at 35% 30%, #7fb4ff, #2f6fd0 60%, #13315f);
  box-shadow: 0 2px 5px rgba(0,0,0,.6);
}
/* V0.3 — coste con descuento activo (aura de Legion, etc.) en verde brillante.
   (El JS solo alterna esta clase: las viejas `cheap`/`pricey` ya no se ponen.) */
.card .cost-badge.discount {
  background: radial-gradient(circle at 35% 30%, #b6ff7a, #7ed957 58%, #2f8a1e);
  border-color:#153a08; color:#0e2405; text-shadow:0 1px 0 rgba(255,255,255,.45);
  box-shadow: 0 0 10px rgba(126,217,87,.9);
}

/* ============================================================
   Sidebar: log + END TURN
   ============================================================ */
.sidebar { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.log-panel { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; padding: 8px 6px; }
/* El registro es `panel-metal`, y su relleno opaco tapaba la columna que
   `texture.png` trae dibujada justo ahí a la derecha. Se hace translúcido para
   que asome el skin, dejando el filo de latón y un velo que garantiza que las
   líneas del log sigan leyéndose. Las entradas ya traen su propio fondo. */
#screen-game .log-panel {
  background: linear-gradient(180deg, rgba(12,11,9,.72), rgba(6,6,5,.8));
}
.log-header {
  text-align: center; font-weight: 800; letter-spacing: 2px; font-size: 12px;
  color: var(--brass-hi); padding-bottom: 6px; border-bottom: 1px solid rgba(180,145,47,.35);
  margin-bottom: 6px;
}
.log { flex: 1 1 auto; overflow-y: auto; padding-right: 4px; display: flex; flex-direction: column; gap: 2px; }
.log-entry {
  font-size: 11.5px; line-height: 1.25; letter-spacing: .2px;
  padding: 3px 6px; border-radius: 4px; color: var(--ink-dim);
  border-left: 3px solid var(--metal-3);
  background: rgba(0,0,0,.2);
  animation: logIn .25s ease;
}
@keyframes logIn { from { opacity: 0; transform: translateX(8px); } to { opacity: 1; } }
.log-entry.turn   { border-color: var(--brass-hi); color: var(--brass-hi); font-weight: 700; }
.log-entry.attack, .log-entry.damage { border-color: var(--red); color: #e8b0a4; }
.log-entry.death  { border-color: #6a6a6a; color: #9a9a9a; font-style: italic; }
.log-entry.play, .log-entry.summon { border-color: var(--green); color: #c2e6a4; }
.log-entry.draw   { border-color: #4f8fd0; color: #a8c8ef; }
.log-entry.heal   { border-color: #3fae5a; color: #a6e6b0; }
.log-entry.buff   { border-color: var(--gold); color: #ecd48a; }
.log-entry.fatigue, .log-entry.burn { border-color: #a03028; color: #d89088; }
/* V0.9 — tipos nuevos. El Virus en su verde bilis (Ranlew) y el sacrificio en
   púrpura, que es una muerte propia y buscada: ni el rojo del daño ni el gris
   de "muere" cuentan que la has provocado tú. */
.log-entry.virus, .log-entry.virusTick { border-color: #8fae1f; color: #cfe83f; }
.log-entry.sacrifice { border-color: #7d2b63; color: #e0a2cd; }
/* V0.15 — Ranlew. `mancha` es el contador (crema), `siembra` el momento de
   plantar en el mazo rival y `estallido` el reventón al robarla, que es el único
   de los tres que duele: por eso sólo ese va en rojo. */
.log-entry.mancha { border-color: #cfc7ae; color: #f2ead6; }
.log-entry.siembra  { border-color: #7c7460; color: #cdc4ad; font-style: italic; }
.log-entry.estallido   { border-color: #b4432c; color: #ffc9b8; font-weight: 600; }
.log-entry.detonar  { border-color: #c9762a; color: #ffd9a8; font-weight: 600; }
.log-entry.seal { border-color: var(--gold-hi); color: #f0d79a; font-weight: 600; }
/* V0.3 — eventos de conexion/reconexion en ambar. */
.log-entry.connection { border-color: #e0a12e; color: #f0c674; font-weight: 600; }
.log::-webkit-scrollbar { width: 7px; }
.log::-webkit-scrollbar-track { background: rgba(0,0,0,.3); }
.log::-webkit-scrollbar-thumb { background: var(--metal-2); border-radius: 4px; }

/* END TURN circular */
.endturn-wrap { flex: 0 0 auto; display: flex; justify-content: center; padding-bottom: 4px; }
.end-turn {
  width: clamp(78px, 10vh, 116px); height: clamp(78px, 10vh, 116px);
  border-radius: 50%; cursor: pointer; font-family: inherit;
  font-weight: 800; letter-spacing: 1px; font-size: clamp(13px, 1.6vh, 18px);
  color: var(--bone); text-shadow: 0 -1px 0 rgba(0,0,0,.8), 0 2px 3px #000; line-height: 1.05;
  /* Medallón negro con aro de latón: el mismo lenguaje que los paneles, en
     redondo. Antes era un disco de peltre gris que no era de esta paleta. */
  background: radial-gradient(circle at 40% 28%, #2a2620, #131210 56%, #060605);
  border: 2px solid var(--gold-lo);
  box-shadow:
    0 0 0 1px rgba(0,0,0,.9),
    inset 0 2px 6px rgba(217,181,109,.2),
    inset 0 -8px 14px rgba(0,0,0,.7),
    0 6px 16px rgba(0,0,0,.7);
  transition: filter .12s, transform .08s, box-shadow .15s, border-color .16s;
}
.end-turn:disabled { filter: grayscale(.8) brightness(.5); cursor: not-allowed; }
/* Listo para pasar turno: el aro de latón se enciende y late. Misma señal que en
   todo lo demás —filo encendido = puedes actuar—, así el rojo no se gasta aquí. */
.end-turn:not(:disabled) {
  color: #fbf1dc; border-color: var(--gold);
  box-shadow:
    0 0 0 1px rgba(0,0,0,.9),
    inset 0 2px 7px rgba(217,181,109,.35),
    inset 0 -8px 14px rgba(0,0,0,.65),
    0 0 20px rgba(178,138,67,.5), 0 6px 16px rgba(0,0,0,.7);
  animation: readyPulse 1.6s ease-in-out infinite;
}
.end-turn:not(:disabled):hover { filter: brightness(1.15); transform: scale(1.04); }
.end-turn:not(:disabled):active { transform: scale(.97); }
.end-turn:focus-visible { outline: 2px solid var(--khaki-hi); outline-offset: 3px; }
@keyframes readyPulse {
  0%,100% { box-shadow: 0 0 0 1px rgba(0,0,0,.9), inset 0 2px 7px rgba(217,181,109,.3), inset 0 -8px 14px rgba(0,0,0,.65), 0 0 14px rgba(178,138,67,.38), 0 6px 16px rgba(0,0,0,.7); }
  50%     { box-shadow: 0 0 0 1px rgba(0,0,0,.9), inset 0 2px 7px rgba(217,181,109,.5), inset 0 -8px 14px rgba(0,0,0,.65), 0 0 30px rgba(217,181,109,.75), 0 6px 16px rgba(0,0,0,.7); }
}
/* Respeta a quien pide menos movimiento: el aro se queda fijo, no parpadeando. */
@media (prefers-reduced-motion: reduce) {
  .end-turn:not(:disabled) { animation: none; }
}

/* ============================================================
   Overlay fin de partida
   ============================================================ */
/* z-index por encima del grano de película (9000): el grano va con
   `mix-blend-mode: overlay`, y sobre el papel claro de un modal metía moteado
   justo encima de las letras pequeñas. Los overlays son superficies de papel y
   chapa vistas de cerca; no les toca grano. */
.overlay {
  position: fixed; inset: 0; z-index: 9100;
  /* Mismo iOS que `.screen` y `.game-grid`: un fijo con `inset: 0` se ancla al
     viewport GRANDE, así que el cambio de mano, el cementerio y el cartel de fin
     de partida se centraban ~96 px por debajo del centro que se ve en un
     iPhone 16. Se coló en la primera pasada porque el bug se reportó sobre el
     tablero, y estas tres capas sólo salen encima de él. */
  height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 50% 40%, rgba(0,0,0,.65), rgba(0,0,0,.9));
  backdrop-filter: blur(3px);
}
.overlay.hidden { display: none; }
/* El modal de mazo NO es un final de partida: no hay por qué apagar la escena
   detrás. Se deja el fondo a la vista (velo suave y sin desenfoque) para que
   elegir mazo siga sintiéndose dentro del juego y no en una caja negra. */
#deck-modal.overlay {
  background: radial-gradient(circle at 50% 40%, rgba(0,0,0,.3), rgba(0,0,0,.62));
  backdrop-filter: none;
}
.gameover-box { width: min(92vw, 520px); padding: 36px 30px; text-align: center; }
/* El título tiene que aguantar textos MUY distintos: de "EMPATE" (6 letras) a
   "VICTORIA DE <nombre>" con hasta 24 caracteres de nombre en modo espectador.
   Medido, con 72px y 6px de tracking se salía de la caja hasta 441px. El tamaño
   baja, el tracking se afloja y —lo que de verdad lo cierra— el texto PUEDE
   partirse: `overflow-wrap: anywhere` parte incluso un nombre de una sola
   palabra, así que ningún título puede desbordar por largo que sea. */
#go-title {
  font-size: clamp(28px, 5.6vw, 54px); font-weight: 700; letter-spacing: 2px;
  line-height: 1.04; max-width: 100%;
  overflow-wrap: anywhere; word-break: break-word;
  text-shadow: 0 3px 0 #000, 0 0 24px currentColor; margin-bottom: 12px;
}
/* Un título largo (el caso del espectador) se achica un escalón más para no
   comerse tres líneas del overlay. Lo pone `showGameOver` al medir el texto. */
#go-title.largo { font-size: clamp(22px, 3.4vw, 34px); letter-spacing: 1.5px; }
#go-title.win  { color: var(--green-hi); }
#go-title.lose { color: var(--red-hi); }
#go-title.draw { color: var(--brass-hi); }
/* El motivo del fin, sobre pergamino: es el único bloque de prosa del overlay.
   El `color` va con especificidad de ID a propósito — la clase `.pergamino` la
   perdía contra esta misma regla y dejaba texto claro sobre papel claro. */
#go-reason {
  font-size: 15px; letter-spacing: .5px; line-height: 1.4;
  margin: 0 auto 24px; padding: 9px 16px; max-width: 84%;
  color: var(--paper-ink);
  border-top: 1px solid var(--gold-lo); border-bottom: 1px solid var(--gold-lo);
  box-shadow: 0 2px 10px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,244,220,.35);
}

/* ---------- Revancha con otro mazo (V0.11) ----------
   El overlay de fin lleva un selector compacto: perder con un mazo y tener que
   pasar por el menú para cambiarlo era el camino largo a lo mismo. */
.go-deck-wrap { margin: 0 0 18px; }
.go-deck-label {
  margin: 0 0 8px; color: var(--brass-hi);
  font-size: 11px; font-weight: 700; letter-spacing: 3px;
}
.go-actions {
  display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; align-items: center;
}
.go-actions .btn-brass { margin-top: 0; }
/* Salida secundaria: el mismo negro, pero con el filo de latón EN SOMBRA y sin
   la brasa roja. Así "Jugar de nuevo" sigue siendo la acción evidente sin que
   este botón se salga de la paleta. */
.btn-brass.ghost {
  padding: 11px 26px; font-size: 15px; letter-spacing: 2px;
  color: var(--bone-dim);
  background: linear-gradient(180deg, var(--soot), var(--pit) 58%, var(--void));
  border-color: var(--gold-lo);
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.22),
    0 0 0 1px rgba(0,0,0,.85),
    0 4px 12px rgba(0,0,0,.65);
}
.btn-brass.ghost:hover { border-color: var(--gold); color: var(--bone); }

/* ============================================================
   Toasts
   ============================================================ */
#toast-container {
  position: fixed; top: 18px; left: 50%; transform: translateX(-50%);
  z-index: 300; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none;
}
.toast {
  padding: 10px 20px; border-radius: 6px; font-weight: 700; letter-spacing: 1px; font-size: 14px;
  color: #fff; text-shadow: 0 1px 2px #000;
  background: linear-gradient(180deg, #a83228, #6e1c15);
  border: 2px solid #2a0a06;
  box-shadow: 0 6px 16px rgba(0,0,0,.6);
  animation: toastIn .2s ease, toastOut .3s ease 2.2s forwards;
}
@keyframes toastIn { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(-12px); } }

/* ============================================================
   V2 — cuenta atras de turno
   ============================================================ */
.hidden { display: none !important; }

.turn-indicator .ti-timer {
  margin-top: 2px; font-weight: 800; font-size: 17px; letter-spacing: 1px;
  color: var(--ink); line-height: 1;
  padding: 1px 10px; border-radius: 10px;
  background: rgba(0,0,0,.35); border: 1px solid rgba(120,90,50,.45);
}
.turn-indicator.urgent .ti-timer {
  color: #fff;
  background: radial-gradient(circle at 40% 30%, #ff6b58, var(--red) 65%, #6e120b);
  border-color: #2a0705;
  animation: urgentPulse .7s ease-in-out infinite;
}
@keyframes urgentPulse {
  0%,100% { transform: scale(1);   box-shadow: 0 0 4px rgba(255,70,50,.4); }
  50%     { transform: scale(1.14); box-shadow: 0 0 16px rgba(255,70,50,.9); }
}

.et-countdown {
  font-weight: 800; font-size: 18px; letter-spacing: 1px;
  color: var(--ink-dim); text-shadow: 0 1px 2px #000;
  padding: 1px 12px; margin-bottom: 2px; border-radius: 10px;
  background: rgba(0,0,0,.3); border: 1px solid rgba(120,90,50,.4);
}
.et-countdown.urgent {
  color: #fff;
  background: radial-gradient(circle at 40% 30%, #ff6b58, var(--red) 65%, #6e120b);
  border-color: #2a0705;
  animation: urgentPulse .7s ease-in-out infinite;
}
.end-turn.urgent:not(:disabled) {
  box-shadow:
    0 0 0 3px #b5402f,
    inset 0 3px 8px rgba(255,255,255,.3),
    inset 0 -7px 13px rgba(0,0,0,.6),
    0 0 22px rgba(255,70,50,.7), 0 6px 14px rgba(0,0,0,.6);
  animation: none;
}

/* ============================================================
   V0.2 — tactil: el arrastre no debe secuestrar scroll/zoom
   ============================================================ */
.card, .minion { touch-action: none; }
/* Elementos arrastrables / objetivos y sus contenedores de arrastre: el gesto
   tactil es siempre drag, nunca scroll. El log SI conserva su scroll tactil. */
.hero-portrait,
#you-hand, #you-board, #opp-board,
.hero-strip { touch-action: none; }
/* En partida: sin seleccion de texto ni destello azul al tocar. */
#screen-game {
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  /* SKIN DEL TABLERO (V0.11): las `texture*.png` no son texturas repetibles, son
     layouts completos —dos bandas de esbirros, el suelo grabado abajo y la
     columna del registro a la derecha, con filigrana dorada en las esquinas—.
     Se ponen a `cover` y sus marcos hacen de marco de verdad. Medido a 1672x941
     (el tamaño nativo): el borde inferior de la banda del rival cae en y=335 y
     la fila real acaba en 336; la columna del registro va de x=1400 a 1660 y la
     barra lateral real de 1434 a 1664. Se diseñaron contra un layout casi
     idéntico, así que alinean sin trocear la imagen.

     La IMAGEN la pone `pickBoardSkin()` en el cliente, sorteando entre las
     cuatro al empezar cada combate. A propósito NO se declara aquí: si el CSS
     nombrara una, el navegador se descargaría esos ~2 MB en todas las partidas
     aunque luego saliera otra. Aquí solo queda el negro de respaldo. */
  background-color: var(--void);
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}
/* El registro de combate SI debe poder desplazarse con el dedo. */
.log { touch-action: pan-y; -webkit-overflow-scrolling: touch; }

/* ============================================================
   V0.2 — version visible (login + badge en partida)
   ============================================================ */
.login-version {
  color: var(--brass-hi); font-size: 12px; letter-spacing: 2px;
  font-weight: 700; opacity: .85; margin: 2px 0 14px;
  min-height: 14px;
}
.version-badge {
  position: fixed; left: 8px; bottom: 6px; z-index: 210;
  pointer-events: none; user-select: none;
  font-size: 11px; letter-spacing: 1px; font-weight: 700;
  color: var(--ink-faint); opacity: .55;
  text-shadow: 0 1px 2px rgba(0,0,0,.8);
}
.version-badge:empty { display: none; }

/* ============================================================
   V0.14.0 — aviso de version desfasada
   ============================================================
   El estado del juego vive en memoria del servidor, asi que cada redespliegue
   lo reinicia; una pestana abierta desde antes se queda con el `main.js` y el
   CSS viejos hablando con un servidor nuevo. El servidor anuncia su version en
   cada conexion y el cliente compara con la que sirvio ESTOS ficheros.

   z-index por encima de TODO (los overlays del juego llegan a 9500): si el
   cliente esta desfasado, ese aviso es lo mas importante que hay en pantalla.
   `padding-top` con el area segura para que no se meta bajo la muesca. */
.stale-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 9800;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: calc(env(safe-area-inset-top, 0px) + 8px) 12px 8px;
  background: linear-gradient(180deg, #7a2b16, #571d0e);
  border-bottom: 2px solid #d98b3a;
  box-shadow: 0 4px 18px rgba(0,0,0,.55);
  color: #f6e3c4; font-size: 13px; font-weight: 700; letter-spacing: .3px;
  text-shadow: 0 1px 2px rgba(0,0,0,.7);
}
.stale-banner.hidden { display: none; }
.stale-banner .sb-ico { font-size: 16px; line-height: 1; }
.stale-banner .sb-txt { text-align: center; }
.stale-banner .sb-btn {
  flex: 0 0 auto; cursor: pointer;
  padding: 6px 14px; border-radius: 4px;
  border: 2px solid #f0c07a; background: #d98b3a; color: #2b1408;
  font: inherit; font-weight: 800; letter-spacing: .6px;
}
.stale-banner .sb-btn:hover { background: #f0c07a; }
.stale-banner .sb-btn:active { transform: translateY(1px); }

@media (max-width: 560px) {
  /* En movil el texto se parte en dos lineas antes que encoger el boton: lo que
     no puede pasar es que ACTUALIZAR se quede sin sitio. */
  .stale-banner { gap: 8px; padding-left: 8px; padding-right: 8px; font-size: 12px; }
  .stale-banner .sb-ico { display: none; }
  .stale-banner .sb-btn { padding: 6px 10px; }
}

/* ============================================================
   V0.3 — boton flotante de musica (abajo-izquierda, junto al badge
   de version). V0.5: es hijo directo de <body>, asi que es visible
   SIEMPRE (menu, espera de rival, lobby, partida y espectador). En movil
   se eleva solo DENTRO de partida (#screen-game activo) para no tapar la mano.
   ============================================================ */
.music-btn {
  position: fixed; left: 8px; bottom: 28px; z-index: 245;
  width: 42px; height: 42px; border-radius: 50%; cursor: pointer;
  font-size: 20px; line-height: 1; padding: 0;
  display: flex; align-items: center; justify-content: center;
  color: #22160a;
  background: radial-gradient(circle at 38% 30%, var(--brass-hi), var(--brass) 60%, var(--brass-lo));
  border: 2px solid #2a1e08;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), 0 4px 12px rgba(0,0,0,.6);
  opacity: .9; transition: filter .12s, transform .08s, opacity .12s;
}
.music-btn:hover { opacity: 1; filter: brightness(1.08); }
.music-btn:active { transform: scale(.94); filter: brightness(.95); }
.music-btn.muted { filter: grayscale(.5) brightness(.92); }

/* ============================================================
   V0.2 — registro flotante colapsable (movil)
   El boton y el backdrop solo se muestran en pantallas estrechas.
   ============================================================ */
.log-toggle {
  position: fixed; top: 8px; right: 8px; z-index: 270; display: none;
  width: 46px; height: 46px; border-radius: 50%; cursor: pointer;
  font-size: 22px; line-height: 1; padding: 0;
  color: #22160a;
  background: radial-gradient(circle at 38% 30%, var(--brass-hi), var(--brass) 60%, var(--brass-lo));
  border: 2px solid #2a1e08;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), 0 4px 12px rgba(0,0,0,.6);
}
.log-toggle:active { filter: brightness(.95); transform: scale(.96); }
.log-backdrop {
  position: fixed; inset: 0; z-index: 255; display: none;
  background: rgba(0,0,0,.5);
}

/* ============================================================
   V2 — reconciliacion: entrada / salida de nodos
   ============================================================ */

.minion.entering { animation: minionIn .38s cubic-bezier(.2,.9,.3,1.3); }
@keyframes minionIn {
  from { opacity: 0; transform: scale(.35) translateY(26px); }
  to   { opacity: 1; transform: none; }
}
.minion.exiting {
  z-index: 6; pointer-events: none;
  animation: minionOut .42s ease forwards;
}
@keyframes minionOut {
  0%   { opacity: 1; transform: scale(1) rotate(0); filter: grayscale(0); }
  30%  { opacity: 1; transform: scale(1.08) rotate(-3deg); filter: brightness(1.5); }
  100% { opacity: 0; transform: scale(.35) rotate(9deg) translateY(14px); filter: grayscale(1); }
}

.card.entering { animation: cardIn .34s ease; }
@keyframes cardIn {
  from { opacity: 0; transform: translateX(140px) translateY(8px) scale(.82); }
  to   { opacity: 1; transform: none; }
}
.card.exiting { z-index: 6; pointer-events: none; animation: cardOut .4s ease forwards; }
@keyframes cardOut {
  to { opacity: 0; transform: translateY(-150px) scale(.6) rotate(-4deg); }
}

/* sacudida del que recibe daño */
.shaking { animation: shakeHit .34s ease; }
@keyframes shakeHit {
  0%,100% { transform: translateX(0); }
  15% { transform: translateX(-7px); }
  30% { transform: translateX(7px); }
  45% { transform: translateX(-5px); }
  60% { transform: translateX(5px); }
  75% { transform: translateX(-3px); }
}
.hero-portrait.shaking { animation: shakeHit .34s ease; }

/* ============================================================
   V2 — numeros flotantes de combate (capa fx)
   ============================================================ */
#fx-layer {
  position: fixed; inset: 0; z-index: 350;
  pointer-events: none; overflow: hidden;
}
.fx-num {
  position: fixed; transform: translate(-50%, -50%);
  font-weight: 900; font-size: 34px; letter-spacing: 1px;
  text-shadow: 0 2px 4px #000, 0 0 10px rgba(0,0,0,.7);
  will-change: transform, opacity;
  animation: floatUp .95s ease-out forwards;
}
.fx-num.fx-dmg   { color: #ff5647; }
.fx-num.fx-heal  { color: #74e86a; }
.fx-num.fx-shield{ color: var(--gold); font-size: 22px; letter-spacing: 2px; }
/* V0.15 — leche. La caja avisa en rojo lechoso y más pequeña que un golpe:
   lo que importa no es la cifra sino que el mazo estaba envenenado. */
.fx-num.fx-estallido  { color: #ffd9cd; font-size: 22px; letter-spacing: 3px;
                   text-shadow: 0 0 10px rgba(205,60,35,.95), 0 2px 4px #000; }
.fx-num.fx-mancha{ color: #fffaf0; }
@keyframes floatUp {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.5); }
  18%  { opacity: 1; transform: translate(-50%, -60%) scale(1.25); }
  40%  { opacity: 1; transform: translate(-50%, -85%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -150%) scale(.95); }
}

/* ============================================================
   Pulido V0.5 — impacto de daño al heroe propio
   Temblor de pantalla (#battlefield) + vignette roja a viewport + proyectil
   igneo (fxLayer). Solo se disparan para el jugador local (nunca espectador).
   ============================================================ */
/* Temblor de la pantalla de combate: translate + leve rotacion, ~420ms. La
   variante -strong (amount>=5) es mas violenta. La clase se quita por JS. */
.battlefield.bf-shake        { animation: bfShake .42s cubic-bezier(.36,.07,.19,.97); }
.battlefield.bf-shake-strong { animation: bfShakeStrong .44s cubic-bezier(.36,.07,.19,.97); }
@keyframes bfShake {
  0%,100% { transform: translate(0,0) rotate(0); }
  15% { transform: translate(-4px,2px) rotate(-.4deg); }
  30% { transform: translate(5px,-2px) rotate(.4deg); }
  45% { transform: translate(-4px,3px) rotate(-.3deg); }
  60% { transform: translate(4px,-1px) rotate(.3deg); }
  78% { transform: translate(-2px,1px) rotate(-.15deg); }
}
@keyframes bfShakeStrong {
  0%,100% { transform: translate(0,0) rotate(0); }
  12% { transform: translate(-7px,4px) rotate(-.8deg); }
  26% { transform: translate(8px,-5px) rotate(.9deg); }
  40% { transform: translate(-7px,5px) rotate(-.7deg); }
  55% { transform: translate(7px,-3px) rotate(.6deg); }
  70% { transform: translate(-5px,3px) rotate(-.4deg); }
  85% { transform: translate(4px,-2px) rotate(.25deg); }
}

/* Vignette roja: transparente en el centro, rojo intenso en los bordes. Fija a
   viewport, sin capturar el puntero. z-index alto pero POR DEBAJO del overlay de
   fin (500) y de los numeros de daño (#fx-layer, 350) para no taparlos. */
.dmg-vignette {
  position: fixed; inset: 0; z-index: 340;
  pointer-events: none; opacity: 0;
  background: radial-gradient(ellipse 78% 78% at 50% 50%,
              transparent 42%, rgba(190,20,12,.35) 74%, rgba(150,8,4,.9) 100%);
}
.dmg-vignette.show { animation: vignettePulse .52s ease-out; }
.dmg-vignette.show.light { animation: vignettePulseLight .52s ease-out; }
@keyframes vignettePulse {
  0%   { opacity: 0; }
  30%  { opacity: .55; }
  100% { opacity: 0; }
}
@keyframes vignettePulseLight {
  0%   { opacity: 0; }
  30%  { opacity: .26; }
  100% { opacity: 0; }
}

/* Proyectil igneo (vive en #fx-layer, sobre la vignette). Bola con nucleo claro,
   halo naranja/rojo y estela (box-shadow + pseudo-elemento). El transform se
   anima por JS de origen (retrato rival) a destino (retrato propio). */
.hero-projectile {
  position: fixed; width: 20px; height: 20px; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle at 40% 35%, #fff6d0 8%, #ffce5a 30%, #ff8a1e 58%, #d6300e 90%);
  box-shadow: 0 0 12px 4px rgba(255,140,40,.9), 0 0 28px 10px rgba(214,48,14,.55);
  transition: transform 320ms cubic-bezier(.42,.02,.6,.7);
  will-change: transform;
}
.hero-projectile::after {
  content: ""; position: absolute; inset: -6px; border-radius: 50%;
  background: radial-gradient(circle, rgba(255,170,70,.5), transparent 70%);
  filter: blur(3px);
}

/* ============================================================
   V2 — drag & drop (Pointer Events)
   ============================================================ */
.drag-ghost {
  position: fixed; z-index: 400; pointer-events: none;
  transform: translate(-50%, -55%) rotate(-3deg);
  border-radius: 8px; opacity: .85;
  background-repeat: no-repeat;
  box-shadow: 0 12px 26px rgba(0,0,0,.7), 0 0 0 2px rgba(140,220,90,.6);
  filter: drop-shadow(0 0 8px rgba(120,200,70,.5));
}
.drag-ghost.card   { background-size: cover; background-position: center; }
.drag-ghost.minion { background-size: 235%; background-position: 50% 15%; background-color: #14100a; }
.dragging-hidden { opacity: .28 !important; filter: grayscale(.5); }

/* hueco de insercion activo bajo el cursor */
.you-board.placing .islot.islot-active {
  width: clamp(56px, 6.5vw, 92px);
  background: rgba(160,240,100,.6);
  box-shadow: 0 0 26px rgba(160,240,100,.95), inset 0 0 12px rgba(255,255,255,.55);
  border-color: rgba(190,255,130,1);
  animation: islotPulse .8s ease-in-out infinite;
}
@keyframes islotPulse {
  0%,100% { filter: brightness(1); }
  50%     { filter: brightness(1.3); }
}
/* objetivo de ataque bajo el cursor durante el arrastre */
.drop-hot { filter: brightness(1.45) saturate(1.2) !important; }

/* ============================================================
   V2 — vista ampliada (hover)
   ============================================================ */
.card-preview {
  position: fixed; left: 18px; top: 50%; transform: translateY(-50%);
  width: clamp(200px, 24vw, 340px); aspect-ratio: 3 / 4;
  z-index: 320; pointer-events: none;
  background-repeat: no-repeat; background-position: center; background-size: contain;
  /* V0.4 — vista ampliada "flotante": sin panel ni fondo, solo la carta con
     una sombra suave que sigue su silueta (drop-shadow). */
  background-color: transparent;
  filter: drop-shadow(0 22px 34px rgba(0,0,0,.8)) drop-shadow(0 0 12px rgba(0,0,0,.5));
  animation: previewIn .16s ease;
}
@keyframes previewIn {
  from { opacity: 0; transform: translateY(-50%) scale(.9); }
  to   { opacity: 1; transform: translateY(-50%) scale(1); }
}

/* ============================================================
   V0.2 — Modo espectador + invitar
   ============================================================ */

/* Badge fijo "MODO ESPECTADOR" (solo espectadores). */
.spectator-badge {
  position: fixed; top: 8px; left: 50%; transform: translateX(-50%);
  z-index: 260; pointer-events: none; user-select: none;
  padding: 5px 16px; border-radius: 20px;
  font-size: 13px; font-weight: 800; letter-spacing: 2px;
  color: #f0e6ff; text-shadow: 0 1px 2px #000;
  background: linear-gradient(180deg, #4a2d73, #2c1a47);
  border: 2px solid #120a20;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.22), 0 4px 12px rgba(0,0,0,.6),
              0 0 14px rgba(150,90,220,.35);
  animation: specBadgePulse 2.4s ease-in-out infinite;
}
@keyframes specBadgePulse {
  0%,100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.22), 0 4px 12px rgba(0,0,0,.6), 0 0 10px rgba(150,90,220,.3); }
  50%     { box-shadow: inset 0 1px 0 rgba(255,255,255,.22), 0 4px 12px rgba(0,0,0,.6), 0 0 20px rgba(170,110,240,.6); }
}

/* En modo espectador no hay boton de fin de turno. */
#screen-game.spectating .endturn-wrap { display: none !important; }

/* El espectador NO tiene mano, pero la fila seguía reservando su altura: en un
   móvil eran ~120px muertos debajo de la franja de héroe, y arriba el tablero
   rival quedaba comprimido y tapado por el chrome flotante. Al ocultarla, los
   dos tableros se reparten ese espacio (`.board-row` es flex: 3). */
#screen-game.spectating .hand-row { display: none !important; }

/* ---- CHROME DE ESPECTADOR ------------------------------------------------
   VOLVER y MODO ESPECTADOR eran `position: fixed` arriba (8px), que es justo
   donde vive la franja del héroe RIVAL: le tapaban el retrato y los puntos de
   vida. Pasaba en TODOS los tamaños, no sólo en móvil, y no había forma de
   descubrirlo porque la app es de viewport fijo (`body { overflow: hidden }`).
   Se mudan a la banda de abajo —que al ocultar la mano queda libre— y se les
   reserva sitio real con `padding-bottom`, en vez de flotar sobre el tablero. */
#screen-game.spectating .battlefield {
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 54px);
}
#screen-game.spectating .spectate-back {
  top: auto; left: 12px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
}
#screen-game.spectating .spectator-badge {
  top: auto; bottom: calc(env(safe-area-inset-bottom, 0px) + 14px);
}
/* La música vivía pegada al borde inferior izquierdo, encima de donde ahora
   acaba la franja del héroe de abajo: se va al otro extremo de la banda. */
#screen-game.spectating.active ~ .music-btn {
  left: auto; right: 12px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
}

/* Barra de invitar + contador de espectadores (solo jugadores). Centrada
   arriba, sobre la franja rival (su centro esta vacio), sin tapar portraits. */
.invite-bar {
  position: fixed; top: 8px; left: 50%; transform: translateX(-50%); z-index: 240;
  display: flex; align-items: center; gap: 8px;
}
.invite-btn {
  cursor: pointer; font-family: inherit;
  padding: 6px 12px; border-radius: 18px;
  font-size: 13px; font-weight: 700; letter-spacing: 1px;
  color: #22160a; text-shadow: 0 1px 0 rgba(255,255,255,.3);
  background: linear-gradient(180deg, var(--brass-hi), var(--brass) 60%, var(--brass-lo));
  border: 2px solid #2a1e08;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), 0 3px 9px rgba(0,0,0,.55);
  opacity: .85; transition: opacity .12s, filter .12s, transform .08s;
}
.invite-btn:hover { opacity: 1; filter: brightness(1.08); }
.invite-btn:active { transform: translateY(1px); }
.spec-count {
  display: inline-flex; align-items: center;
  padding: 4px 10px; border-radius: 14px;
  font-size: 13px; font-weight: 800; letter-spacing: 1px;
  color: var(--ink); text-shadow: 0 1px 2px #000;
  background: linear-gradient(180deg, rgba(74,45,115,.85), rgba(44,26,71,.85));
  border: 1px solid #120a20;
  box-shadow: 0 2px 6px rgba(0,0,0,.5);
}

/* ============================================================
   V0.3 — Banner de rival desconectado (periodo de gracia)
   ============================================================ */
.disconnect-banner {
  position: fixed; top: 44px; left: 50%; transform: translateX(-50%);
  z-index: 250; pointer-events: none; user-select: none; white-space: nowrap;
  max-width: 92vw; overflow: hidden; text-overflow: ellipsis;
  padding: 6px 18px; border-radius: 20px;
  font-size: 13px; font-weight: 800; letter-spacing: .6px;
  color: #241704; text-shadow: 0 1px 0 rgba(255,255,255,.25);
  background: linear-gradient(180deg, #f0c256, #d99422 60%, #b0741a);
  border: 2px solid #4a3208;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 4px 12px rgba(0,0,0,.6),
              0 0 16px rgba(224,161,46,.5);
  animation: dcBannerPulse 1.4s ease-in-out infinite;
}
.disconnect-banner.hidden { display: none; }
@keyframes dcBannerPulse {
  0%,100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 4px 12px rgba(0,0,0,.6), 0 0 10px rgba(224,161,46,.4); }
  50%     { box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 4px 12px rgba(0,0,0,.6), 0 0 22px rgba(240,200,90,.75); }
}

/* ============================================================
   V0.5 — Emotes (picarse con emojis estilo Hearthstone)
   Boton flotante 💬 abajo-derecha (junto a tu franja de heroe, a la
   izquierda de la columna del END TURN en escritorio); panel compacto de
   6 emojis que se despliega hacia arriba; burbujas sobre las franjas de
   heroe. Reutiliza el look de laton de .music-btn.
   ============================================================ */
.emote-btn {
  position: fixed; z-index: 246;
  right: calc(clamp(160px, 15vw, 230px) + 22px); bottom: 12px;
  width: 42px; height: 42px; border-radius: 50%; cursor: pointer;
  font-size: 20px; line-height: 1; padding: 0;
  display: flex; align-items: center; justify-content: center;
  color: #22160a;
  background: radial-gradient(circle at 38% 30%, var(--brass-hi), var(--brass) 60%, var(--brass-lo));
  border: 2px solid #2a1e08;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), 0 4px 12px rgba(0,0,0,.6);
  opacity: .9; transition: filter .12s, transform .08s, opacity .12s;
}
.emote-btn.hidden { display: none; }
.emote-btn:hover { opacity: 1; filter: brightness(1.08); }
.emote-btn:active { transform: scale(.94); }
.emote-btn .eb-cd { display: none; }
/* Cooldown local (UX; el servidor manda): atenuado con cuenta atras 10->0. */
.emote-btn.cooling { cursor: default; opacity: .55; filter: grayscale(.45) brightness(.9); }
.emote-btn.cooling .eb-ico { display: none; }
.emote-btn.cooling .eb-cd { display: block; font-size: 16px; font-weight: 800; }

/* Panel de emojis: rejilla 3x2, se posiciona por JS justo encima del boton. */
.emote-panel {
  position: fixed; z-index: 244;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px;
  padding: 8px; border-radius: 12px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.25)),
    linear-gradient(160deg, var(--metal-2), var(--metal-1));
  border: 2px solid #1b1a15;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 8px 22px rgba(0,0,0,.7);
  animation: emotePanelIn .16s ease;
}
.emote-panel.hidden { display: none; }
.emote-pick {
  width: 46px; height: 46px; min-width: 40px; min-height: 40px;
  border-radius: 9px; cursor: pointer; padding: 0;
  font-size: 26px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 40% 30%, #4a4740, #2c2a24);
  border: 1px solid #14130e;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12);
  transition: transform .08s, filter .12s;
}
.emote-pick:hover { filter: brightness(1.25); transform: translateY(-2px); }
.emote-pick:active { transform: scale(.9); }
@keyframes emotePanelIn {
  from { opacity: 0; transform: translateY(8px) scale(.94); }
  to   { opacity: 1; transform: none; }
}

/* Capa de burbujas (a viewport, sin capturar el puntero). Bajo el overlay de
   fin (350) y bajo los numeros de daño (400), sobre el resto del juego. */
.emote-layer { position: fixed; inset: 0; z-index: 335; pointer-events: none; }
.emote-bubble {
  position: fixed; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 7px 14px 8px; border-radius: 14px; white-space: nowrap;
  background: linear-gradient(180deg, #f3e2b8, #d9bd85 68%, #c2a266);
  border: 2px solid #4a3208;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.5), 0 6px 16px rgba(0,0,0,.6);
}
.emote-bubble .eb-emoji { font-size: 44px; line-height: 1; }
.emote-bubble .eb-label {
  font-size: 12px; font-weight: 800; letter-spacing: .4px;
  color: #3a2708; text-shadow: 0 1px 0 rgba(255,255,255,.35);
}
/* Pico del bocadillo apuntando hacia el heroe. */
.emote-bubble::after {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%);
  border: 8px solid transparent;
}
.emote-bubble.you::after { top: 100%; border-top-color: #c2a266; }
.emote-bubble.opp::after { bottom: 100%; border-bottom-color: #f3e2b8; }
/* Pop-in con rebote. 'you' ancla su base sobre la franja inferior (translateY
   -100%); 'opp' cuelga bajo la franja superior (translateY 0). */
.emote-bubble.you { animation: emoteInYou .34s cubic-bezier(.2,.9,.3,1.5) both; }
.emote-bubble.opp { animation: emoteInOpp .34s cubic-bezier(.2,.9,.3,1.5) both; }
@keyframes emoteInYou {
  0%   { opacity: 0; transform: translate(-50%, -100%) scale(.4); }
  70%  { opacity: 1; transform: translate(-50%, -100%) scale(1.08); }
  100% { opacity: 1; transform: translate(-50%, -100%) scale(1); }
}
@keyframes emoteInOpp {
  0%   { opacity: 0; transform: translate(-50%, 0) scale(.4); }
  70%  { opacity: 1; transform: translate(-50%, 0) scale(1.08); }
  100% { opacity: 1; transform: translate(-50%, 0) scale(1); }
}
.emote-bubble.you.leaving { animation: emoteOutYou .3s ease forwards; }
.emote-bubble.opp.leaving { animation: emoteOutOpp .3s ease forwards; }
@keyframes emoteOutYou { to { opacity: 0; transform: translate(-50%, -100%) scale(.85); } }
@keyframes emoteOutOpp { to { opacity: 0; transform: translate(-50%, 0) scale(.85); } }

/* ============================================================
   Responsive — V0.2: movil / pantalla estrecha o en retrato
   ============================================================ */
@media (max-width: 820px), (orientation: portrait) {
  /* Esbirros mas pequenos: hasta 7 por fila sin desbordar. */
  :root {
    --minion-h: clamp(54px, 15vw, 96px);
    --minion-w: calc(var(--minion-h) * 0.74);
  }

  /* El campo de batalla ocupa todo; registro y END TURN pasan a flotantes. */
  .game-grid { grid-template-columns: 1fr; grid-template-rows: 1fr; gap: 4px; padding: 4px; }
  .battlefield { padding: 4px 6px 0; gap: 4px; }
  .sidebar { display: contents; }

  /* Controles flotantes visibles. El registro baja bajo la franja rival: en su
     esquina superior derecha tapaba MAZO / CEMENT. / VIRUS del enemigo. */
  .log-toggle {
    display: flex; align-items: center; justify-content: center;
    top: calc(env(safe-area-inset-top, 0px) + 80px);
  }
  .log-backdrop { display: block; }

  /* Registro: panel deslizante desde la derecha (oculto por defecto). */
  .log-panel {
    position: fixed; top: 0; right: 0; z-index: 260;
    width: min(86vw, 340px); height: 100vh; max-height: none;
    height: 100dvh;
    border-radius: 0; padding: 46px 8px 12px;
    transform: translateX(102%); transition: transform .25s ease;
  }
  #screen-game.log-open .log-panel { transform: translateX(0); box-shadow: -10px 0 30px rgba(0,0,0,.65); }

  /* END TURN flotante, pegado al fondo (V0.12.1). A 188px quedaba a media
     altura y con la fila propia llena (7+ esbirros) se comia el del extremo
     derecho, que es justo el que hay que poder tocar para atacar. Abajo del
     todo solo roza la mano, y la mano se arrastra desde el centro. */
  .endturn-wrap {
    position: fixed; right: 8px; z-index: 240; padding: 0;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
    flex-direction: column; align-items: center; gap: 3px;
  }
  .end-turn { width: 62px; height: 62px; font-size: 13px; border-width: 3px; }
  .et-countdown { margin: 0; font-size: 15px; padding: 1px 9px; }

  /* Franjas de heroe compactas. */
  .hero-strip { min-height: 44px; gap: 8px; padding: 2px 6px; }
  .hero-portrait { width: 50px; height: 50px; font-size: 22px; }
  /* Heroe rival: area clicable ampliada (>=56px) para atacarlo con el dedo. */
  .hero-portrait.opp { width: 56px; height: 56px; }
  .hero-portrait.opp::before {
    content: ""; position: absolute; inset: -8px; border-radius: 50%;
  }
  /* La chapa de Virus se recoge: con el retrato a 50px, un -12px de sangrado se
     comía el borde de la franja. Sigue legible, sólo que más ceñida. */
  .virus-badge {
    top: -5px; left: -7px; min-width: 26px; height: 21px;
    padding: 0 4px; font-size: 12.5px; gap: 1px;
  }
  .virus-badge .vb-ico { font-size: 10.5px; }
  .hero-name { font-size: 13px; max-width: 92px; }
  .hero-meta { min-width: 0; gap: 2px; }
  .mana-pips { max-width: 108px; gap: 2px; }
  .sellos { gap: 3px; }
  .sello { width: 12px; height: 12px; font-size: 8px; }
  .pip { width: 8px; height: 11px; }
  .mana-num { font-size: 11px; }
  .zones { gap: 5px; }
  .zones .zone { min-width: 30px; padding: 2px 3px; }
  /* Con lo sembrado, la franja pasa de dos contadores a cinco. En vertical no
     caben con rótulo, y el rótulo es lo prescindible: el icono ya dice de qué
     es y el número es lo que se mira. El texto completo sigue en el `title`. */
  .zones .zone.sembrada { min-width: 24px; padding: 2px; }
  .zones .zone.sembrada .zlbl { display: none; }
  .zones .zone.sembrada .zicon { font-size: 12px; }
  .zones .zone.sembrada .zcount { font-size: 13px; }
  .zone .zicon { font-size: 13px; }
  .zone .zcount { font-size: 13px; }
  .zone .zlbl { font-size: 7px; }

  /* Franja central compacta. */
  .mid-strip { gap: 8px; }
  .turn-indicator { min-width: 116px; padding: 2px 12px; }
  .turn-indicator .ti-label { font-size: 13px; }

  /* Filas de tablero: menos hueco, esbirros con estadisticas mas pequenas. */
  .board-row { gap: 4px; padding: 4px; }
  /* V0.3 — fila abarrotada en movil: esbirros aun mas pequenos para 10 sin
     desbordar en pantallas estrechas (p. ej. 390px). Re-declara ambas variables. */
  .board-row.crowded {
    --minion-h: clamp(40px, 8vw, 70px);
    --minion-w: calc(var(--minion-h) * 0.74);
    gap: 3px;
  }
  .mstat { width: 24px; height: 24px; font-size: 15px; bottom: -4px; }
  .mstat.atk { left: -4px; }
  .mstat.hp { right: -4px; }
  .minion .mname { font-size: 9px; padding: 8px 2px 2px; }

  /* Mano compacta con solape ADAPTATIVO. El `padding-right` reserva la columna
     de END TURN + emote, que desde la V0.12.1 viven en esta misma banda: sin el,
     la mano se centra en todo el ancho y su cola pasa por debajo de los botones.

     V0.13 — el solape lo calcula esta regla, no un escalon fijo. `--n` lo publica
     `reconcileHand()` con las cartas que hay, y el `calc()` saca el solape MINIMO
     para que quepan todas: el `100%` de un margen porcentual se resuelve contra
     el ANCHO DE CONTENIDO de .hand-row, o sea con el padding-right ya
     descontado. `min()` se queda con el mas cerrado de los dos, asi que -32px es
     el solape normal y solo se aprieta cuando de verdad no cabe.

     Lo que arregla, medido en Chromium con la mano llena: el -42px fijo dejaba
     18px de tira tocable por carta a 360-390px (WCAG 2.5.8 pide 24) y AUN ASI
     desbordaba con 10 cartas a 360px y con 9 a 510px. Con esto no desborda en
     ningun ancho y con 7 cartas la tira sube ~10px. */
  .hand-row {
    min-height: 116px; gap: 0; padding: 2px 124px 0 4px; margin-bottom: -2px;
    --hcw: clamp(60px, 15.5vw, 78px);
  }
  .hand-row .card {
    width: var(--hcw);
    height: clamp(92px, 22vw, 112px);
  }
  .hand-row .card + .card {
    margin-left: min(-32px, calc((100% - var(--hcw) * var(--n, 10)) / (var(--n, 10) - 1)));
  }
  .hand-row .card:first-child { margin-left: 0; }
  .card:hover { transform: none; }        /* sin salto de hover en tactil */
  .card .cost-badge { min-width: 22px; height: 22px; font-size: 14px; top: -5px; left: -5px; }

  /* Huecos de despliegue: minimos comodos para el dedo. */
  .you-board.placing .islot { min-width: 46px; }

  /* Boton de musica en movil: mismo tamaño en todas las pantallas. DENTRO de
     partida (#screen-game activo) se eleva sobre la mano (alineado con END TURN a
     la izquierda) para no taparla; en menu/espera/lobby se queda abajo-izquierda. */
  .music-btn { left: 8px; width: 46px; height: 46px; font-size: 21px; }
  /* EN PARTIDA los flotantes se apartan de las FRANJAS DE HÉROE. Medido en un
     iPhone (402x874): la franja rival ocupa 11-73 y la tuya 693-749, y ahí es
     donde caían los cuatro botones — la música tapaba tu retrato y tu vida, el
     registro el mazo del rival, y emotes/END TURN tus contadores. Las franjas
     llevan los NÚMEROS con los que se decide el turno; el tablero, en cambio,
     admite que lo tapen. Así que la música baja bajo la franja rival y los de
     abajo suben por encima de la tuya. */
  #screen-game.active ~ .music-btn {
    bottom: auto; top: calc(env(safe-area-inset-top, 0px) + 80px);
  }

  /* Emotes en movil: el boton 💬 acompaña al END TURN en la banda de abajo, a su
     izquierda y centrado con el. Se probo apilarlo ENCIMA para estrechar la
     pareja, y medido caia sobre la franja de heroe propia (670-716 frente a
     641-697): tapaba media chapa de CEMENT., que es clicable. Por encima de la
     franja solo cabe volviendo a los ~188px, o sea encima del tablero, que es
     justo lo que se queria evitar. Al lado, entonces. El panel lo posiciona el
     JS respecto al boton, asi que lo sigue sin tocar nada. */
  .emote-btn {
    right: 78px; bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
    width: 46px; height: 46px; font-size: 21px;
  }
  .emote-pick { width: 44px; height: 44px; font-size: 24px; }

  /* Invitar / espectador: compactos y sin estorbar (log-toggle a la derecha,
     barra de invitar y badge centrados arriba, sobre el centro vacio). */
  .invite-bar { top: 6px; gap: 5px; }
  .invite-btn { padding: 5px 9px; font-size: 12px; }
  .spec-count { padding: 3px 8px; font-size: 12px; }
  .spectator-badge { top: 6px; font-size: 11px; padding: 4px 11px; letter-spacing: 1px; }
  .disconnect-banner { top: 40px; font-size: 11.5px; padding: 5px 12px; }

  /* ---- ESPECTADOR EN MÓVIL ----------------------------------------------
     El problema: VOLVER (arriba-izq), MODO ESPECTADOR (arriba-centro) y el
     registro (arriba-dcha) son `position: fixed` a 6-8px del borde, y en un
     móvil la franja del héroe RIVAL arranca justo ahí: le tapaban retrato,
     vida y contador de Virus. Y no se puede hacer scroll para descubrirla,
     porque la partida ocupa el alto exacto de la pantalla.
     La solución: como el espectador no tiene mano, esa banda de abajo queda
     libre — el chrome se muda ahí y el borde superior queda limpio. De paso,
     VOLVER se queda en icono, que es lo que pide un móvil. */
  /* La banda de abajo la fija la regla global; en móvil hace falta más alto
     porque los botones táctiles son de 46px. */
  #screen-game.spectating .battlefield {
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 62px);
  }
  /* VOLVER se queda en icono: en un móvil el texto sólo roba ancho. */
  #screen-game.spectating .spectate-back {
    left: 8px; width: 46px; height: 46px; padding: 0; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 22px; letter-spacing: 0;
  }
  #screen-game.spectating .spectate-back .sb-txt { display: none; }
  #screen-game.spectating .spectator-badge {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 21px);
  }
  #screen-game.spectating .log-toggle {
    top: auto; right: 8px;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
  }
  /* En móvil la esquina derecha de la banda la ocupa el registro, así que la
     música se pone junto a VOLVER en vez de a la derecha. */
  #screen-game.spectating.active ~ .music-btn { left: 62px; right: auto; }

  /* Vista ampliada centrada (long-press), sin animacion de transform. */
  .card-preview {
    left: 50%; right: auto; top: 46%;
    transform: translate(-50%, -50%);
    width: min(74vw, 320px); animation: none;
  }
}

/* ============================================================
   V0.5 — Cartas con plantilla (composite dinamico desde cards.json)
   EXCEPCION a "solo system fonts": estas cartas usan Fira Sans Condensed
   (servida en /fonts) para clavar la tipografia de las cartas horneadas.
   El composite es contenido del div .card, asi que los drop-shadow/filtros
   de .card / .card-preview siguen aplicandole igual (sombra de silueta).
   ============================================================ */
@font-face {
  font-family: 'TrincheraBody';
  font-weight: 600; font-style: normal; font-display: swap;
  src: url('/fonts/FiraSansCondensed-SemiBold.ttf') format('truetype');
}
@font-face {
  font-family: 'TrincheraKeyword';
  font-weight: 800; font-style: normal; font-display: swap;
  src: url('/fonts/FiraSansCondensed-ExtraBold.ttf') format('truetype');
}
/* Rótulo del banner de tribu genérico (placa banner_plain): serif chunky estilo
   Belwe (Grenze, OFL) para acercarse a la palabra rotulada de los banners
   nativos. Solo se usa para tribus sin sprite propio (Vampiro, Humano...). */
@font-face {
  font-family: 'TrincheraBanner';
  font-weight: 600; font-style: normal; font-display: swap;
  src: url('/fonts/Grenze-SemiBold.ttf') format('truetype');
}
@font-face {
  font-family: 'TrincheraBanner';
  font-weight: 700; font-style: normal; font-display: swap;
  src: url('/fonts/Grenze-Bold.ttf') format('truetype');
}

/* Contenedor que centra y clipa el lienzo 785x1100 dentro de la .card.
   El .card-face se escala por JS (scale = anchoCarta/785, origen centro);
   el flex-center reparte la diferencia vertical (caja 1.42 vs carta ~1.401). */
.card-face-wrap {
  position: absolute; inset: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  border-radius: inherit; pointer-events: none;
}
.card-face {
  position: relative; flex: 0 0 auto;
  width: 785px; height: 1100px;
  transform-origin: center center;
}
.card-face .cf-base {
  position: absolute; left: 0; top: 0; width: 785px; height: 1100px; display: block;
}
.card-face .cf-sprite { position: absolute; display: block; image-rendering: auto; }

/* Digito de respaldo (valor sin sprite horneado): centrado en el ancla, blanco
   con borde negro grueso via text-shadow en 8 direcciones. */
.card-face .cf-digit {
  position: absolute; transform: translate(-50%, -50%);
  font-family: 'TrincheraKeyword', sans-serif; font-weight: 800;
  font-size: 115px; line-height: 1; color: #fff; white-space: nowrap;
  text-shadow:
    3px 0 0 #000, -3px 0 0 #000, 0 3px 0 #000, 0 -3px 0 #000,
    3px 3px 0 #000, -3px 3px 0 #000, 3px -3px 0 #000, -3px -3px 0 #000;
}

/* Rótulo de tribu sobre banner_plain: blanco cálido con contorno casi negro
   (calca la palabra de los banners nativos). Centrado en banner_text_anchor.
   font-size = tamaño base; cfFitBanner() lo reduce si no cabe en la placa.
   VALORES CALIBRADOS (deben coincidir con cards/generate/template.html):
   size 44 · letter-spacing 1 · fill #faf6ee · contorno #100b07 · maxW 300. */
.card-face .cf-banner-text {
  position: absolute; transform: translate(-50%, -50%);
  font-family: 'TrincheraBanner', serif; font-weight: 700;
  font-size: 44px; line-height: 1; white-space: nowrap;
  letter-spacing: 1px; color: #faf6ee;
  text-shadow:
    2.2px 0 0 #100b07, -2.2px 0 0 #100b07, 0 2.2px 0 #100b07, 0 -2.2px 0 #100b07,
    2px 2px 0 #100b07, -2px 2px 0 #100b07, 2px -2px 0 #100b07, -2px -2px 0 #100b07;
}

/* Bloque de texto (mismo layout que cards/generate/template.html). */
.card-face .cf-text {
  position: absolute; left: 392.5px; top: 857px;
  transform: translate(-50%, -50%);
  width: 490px; text-align: center;
  color: rgb(38, 31, 24);
  font-family: 'TrincheraBody', sans-serif; font-weight: 600;
  font-size: 52px; line-height: 1.1; letter-spacing: -0.3px;
  -webkit-font-smoothing: antialiased;
}
.card-face .cf-para { margin: 0; text-wrap: balance; }
.card-face .cf-para + .cf-para { margin-top: 0.32em; }
.card-face .cf-text b { font-family: 'TrincheraKeyword', sans-serif; font-weight: 800; }

/* Preview compuesto: centra el composite dentro de la caja fija del preview. */
.card-preview.has-face {
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; background: none;
}

/* ============================================================
   COLECCION (V0.8) — ojeador de cartas por mazo, fuera de partida.
   Conviven los dos renders: las cartas con `template` se componen en vivo
   (reutilizan .card-face-wrap/.card-face del juego, escaladas por JS) y las
   horneadas se pintan como imagen de fondo.
   ============================================================ */
/* El tinte de COLECCIÓN vive con los demás botones del menú (--khaki-lo); aquí
   solo queda el matiz del subtítulo. */
.menu-btn.collection .mb-sub { color: rgba(203,176,105,.7); }

/* ============================================================
   CRÉDITOS (V0.9) — quién ha hecho esto.
   Placa de latón sobre metal, con el mismo lenguaje que el resto del menú.
   ============================================================ */
.menu-btn.credits .mb-sub { color: var(--bone-faint); }

.credits-box { width: min(94vw, 560px); }

.credits-list {
  list-style: none; margin: 18px 0 6px; padding: 0;
  display: flex; flex-direction: column; gap: 14px;
}
.credits-list li {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 12px 14px; border-radius: 8px;
  background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.25));
  border: 1px solid rgba(120,116,98,.35);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}
/* El ROL en pequeño y apagado; el NOMBRE grande y en BLANCO: en unos créditos
   lo que se busca es a la persona, no el cargo. Blanco y no latón porque el
   latón es el color del título de la pantalla, y con los dos en el mismo tono
   la lista se leía como una continuación del rótulo en vez de como su
   contenido. El resplandor cálido se queda: es lo que la ata al resto. */
.cr-rol {
  font-size: 11px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--ink-faint); text-align: center;
}
.cr-quien {
  font-size: clamp(16px, 2.6vw, 21px); font-weight: 800; letter-spacing: 1px;
  color: #fff8f0; text-shadow: 0 2px 0 #000, 0 0 12px rgba(200,150,60,.25);
  text-align: center;
}

/* La dedicatoria: separada por un filete, en cursiva y con respiro alrededor.
   Es el cierre de la pantalla, no una línea más de la lista. */
.credits-gift {
  margin: 20px 4px 2px; padding-top: 16px;
  border-top: 1px solid rgba(120,116,98,.35);
  text-align: center; font-style: italic;
  font-size: clamp(13px, 2.2vw, 16px); letter-spacing: 1px;
  color: var(--ink); opacity: .92;
}

@media (max-width: 420px) {
  .credits-list { gap: 10px; margin-top: 12px; }
  .credits-list li { padding: 9px 10px; }
  .credits-gift { margin-top: 14px; padding-top: 12px; }
  /* La columna única de la V0.11 ya es lo mejor para móvil: no hay nada que
     reorganizar aquí, solo apretar el ritmo vertical. */
  .menu-actions { gap: 6px; }
}

.collection-box { width: min(96vw, 1180px); max-height: 94vh; }
/* El contador ocupa el hueco que .lobby-head::after reserva para centrar el
   titulo, asi que ahi sobra el placeholder. */
.collection-box .lobby-head::after { display: none; }
.collection-count {
  flex: 0 0 auto; width: 118px; text-align: right; white-space: nowrap;
  font-size: 11px; letter-spacing: .5px; color: var(--ink-faint);
}

.collection-tabs {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 12px 0 10px; justify-content: center;
}
.col-tab {
  cursor: pointer; font-family: inherit;
  padding: 7px 14px; border-radius: 8px;
  font-size: 12px; font-weight: 800; letter-spacing: 1.5px;
  color: var(--ink-dim); text-shadow: 0 1px 2px #000;
  border: 1px solid #17130b;
  background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.3));
  box-shadow: inset 0 0 0 1px rgba(120,116,98,.18);
  transition: color .14s, box-shadow .14s, transform .08s;
}
.col-tab:hover { color: var(--ink); }
.col-tab:active { transform: translateY(1px); }
.col-tab.selected {
  color: var(--brass-hi);
  border-color: var(--brass-lo);
  box-shadow: inset 0 0 0 1px rgba(232,194,90,.4), 0 0 10px rgba(200,150,60,.25);
}

.collection-grid {
  flex: 1 1 auto; overflow-y: auto; min-height: 120px; padding-right: 6px;
}
.collection-grid::-webkit-scrollbar { width: 8px; }
.collection-grid::-webkit-scrollbar-track { background: rgba(0,0,0,.3); }
.collection-grid::-webkit-scrollbar-thumb { background: var(--metal-2); border-radius: 4px; }

.col-msg {
  text-align: center; color: var(--ink-faint); letter-spacing: 2px;
  font-size: 14px; padding: 40px 8px;
}
.col-group {
  margin: 18px 0 10px; font-size: 12px; letter-spacing: 2px;
  color: var(--ink-faint); text-align: center; font-weight: 800;
  border-top: 1px solid rgba(120,116,98,.22); padding-top: 14px;
}
.col-row {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(clamp(112px, 13vw, 158px), 1fr));
}
.col-card {
  position: relative; aspect-ratio: 785 / 1100; cursor: pointer;
  border-radius: 8px; overflow: hidden;
  background-color: #140e05; background-size: contain;
  background-position: center; background-repeat: no-repeat;
  box-shadow: 0 3px 9px rgba(0,0,0,.55);
  transition: transform .1s, box-shadow .16s, filter .16s;
}
.col-card:hover {
  transform: translateY(-3px) scale(1.02);
  box-shadow: 0 8px 18px rgba(0,0,0,.7), 0 0 14px rgba(200,150,60,.25);
  z-index: 2;
}
.col-card-empty {
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 10px; font-size: 12px; color: var(--ink-faint);
  border: 1px dashed rgba(120,116,98,.35);
}

/* --- Lupa: la carta a tamaño de lectura + su ficha --- */
.collection-zoom {
  /* Por encima de los overlays (9100): la lupa se abre DESDE el visor de
     cementerio, que es uno de ellos, y con z-index 320 quedaba detrás. */
  position: fixed; inset: 0; z-index: 9200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(6,4,1,.86); backdrop-filter: blur(3px);
  padding: 24px;
}
.collection-zoom.hidden { display: none; }
.cz-inner {
  display: flex; align-items: center; gap: clamp(16px, 3vw, 40px);
  max-width: 100%; max-height: 100%;
}
.cz-card {
  position: relative; flex: 0 0 auto;
  height: min(78vh, 700px); aspect-ratio: 785 / 1100;
  border-radius: 12px; overflow: hidden;
  background-size: contain; background-position: center; background-repeat: no-repeat;
  box-shadow: 0 12px 34px rgba(0,0,0,.8);
}
.cz-info {
  flex: 0 1 320px; min-width: 0; color: var(--ink);
  cursor: default; /* clicar la ficha no cierra la lupa */
}
.cz-info h3 {
  margin: 0 0 10px; font-size: clamp(18px, 2.4vw, 26px); letter-spacing: 1px;
  color: var(--brass-hi); text-shadow: 0 2px 0 #000;
}
.cz-stats { display: flex; gap: 10px; margin: 0 0 10px; }
.cz-stats span {
  min-width: 38px; padding: 5px 8px; border-radius: 7px; text-align: center;
  font-weight: 800; font-size: 17px; text-shadow: 0 1px 2px #000;
  border: 1px solid #17130b;
}
.cz-cost { background: linear-gradient(180deg, #6ea8e6, #2b4f8c); }
.cz-atk  { background: linear-gradient(180deg, #e6b45c, #8a5a16); }
.cz-hp   { background: linear-gradient(180deg, #e07a72, #8c2620); }
.cz-meta { margin: 0 0 6px; font-size: 13px; letter-spacing: 1px; color: var(--ink-dim); }
.cz-kw { margin: 0 0 10px; font-size: 13px; color: var(--brass-hi); letter-spacing: .5px; }
.cz-text {
  font-size: 15px; line-height: 1.45; color: var(--ink);
  border-top: 1px solid rgba(120,116,98,.25); padding-top: 10px;
}
.cz-text .cf-para { margin: 0; }
.cz-text .cf-para + .cf-para { margin-top: .5em; }
.cz-close {
  position: absolute; top: 14px; right: 18px; z-index: 2;
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
  font-size: 26px; line-height: 1; font-family: inherit;
  color: var(--ink); background: rgba(20,14,5,.85);
  border: 1px solid var(--metal-2);
}
.cz-close:hover { color: var(--brass-hi); border-color: var(--brass-lo); }

@media (max-width: 720px) {
  .collection-box { padding: 14px 12px 16px; }
  .collection-count { display: none; }
  .collection-box .lobby-head::after { display: block; }
  .col-row { gap: 9px; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
  /* En vertical no caben carta y ficha en fila: la ficha baja debajo y el
     conjunto scrollea. La carta se dimensiona por ANCHO (no por alto como en
     escritorio): con alto, a 390 px de pantalla se salía de la caja y arrastraba
     la ficha fuera del centro. */
  .collection-zoom { padding: 16px 12px; }
  .cz-inner {
    flex-direction: column; align-items: center; gap: 14px;
    width: 100%; overflow-y: auto;
  }
  .cz-card { height: auto; width: min(72vw, 310px); max-width: 100%; }
  .cz-info { flex: 0 0 auto; width: min(88vw, 420px); max-width: 100%; }
}

/* ============================================================
   V0.8 — Palabras clave CONCEDIDAS a una carta de la mano.
   Gavril le da Escudo divino al Gran Inquisidor mientras sigue en la mano o en
   el mazo: no sale en el texto impreso de la carta, asi que se rotula encima.
   (El chip de Arrasar sigue soportado aunque hoy ninguna carta lo conceda.)
   ============================================================ */
.kw-chips {
  position: absolute; left: 3%; right: 3%; bottom: 4%; z-index: 6;
  display: flex; flex-wrap: wrap; gap: 3px; justify-content: center;
  pointer-events: none;
}
.kw-chips:empty { display: none; }
.kw-chip {
  padding: 1px 4px; border-radius: 4px;
  font-size: clamp(6px, .78vw, 9px); font-weight: 800; letter-spacing: .5px;
  line-height: 1.35; white-space: nowrap;
  color: #140e05; border: 1px solid rgba(0,0,0,.55);
  box-shadow: 0 1px 3px rgba(0,0,0,.6);
}
.chip-divine  { background: linear-gradient(180deg, #ffe9a8, #e8b93c); }
.chip-trample { background: linear-gradient(180deg, #ffc78a, #ef8a2a); }
.chip-taunt   { background: linear-gradient(180deg, #d7d5a8, #9d9c5e); }
.chip-stealth { background: linear-gradient(180deg, #d3d8e6, #8f97ad); }
/* En la vista ampliada hay sitio de sobra: se leen a tamaño normal. */
.card-preview .kw-chips { bottom: 6%; gap: 6px; }
.card-preview .kw-chip {
  font-size: clamp(10px, 1.1vw, 14px); padding: 3px 9px; border-radius: 6px;
}

/* ============================================================
   V0.8 — CURACION. Halo verde + anillo que se expande + motas ascendentes +
   destello sobre la carta curada. Todo en #fx-layer (position:fixed,
   pointer-events:none) salvo el destello, que va en el propio esbirro.
   ============================================================ */
.heal-glow, .heal-ring, .heal-mote {
  position: fixed; pointer-events: none;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
}
/* Halo: bocanada de luz verde que nace en la carta y se apaga. */
.heal-glow {
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(196,255,178,.85) 0%, rgba(116,232,106,.55) 32%,
    rgba(60,180,80,.22) 58%, transparent 74%);
  filter: blur(2px);
  animation: healGlow .62s ease-out forwards;
}
.heal-glow.strong { animation-duration: .74s; filter: blur(1px); }
@keyframes healGlow {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.35); }
  22%  { opacity: 1; transform: translate(-50%, -50%) scale(.85); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.18); }
}
/* Anillo: el borde nitido que le da lectura de "restaurado" al halo. */
.heal-ring {
  border-radius: 50%;
  border: 3px solid rgba(170,255,150,.9);
  box-shadow: 0 0 16px 3px rgba(116,232,106,.6), inset 0 0 14px rgba(116,232,106,.45);
  animation: healRing .66s cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes healRing {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.25); border-width: 5px; }
  25%  { opacity: .95; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.24); border-width: 1px; }
}
/* Curacion en masa: el anillo casi no se expande, para que cuatro a la vez no
   se solapen en una sola mancha verde. */
.heal-ring.light { animation-name: healRingLight; animation-duration: .58s; }
@keyframes healRingLight {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.35); border-width: 4px; }
  28%  { opacity: .8; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.06); border-width: 1px; }
}
/* Motas: --drift/--rise/--mote los fija el JS por particula (si todas suben
   igual parece una barra, no un balsamo). */
.heal-mote {
  width: 9px; height: 9px; border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #f2ffe8 12%, #a6f58e 45%, #4cc45c 78%, transparent 92%);
  box-shadow: 0 0 8px 2px rgba(140,240,130,.75);
  animation: healMote .82s ease-out forwards;
}
@keyframes healMote {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.2); }
  20%  { opacity: 1; transform: translate(-50%, -50%) scale(var(--mote, 1)); }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--drift, 0px)), calc(-50% + var(--rise, -40px)))
               scale(calc(var(--mote, 1) * .45));
  }
}
/* Destello sobre lo curado. Es filter+box-shadow para no pelearse con las
   clases de estado del esbirro (taunt/divine/stealth ya usan box-shadow, asi
   que la animacion lo restituye al terminar). */
.minion.healed, .hero-portrait.healed {
  animation: healedPulse .62s ease-out;
}
@keyframes healedPulse {
  0%   { filter: none; }
  25%  { filter: brightness(1.32) saturate(1.3) drop-shadow(0 0 12px rgba(120,240,110,.95)); }
  100% { filter: none; }
}

/* Latido del numero de vida cuando sube. Remata la curacion en el sitio donde
   el jugador mira de verdad: la cifra. */
.mstat.hp.hp-up { animation: hpUp .54s ease-out; }
@keyframes hpUp {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.42); color: #b6ffa8; text-shadow: 0 0 10px rgba(120,240,110,.95), 0 2px 3px #000; }
  100% { transform: scale(1); }
}

/* Menos movimiento: se conserva el destello (informa) y se quitan las
   particulas y la expansion (decoran). */
@media (prefers-reduced-motion: reduce) {
  .heal-mote { display: none; }
  .heal-glow, .heal-ring { animation-duration: .3s; }
  .mstat.hp.hp-up { animation-duration: .25s; }
}

/* ============================================================
   V0.9 — VIRUS (mazo Ranlew)
   El verde ya lo usaba la curacion, asi que la infeccion se separa por dos ejes
   ademas del color: TONO (verde bilis amarillento vs esmeralda limpio) y
   DIRECCION (las esporas caen; las motas de curacion suben). Y el tic no usa el
   temblor seco del daño sino un balanceo lento: el daño golpea, el Virus marea.
   ============================================================ */

/* Numero del tic. Lleva ☣ delante para que no se confunda con el −N rojo. */
.fx-num.fx-virus {
  color: #cfe83f;
  text-shadow: 0 0 10px rgba(150,190,20,.9), 0 2px 3px #000;
}
/* El tic doblado es el golpe gordo del mazo: tiene que salir MÁS grande que el
   34px de `.fx-num`, no más pequeño. */
.fx-num.fx-virus.doubled {
  font-size: 44px; letter-spacing: 1px;
  text-shadow: 0 0 16px rgba(170,210,25,1), 0 2px 4px #000;
}

/* Vignette bilioso a viewport. Nodo propio (no reusa .dmg-vignette) porque el
   Virus y un golpe normal pueden solaparse en el mismo lote de eventos. */
.tox-vignette {
  position: fixed; inset: 0; z-index: 338;
  pointer-events: none; opacity: 0;
  background: radial-gradient(ellipse 80% 80% at 50% 50%,
              transparent 38%, rgba(120,150,20,.32) 70%, rgba(58,78,8,.92) 100%);
}
.tox-vignette.show        { animation: toxPulse .74s ease-in-out; }
.tox-vignette.show.strong { animation: toxPulseStrong .74s ease-in-out; }
@keyframes toxPulse {
  0% { opacity: 0; }
  35% { opacity: .5; }
  62% { opacity: .3; }
  100% { opacity: 0; }
}
@keyframes toxPulseStrong {
  0% { opacity: 0; }
  30% { opacity: .78; }
  58% { opacity: .45; }
  100% { opacity: 0; }
}

/* Balanceo mareante del campo. A proposito NO es bfShake: aquel son sacudidas
   cortas y rectas; este es lento, con rotacion y escala, y se va apagando. */
.battlefield.bf-sway        { animation: bfSway .76s cubic-bezier(.4,0,.4,1); }
.battlefield.bf-sway-strong { animation: bfSwayStrong .78s cubic-bezier(.4,0,.4,1); }
@keyframes bfSway {
  0%,100% { transform: rotate(0) scale(1); }
  25% { transform: rotate(.7deg) scale(1.012); }
  55% { transform: rotate(-.6deg) scale(1.006); }
  80% { transform: rotate(.25deg) scale(1.002); }
}
@keyframes bfSwayStrong {
  0%,100% { transform: rotate(0) scale(1); }
  22% { transform: rotate(1.4deg) scale(1.025); }
  50% { transform: rotate(-1.2deg) scale(1.012); }
  76% { transform: rotate(.5deg) scale(1.004); }
}

/* Esporas del contagio: caen sobre el retrato. --drift/--fall/--spore los fija
   el JS por particula. Turbias y con nucleo amarillento, no brillantes. */
.virus-spore {
  position: fixed; pointer-events: none;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
  width: 8px; height: 8px; border-radius: 50%;
  background: radial-gradient(circle at 36% 32%, #f0ff9e 10%, #c3dd2e 42%, #6c8410 76%, transparent 92%);
  box-shadow: 0 0 7px 2px rgba(160,200,30,.6);
  animation: virusSpore .86s ease-in forwards;
}
@keyframes virusSpore {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.25); }
  22%  { opacity: .95; transform: translate(-50%, -50%) scale(var(--spore, 1)); }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--drift, 0px)), calc(-50% + var(--fall, 40px)))
               scale(calc(var(--spore, 1) * .5));
  }
}

/* V0.15 — Efectos del mazo Ranlew (aperturas, explosiones y sticker). El arte
   vive en client/images/fx/ y se declara en fx.json; aquí sólo está la mecánica
   de reproducirlo. Si no hay PNG no se crea el nodo, así que estas reglas
   simplemente no llegan a usarse. */

/* Tira de fotogramas: un PNG horizontal que se recorre moviendo la posición del
   fondo con `steps()`. Sin canvas y sin un temporizador por fotograma — el
   navegador lo compone en la GPU y no toca el hilo principal, que es lo que hace
   falta en mitad de una cadena de estallidos. */
.fx-tira {
  position: fixed; pointer-events: none; z-index: 60;
  transform: translate(-50%, -50%);
  background-repeat: no-repeat;
  background-position: 0 0;
  will-change: background-position;
}
@keyframes fxTira {
  from { background-position: 0 0; }
  to   { background-position: var(--fx-fin, -100%) 0; }
}
/* Una tira escalonada (la segunda de una cadena) tiene que estar INVISIBLE
   durante su espera: si no, el primer fotograma se queda ahí quieto y se ve una
   caja cerrada aparecida de la nada antes de que le toque abrirse. El
   `backwards` es lo que aplica el 0% durante el retardo. */
@keyframes fxAparece { from { opacity: 0; } to { opacity: 1; } }

/* Sticker: entra de golpe, se queda quieto y se va. El rebote de entrada es
   corto a propósito —el 12% del total— para que el sticker se LEA: si la
   animación ocupa todo el tiempo, no da tiempo a mirarlo. */
.fx-sticker {
  position: fixed; pointer-events: none; z-index: 61;
  left: 50%; top: 46%;
  height: auto;
  /* El JS le pone el ancho; el techo lo pone aquí en unidades de pantalla. Hace
     falta el de ALTO además del de ancho: el sticker es más alto que ancho, y
     recortando sólo por ancho en un móvil se salía por arriba y por abajo. */
  max-width: 74vw; max-height: 56vh;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 6px 18px rgba(0,0,0,.75));
  animation-name: fxSticker;
  animation-timing-function: cubic-bezier(.2,.9,.25,1);
  /* `both` y no `forwards`: el sticker de JBauer sale con espera —cuando ha
     terminado de reventar— y sin el estado de ENTRADA asomaba entero, quieto,
     durante toda la cuenta atrás. */
  animation-fill-mode: both;
}
@keyframes fxSticker {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.35) rotate(-9deg); }
  12%  { opacity: 1; transform: translate(-50%, -50%) scale(1.12) rotate(3deg); }
  20%  { transform: translate(-50%, -50%) scale(1) rotate(0deg); }
  80%  { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(0deg); }
  100% { opacity: 0; transform: translate(-50%, -54%) scale(.94) rotate(0deg); }
}

/* Quien haya pedido menos movimiento se queda con el sticker quieto: entra y
   sale con opacidad, sin el golpe de escala ni el giro. La tira no se toca —
   son fotogramas dibujados, no movimiento de la interfaz. */
@media (prefers-reduced-motion: reduce) {
  .fx-sticker { animation-name: fxStickerSuave; }
  @keyframes fxStickerSuave {
    0%   { opacity: 0; }
    12%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    80%  { opacity: 1; }
    100% { opacity: 0; }
  }
}

/* --- Salpicadura de leche (el Brick) ---------------------------------------
   La tinta del blooper de Mario Kart, pero de leche. Es el ÚNICO efecto del
   juego que tapa el tablero, así que sólo sale entera sobre quien se come el
   Brick.

   Cada mancha son varios LÓBULOS del mismo blanco plano, superpuestos: un óvalo
   suelto se lee como un globo y lo que da sensación de líquido es la silueta
   rota. Como el blanco es plano no se ven costuras por dentro, y la sombra la
   pone el contenedor con `drop-shadow`, que sigue el contorno de la UNIÓN y no
   el de cada lóbulo. */
.fx-leche {
  position: fixed; pointer-events: none; z-index: 58;
  overflow: hidden;
}
.fx-leche.full { z-index: 62; }

/* OJO con el nombre: `fx-mancha` lo usan DOS cosas — la mancha de leche (esto) y
   la clase del número flotante «+N» del contador de Manchas. Sin acotar la regla
   al contenedor, el número heredaba `position: absolute` y la animación de la
   mancha, y en vez de flotar aparecía y se cortaba en seco a opacidad plena. */
.fx-leche .fx-mancha {
  position: absolute;
  transform: translate(-50%, -50%) scale(.15);
  opacity: 0;
  filter: drop-shadow(0 4px 10px rgba(48,40,26,.5));
  animation: fxMancha var(--fx-ms, 1700ms) cubic-bezier(.16,.9,.3,1) forwards;
  will-change: transform, opacity;
}
/* Impacto seco y salida lenta: la leche llega de golpe (8% de la duración) y el
   resto del tiempo escurre. Al revés parece nieve cayendo. */
@keyframes fxMancha {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.15); }
  8%   { opacity: 1; transform: translate(-50%, -50%) scale(1.16); }
  17%  { transform: translate(-50%, -50%) scale(1); }
  72%  { opacity: 1; transform: translate(-50%, -47%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -40%) scale(1.03); }
}
/* El giro va aquí y no en la mancha entera: los regueros cuelgan fuera del
   cuerpo para que caigan siempre hacia abajo, gire lo que gire la salpicadura. */
.fx-cuerpo { position: absolute; inset: 0; }
.fx-lobulo, .fx-rayo {
  position: absolute;
  transform: translate(-50%, -50%);
  background: #fdfaf0;
}
/* Rayo: la lengua alargada que sale disparada del centro. Es lo que separa una
   salpicadura de una nube. El extremo va en punta y el nacimiento, ancho. */
.fx-rayo {
  left: 50%; top: 50%;
  transform-origin: 0 50%;
  border-radius: 46% 60% 60% 46% / 50% 40% 60% 50%;
}
/* El brillo húmedo: una sola mancha de luz sobre la UNIÓN, no una por lóbulo.
   Es lo que separa "leche" de "papel recortado". */
.fx-brillo {
  position: absolute; left: 34%; top: 30%;
  transform: translate(-50%, -50%) rotate(-18deg);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255,255,255,.95), rgba(255,255,255,0));
}
/* El reguero cae DESDE la mancha: nace en su parte baja y se estira hacia
   abajo, con la punta redondeada — un churretón, no un pincho. */
.fx-reguero {
  position: absolute; top: 55%;
  transform: translateX(-50%);
  height: 4px;
  border-radius: 40% 40% 50% 50% / 8% 8% 92% 92%;
  background: #fdfaf0;
  transform-origin: 50% 0;
  animation: fxReguero var(--fx-ms, 1700ms) cubic-bezier(.2,.8,.3,1) forwards;
}
@keyframes fxReguero {
  0%   { height: 2px; }
  55%  { height: var(--fx-caida, 40px); }
  100% { height: var(--fx-caida, 40px); }
}
.fx-salpica {
  position: absolute;
  background: #fdfaf0;
  border-radius: 46% 54% 52% 48% / 50% 46% 54% 50%;
  box-shadow: 0 2px 6px rgba(48,40,26,.4);
  opacity: 0;
  transform: translate(-50%, -50%) scale(.2);
  animation: fxSalpica var(--fx-ms, 1700ms) ease-out forwards;
}
@keyframes fxSalpica {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.2); }
  10%  { opacity: .95; transform: translate(-50%, -50%) scale(1); }
  60%  { opacity: .9; }
  100% { opacity: 0; transform: translate(-50%, -34%) scale(.9); }
}

/* --- La carta robada, revelada antes de estallar ----------------------------
   Sale del mazo, se aparta hacia el centro, CRECE hasta que se lee y se queda
   quieta antes de irse. La pausa se lleva casi todo el tiempo a propósito (con
   los valores por defecto, 3 s de 3,58): es lo único que convierte "me ha
   explotado algo" en "me ha explotado un Brick de leche". */
.fx-revela {
  position: fixed; pointer-events: none; z-index: 63;
  border-radius: 8px;
  /* Fondo negro DETRÁS del arte: las plantillas de carta llevan alfa alrededor
     del marco, y sin nada debajo se veía el tablero por los huecos y la carta
     parecía un recorte flotando. El negro le devuelve el cuerpo. */
  background-color: #07060a;
  /* La carta va compuesta (marco + gemas + TEXTO) sobre un lienzo de 785×1100
     que se escala al ancho de aquí, igual que en la colección. `overflow` lo
     recorta mientras entra, que si no el lienzo sin escalar asoma un fotograma. */
  overflow: hidden;
  background-size: cover; background-position: center;
  box-shadow: 0 10px 28px rgba(0,0,0,.8), 0 0 0 2px rgba(220,190,110,.55),
              0 0 26px rgba(240,200,110,.35);
  /* DOS animaciones y no una: la entrada con `both` deja la carta clavada en su
     sitio al acabar, y la salida arranca cuando le toca. Así la pausa se alarga
     sin tocar el vuelo de entrada — con una sola animación de porcentajes, subir
     el tiempo total hacía la entrada más lenta en vez de dar más tiempo de
     lectura, que es justo lo que hace falta. */
  animation:
    fxRevelaEntra var(--fx-in, 300ms) cubic-bezier(.2,.9,.25,1) var(--fx-espera, 0ms) both,
    fxRevelaSale var(--fx-out, 280ms) ease-in
      calc(var(--fx-espera, 0ms) + var(--fx-in, 300ms) + var(--fx-quieta, 3000ms)) forwards;
  will-change: transform, opacity;
}
.fx-revela.dorso {
  background:
    repeating-linear-gradient(45deg, rgba(0,0,0,.18) 0 6px, rgba(255,255,255,.05) 6px 12px),
    linear-gradient(160deg, #3c3222, #221a10);
}
@keyframes fxRevelaEntra {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.18) rotate(-8deg); }
  to   { opacity: 1;
         transform: translate(calc(-50% + var(--fx-dx, 0px)), calc(-50% + var(--fx-dy, 0px)))
                    scale(1) rotate(0deg); }
}
@keyframes fxRevelaSale {
  from { opacity: 1;
         transform: translate(calc(-50% + var(--fx-dx, 0px)), calc(-50% + var(--fx-dy, 0px)))
                    scale(1) rotate(0deg); }
  to   { opacity: 0;
         transform: translate(calc(-50% + var(--fx-dx, 0px)), calc(-50% + var(--fx-dy, 0px)))
                    scale(.86) rotate(0deg); }
}

/* --- Cartas volando al mazo rival -------------------------------------------
   Tres capas anidadas para dibujar una parábola sin calcularla: la de fuera
   mueve en X a ritmo constante, la de dentro mueve en Y con una curva que
   primero sale hacia arriba, y la última gira y escala. Se anima `transform` y
   no `left`/`top` a propósito: lo segundo obliga al navegador a recalcular la
   página en cada fotograma, y aquí pueden volar tres cartas a la vez. */
.fx-vuelo {
  position: fixed; pointer-events: none; z-index: 60;
  animation: fxVueloX var(--fx-ms, 620ms) linear forwards;
}
@keyframes fxVueloX {
  from { transform: translateX(0); }
  to   { transform: translateX(var(--fx-dx, 0px)); }
}
/* La curva de salida negativa (-0.55) es la que levanta la carta antes de
   caer: sin ella el vuelo es una línea recta y parece un deslizamiento. */
.fx-salto {
  position: absolute; left: 0; top: 0;
  animation: fxVueloY var(--fx-ms, 620ms) cubic-bezier(.4,-0.55,.75,1) forwards;
}
@keyframes fxVueloY {
  from { transform: translateY(0); }
  to   { transform: translateY(var(--fx-dy, 0px)); }
}
.fx-carta {
  position: absolute;
  transform: translate(-50%, -50%) scale(.2) rotate(var(--fx-giro0, 0deg));
  border-radius: 6px;
  /* Igual que la revelada: sin fondo, el alfa del marco dejaba ver el tablero a
     través de la carta mientras vuela. */
  background-color: #07060a;
  background-size: cover; background-position: center;
  box-shadow: 0 6px 16px rgba(0,0,0,.7), 0 0 0 1px rgba(0,0,0,.6);
  /* `both` por lo mismo que la explosión: en un vuelo escalonado, las cartas
     2..n se quedaban quietas y visibles sobre el esbirro hasta que arrancaban. */
  opacity: 0;
  animation: fxVueloGiro var(--fx-ms, 620ms) ease-in both;
  will-change: transform, opacity;
}
/* Dorso: lo que vuela cuando el catálogo aún no ha llegado. Mismo trapo que los
   dorsos de la mano del rival, para que no parezca otra cosa. */
.fx-carta.dorso {
  background:
    repeating-linear-gradient(45deg, rgba(0,0,0,.18) 0 6px, rgba(255,255,255,.05) 6px 12px),
    linear-gradient(160deg, #3c3222, #221a10);
  border: 2px solid #0d0a05;
}
/* Crece a la mitad del vuelo —ahí es donde se LEE qué carta es— y se hunde al
   llegar: entra en el mazo, no aterriza encima. */
@keyframes fxVueloGiro {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.2) rotate(var(--fx-giro0, 0deg)); }
  12%  { opacity: 1; }
  50%  { transform: translate(-50%, -50%) scale(1) rotate(calc(var(--fx-giro1, 0deg) * .5)); }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(.28) rotate(var(--fx-giro1, 0deg)); }
}
/* El mazo acusa el impacto. */
.zone.fx-golpe { animation: fxGolpeMazo .34s cubic-bezier(.2,.9,.3,1); }
@keyframes fxGolpeMazo {
  0%   { transform: scale(1); box-shadow: none; }
  35%  { transform: scale(1.16); box-shadow: 0 0 14px rgba(215,190,110,.8); }
  100% { transform: scale(1); box-shadow: none; }
}

/* --- EL DADO: la ficha que aterriza de otro color ---------------------------
   El color de cada Caja se sortea al colocarla, y el sorteo era invisible: las
   verdes y las rojas volaban al mazo con la misma silueta y 200 ms de
   diferencia. Aquí la ficha llega verde, tiembla, se funde en blanco y sale
   roja, encima del contador de MAZO donde acaba de entrar.

   El blanco NO es un adorno: es lo que tapa el cambio de cara. Sin él se ve el
   corte de una imagen sustituyéndose por otra, que parece un fallo de carga. */
.fx-dado {
  position: fixed; pointer-events: none; z-index: 61;
  transform: translate(-50%, -50%);
  animation: fxDado 1180ms cubic-bezier(.2,.9,.3,1) both;
  will-change: transform, opacity;
}
@keyframes fxDado {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.34); }
  7%   { opacity: 1; transform: translate(-50%, -50%) scale(1.04); }
  12%  { transform: translate(-50%, -50%) scale(1); }
  /* El respingo del cambio, justo con el fogonazo. */
  36%  { transform: translate(-50%, -50%) scale(1.14); }
  46%  { transform: translate(-50%, -50%) scale(1.02); }
  82%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  /* Y se hunde en el mazo, como las que vuelan: entra dentro, no se posa. */
  100% { opacity: 0; transform: translate(-50%, -50%) scale(.3); }
}
.fx-dado-cara {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  border-radius: 6px;
  /* Fondo opaco por lo mismo que la carta que vuela: el alfa del marco dejaba
     ver el tablero a través de la ficha. */
  background-color: #07060a;
  background-size: cover; background-position: center;
  box-shadow: 0 8px 20px rgba(0,0,0,.75), 0 0 0 1px rgba(0,0,0,.6);
  animation: fxDadoTiembla 420ms ease-in-out both;
  transition: box-shadow .3s ease-out;
}
.fx-dado-cara.dorso {
  background:
    repeating-linear-gradient(45deg, rgba(0,0,0,.18) 0 6px, rgba(255,255,255,.05) 6px 12px),
    linear-gradient(160deg, #3c3222, #221a10);
}
/* El temblor CRECE hasta el fogonazo en vez de apagarse: lo que anuncia no es
   un golpe recibido, es que algo está a punto de pasar. */
@keyframes fxDadoTiembla {
  0%   { transform: translateX(0) rotate(0deg); }
  20%  { transform: translateX(-2px) rotate(-.6deg); }
  38%  { transform: translateX(3px) rotate(.9deg); }
  56%  { transform: translateX(-4px) rotate(-1.3deg); }
  72%  { transform: translateX(5px) rotate(1.7deg); }
  86%  { transform: translateX(-6px) rotate(-2deg); }
  100% { transform: translateX(0) rotate(0deg); }
}
/* Ya virada: el rojo se queda un momento alrededor. Es la única pista de color
   que sobrevive al blanco, y es la que se recuerda. */
.fx-dado.virada .fx-dado-cara {
  box-shadow:
    0 8px 20px rgba(0,0,0,.75), 0 0 0 1px rgba(0,0,0,.6),
    0 0 30px rgba(214,66,30,.9), 0 0 60px rgba(214,66,30,.45);
}
.fx-dado-flash {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  border-radius: 6px; background: #fff; opacity: 0;
  animation: fxDadoFogonazo 1180ms ease-out both;
}
@keyframes fxDadoFogonazo {
  0%   { opacity: 0; }
  28%  { opacity: .12; }
  36%  { opacity: 1; }
  44%  { opacity: .92; }
  64%  { opacity: 0; }
  100% { opacity: 0; }
}

/* --- Contador de lo sembrado en un mazo ------------------------------------
   Va en la franja de zonas, al lado de MAZO, porque es exactamente eso: lo que
   hay DENTRO de ese mazo esperando. Se pinta en las dos franjas —el que siembra
   necesita saber cuánto le queda por estallar y el que lo tiene dentro necesita
   poder jugar sabiéndolo— y sólo aparece cuando hay algo. */
.zone.sembrada { min-width: 34px; }
.zone.sembrada .zicon { font-size: 14px; }
.zone.sembrada .zcount { font-size: 14px; }
/* La chapa de Cajas ROTAS lleva un fotograma de la animación de apertura en vez
   de un emoji: el resto de la franja son emojis a color y éste es la única
   imagen, así que se le da el mismo tamaño de caja para que no descuadre la
   fila. `block` quita el hueco de línea base que deja un <img> en línea. */
.zone.sembrada.rotas .zicon img {
  display: block; width: 18px; height: 18px; object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.8));
}
/* El "−N" del rótulo de la chapa roja: lo que le va a hacer A LA CARA la próxima
   que reviente, que sube con cada roja ya estallada. Va en el rótulo y no junto
   a la cifra de Cajas restantes, que ahí los dos números se leían como uno. */
.zone.sembrada .zsub {
  font-weight: 900; color: var(--blood-hi, #ff9a8a);
}
/* El color va al NÚMERO, no al icono: los iconos son emojis a color (🥛🟩🟥) y
   se pintan solos, así que un `color` sobre ellos no hacía nada. Tiñendo la
   cifra, cada contador se distingue de un vistazo aunque el borde quede
   apagado sobre el fondo. */
.zone.sembrada.brick  { border-color: #4a6f9e; }
.zone.sembrada.brick  .zcount { color: #cfe2f7; }
.zone.sembrada.verde  { border-color: #4f7a2a; }
.zone.sembrada.verde  .zcount { color: #a8d878; }
.zone.sembrada.roja   { border-color: #8f2a20; }
.zone.sembrada.roja   .zcount { color: #f0a094; }

/* --- Explosión roja (sin arte) ---------------------------------------------
   Fogonazo, núcleo, onda, esquirlas y pavesas, pintados con divs. Va con divs y
   no con una tira de fotogramas para poder tirarla sobre CUALQUIER cosa —el
   mazo, un esbirro, el héroe— escalando con el nodo: una tira de tamaño fijo no
   hace eso. */
.fx-boom {
  position: fixed; pointer-events: none; z-index: 59;
  width: 0; height: 0;
}
.fx-fogonazo, .fx-nucleo, .fx-onda, .fx-esquirla, .fx-pavesa {
  position: absolute; left: 0; top: 0;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
  /* Invisible hasta que le toque. Con `forwards` a secas, una explosión
     retrasada —y ahora se retrasan lo que dure la carta revelada, hasta 3,6 s—
     se quedaba CONGELADA en pantalla a opacidad 1 durante toda la espera y
     luego saltaba a cero para empezar. `both` aplica el 0% durante el retardo,
     que es justo lo que hace falta. */
  opacity: 0;
}
/* El fogonazo dura un suspiro y es lo que hace que la explosión "pegue": sin él
   el núcleo crece desde nada y parece que se infla. */
.fx-fogonazo {
  width: calc(var(--fx-d, 170px) * .8); height: calc(var(--fx-d, 170px) * .8);
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, rgba(255,240,210,.9) 42%, rgba(255,190,120,0) 72%);
  animation: fxFogonazo calc(var(--fx-ms, 640ms) * .35) ease-out both;
}
@keyframes fxFogonazo {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.2); }
  22%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.3); }
}
.fx-nucleo {
  width: var(--fx-d, 170px); height: var(--fx-d, 170px);
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%,
    #fff 0%, #ffe6b0 14%, #ff8a3c 32%, #e02818 56%, rgba(150,20,12,0) 76%);
  animation: fxNucleo var(--fx-ms, 640ms) ease-out both;
}
@keyframes fxNucleo {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.12); }
  14%  { opacity: 1; transform: translate(-50%, -50%) scale(.78); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.2); }
}
.fx-onda {
  width: var(--fx-d, 170px); height: var(--fx-d, 170px);
  border-radius: 50%;
  border: 3px solid rgba(255,190,140,.9);
  box-shadow: 0 0 18px rgba(255,110,60,.75), inset 0 0 14px rgba(255,140,80,.5);
  animation: fxOnda var(--fx-ms, 640ms) cubic-bezier(.1,.7,.3,1) both;
}
@keyframes fxOnda {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.15); border-width: 6px; }
  18%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.6); border-width: 1px; }
}
.fx-esquirla {
  border-radius: 2px;
  background: linear-gradient(90deg, #fff2cf, #ff7a2e 45%, #c31d12);
  box-shadow: 0 0 10px rgba(255,120,50,.85);
  animation: fxEsquirla var(--fx-ms, 640ms) cubic-bezier(.12,.75,.28,1) both;
}
@keyframes fxEsquirla {
  0%   { opacity: 0; transform: rotate(var(--fx-ang, 0deg)) translateX(0) scaleX(.35); }
  14%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(var(--fx-ang, 0deg)) translateX(var(--fx-dist, 60px)) scaleX(1); }
}
/* Las pavesas se apagan DESPUÉS que el resto (×1.5): sin una brasa que quede
   flotando, la explosión se corta en seco. */
.fx-pavesa {
  border-radius: 50%;
  background: radial-gradient(circle, #fff3d0, #ff9a3c 55%, rgba(200,40,20,0));
  box-shadow: 0 0 8px rgba(255,150,70,.9);
  animation: fxPavesa calc(var(--fx-ms, 640ms) * 1.5) ease-out both;
}
@keyframes fxPavesa {
  0%   { opacity: 0; transform: rotate(var(--fx-ang, 0deg)) translateX(0); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(var(--fx-ang, 0deg)) translateX(var(--fx-dist, 60px)); }
}

/* Quien haya pedido menos movimiento se queda con lo que INFORMA (la mancha, el
   fogonazo) y sin lo que sólo se mueve: ni esquirlas volando ni regueros. */
@media (prefers-reduced-motion: reduce) {
  .fx-esquirla, .fx-pavesa, .fx-reguero { display: none; }
  .fx-leche .fx-mancha, .fx-salpica { animation-timing-function: ease-out; }
  /* La carta revelada se QUEDA: es información, no adorno — sin ella vuelve el
     "no sé ni por qué me explota". Lo que se quita es el giro de las que vuelan
     al mazo, que cruzan la pantalla dando vueltas. */
  .fx-carta { animation-name: fxVueloSuave; }
  @keyframes fxVueloSuave {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(.35); }
    12%  { opacity: 1; }
    50%  { transform: translate(-50%, -50%) scale(.9); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(.35); }
  }
  /* El dado se queda: el fogonazo y el cambio de cara son la única explicación
     de por qué esa Caja ha salido roja. Lo que se quita es el temblor, que es lo
     único de ahí que sólo se mueve. */
  .fx-dado-cara { animation: none; }
  /* Hermana de la espora del Virus, que ya se ocultaba aquí: las dos caen desde
     arriba y las dos sobran si has pedido menos movimiento. */
  .mancha-gota { display: none; }
}

/* Móvil. El número de piezas ya lo baja el JS; lo que se recorta aquí es lo
   CARO de pintar. `drop-shadow` sobre una mancha que además escala obliga al
   móvil a rasterizar la unión de todos los lóbulos en cada fotograma, y son
   varias a la vez: se cambia por una sombra plana, que no depende de la forma.
   Los resplandores de la explosión bajan por el mismo motivo. */
@media (max-width: 719px) {
  /* Sin sombra, y sin ponérsela a cada lóbulo por su cuenta: probado, y lo que
     sale es peor — cada lóbulo proyecta la suya SOBRE los de al lado y se ven
     las costuras por dentro de la mancha, que es justo lo que el `drop-shadow`
     del contenedor evitaba. Sobre el tablero oscuro, el blanco plano se recorta
     igual de bien sin nada. */
  .fx-leche .fx-mancha { filter: none; }
  .fx-esquirla { box-shadow: none; }
  .fx-onda { box-shadow: 0 0 12px rgba(255,110,60,.6); }
}

/* V0.15 — Mancha de leche. Misma coreografía que la espora del Virus (cae desde
   arriba sobre el retrato) con otro color: blanco roto en vez de verde bilis.
   Comparten gesto porque son la misma clase de aviso, y se distinguen por el
   color porque hacen cosas distintas: aquello come vida y esto sólo marca. */
.mancha-gota {
  position: fixed; pointer-events: none;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
  width: 8px; height: 8px; border-radius: 50%;
  background: radial-gradient(circle at 36% 32%, #ffffff 10%, #f3ecdc 42%, #b9ac93 76%, transparent 92%);
  box-shadow: 0 0 7px 2px rgba(245,240,225,.55);
  animation: virusSpore .86s ease-in forwards;
}

/* Salpicadura al manchar: destello lechoso corto, hermano de `infectingFlash`. */
.hero-portrait.manchando { animation: manchandoFlash .6s ease-out; }
@keyframes manchandoFlash {
  0%   { filter: none; }
  30%  { filter: brightness(1.35) drop-shadow(0 0 12px rgba(255,252,240,.95)); }
  100% { filter: none; }
}

/* Chapa de Manchas, hermana de la del Virus pero ARRIBA A LA DERECHA: la de
   Virus ocupa la izquierda y la de vida la esquina de abajo, así que las tres
   pueden crecer a dos cifras sin pisarse. */
.mancha-badge {
  position: absolute; top: -7px; right: -12px; z-index: 3;
  min-width: 30px; height: 24px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center; gap: 2px;
  font-weight: 800; font-size: 14px; line-height: 1; color: #2a2418;
  background: radial-gradient(circle at 35% 28%, #ffffff, #efe6d2 58%, #b3a68c);
  border: 2px solid #3b3222; border-radius: 12px;
  text-shadow: 0 1px 0 rgba(255,255,255,.6);
  box-shadow: 0 2px 4px rgba(0,0,0,.55), 0 0 12px rgba(240,235,215,.45);
}
.mancha-badge .mb-ico { font-size: 12px; opacity: .95; }

/* Estado permanente del infectado: latido bilioso mientras quede Virus. La
   intensidad la fija el JS en --vx (0..1) segun la dosis acumulada.
   `:not(.target-legal)` para que el anillo rojo de "objetivo legal" siga
   mandando: esa informacion es de juego y esta solo decora. */
.hero-portrait.infected:not(.target-legal) {
  animation: infectedPulse 2.1s ease-in-out infinite;
}
@keyframes infectedPulse {
  0%,100% {
    box-shadow: 0 0 0 2px rgba(140,175,25,calc(.30 + .35 * var(--vx, .5))),
                0 0 10px rgba(150,190,30,calc(.22 + .40 * var(--vx, .5))),
                inset 0 2px 5px rgba(255,255,255,.25), inset 0 -4px 8px rgba(0,0,0,.6);
  }
  50% {
    box-shadow: 0 0 0 4px rgba(175,210,45,calc(.42 + .45 * var(--vx, .5))),
                0 0 20px rgba(180,220,50,calc(.35 + .55 * var(--vx, .5))),
                inset 0 2px 5px rgba(255,255,255,.25), inset 0 -4px 8px rgba(0,0,0,.6);
  }
}

/* OJO A LA CASCADA. La regla de arriba es (0,3,0) —el argumento de `:not()`
   cuenta para la especificidad— y declara `animation`. Todas las animaciones
   PUNTUALES del retrato son (0,2,0) y declaran esa misma propiedad, asi que sin
   esto las perderia TODAS mientras el heroe estuviera infectado... que es
   justo cuando hacen falta: un infectado siempre lleva `.infected` cuando le
   pica el Virus, de modo que la onda del tic no se veria JAMAS, y de rebote se
   apagaban tambien el temblor del golpe y el destello de curacion, que
   funcionaban antes de que existiera el Virus.
   Se empatan aqui a (0,3,0) y manda el orden del fichero, que ya es el bueno.
   `.target-legal` no necesita rescate: el `:not()` ya lo deja fuera.

   Y OJO AL ORDEN DE ESTAS CINCO. Un elemento solo tiene un hueco de
   `animation`, asi que cuando dos caen en el mismo lote de eventos gana la
   ultima del fichero. Pasa constantemente: con Markal en mesa, cada ronda trae
   `virus` (contagio) y `virusTick` (el tic) sobre el mismo retrato. Manda el
   TIC, que es el que cuenta perdida de vida; el contagio ya tiene sus esporas
   en nodos aparte y no se queda mudo. Por eso `.corroding` va la ultima.
   El mismo criterio rige fuera de este bloque: en el CSS de abajo `.infecting`
   se declara antes que `.corroding`, asi que el caso sin infectar coincide. */
.hero-portrait.infected.infecting        { animation: infectingFlash .6s ease-out; }
.hero-portrait.infected.shaking          { animation: shakeHit .34s ease; }
.hero-portrait.infected.healed           { animation: healedPulse .62s ease-out; }
.hero-portrait.infected.corroding        { animation: corrode .68s ease-in-out; }
.hero-portrait.infected.corroding-strong { animation: corrodeStrong .68s ease-in-out; }

/* Contagio (sube el contador): destello acido corto. */
.hero-portrait.infecting { animation: infectingFlash .6s ease-out; }
@keyframes infectingFlash {
  0%   { filter: none; }
  30%  { filter: saturate(1.5) drop-shadow(0 0 12px rgba(190,230,50,.95)); }
  100% { filter: none; }
}

/* El tic. Distinto del destello de curacion a proposito: aquello es un fogonazo
   brillante y esto es una descomposicion — primero se apaga y desatura, luego
   vira a bilis y vuelve. */
.hero-portrait.corroding        { animation: corrode .68s ease-in-out; }
.hero-portrait.corroding-strong { animation: corrodeStrong .68s ease-in-out; }
@keyframes corrode {
  0%   { filter: none; }
  30%  { filter: saturate(.35) brightness(.78); }
  60%  { filter: sepia(.75) hue-rotate(38deg) saturate(2.4) brightness(1.06)
                 drop-shadow(0 0 12px rgba(160,200,25,.9)); }
  100% { filter: none; }
}
@keyframes corrodeStrong {
  0%   { filter: none; }
  26%  { filter: saturate(.2) brightness(.62); }
  58%  { filter: sepia(.9) hue-rotate(42deg) saturate(3.4) brightness(1.18)
                 drop-shadow(0 0 20px rgba(190,235,40,1)); }
  100% { filter: none; }
}

/* Chapa pegada al retrato, hermana de .hp-badge. Es el sitio donde de verdad se
   mira —justo al lado de los puntos de vida que el Virus se va a comer— porque
   en la franja de zonas competía de tú a tú con MAZO y CEMENT., que no hacen
   daño, y el rival podía llegar a 8 de Virus sin que te enteraras. */
/* ARRIBA a la izquierda, no abajo: la chapa de vida ocupa la esquina inferior
   derecha y con dos cifras en cada una (☣10 junto a 34 de vida) se solapaban 5px
   y el Virus tapaba los puntos de vida. En filas distintas no pueden chocar por
   mucho que crezcan. La franja no recorta, así que el sangrado hacia arriba se
   ve entero. */
.virus-badge {
  position: absolute; top: -7px; left: -12px; z-index: 3;
  min-width: 30px; height: 24px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center; gap: 2px;
  font-weight: 800; font-size: 14px; line-height: 1; color: #f2ffb0;
  background: radial-gradient(circle at 35% 28%, #c3dd2e, #7d9b12 58%, #35440a);
  border: 2px solid #1b2305; border-radius: 12px;
  text-shadow: 0 1px 2px #000;
  box-shadow: 0 2px 4px rgba(0,0,0,.55), 0 0 12px rgba(160,200,30,.6);
}
.virus-badge .vb-ico { font-size: 12px; opacity: .95; }
/* Doblador armado: la chapa late, que es una sorpresa demasiado cara para
   dejarla en un número quieto. */
/* Doblador armado: late y cambia de borde. NO se le mete el "×2" dentro — la
   chapa se ensancharía hasta tapar la vida del retrato, que es lo único que no
   puede esconder. La cifra doblada vive en el contador de zonas y el rótulo. */
.virus-badge.doubled {
  animation: virusBadgePulse 1.05s ease-in-out infinite;
  border-color: #6b3a06;
}
@keyframes virusBadgePulse {
  0%,100% { box-shadow: 0 2px 4px rgba(0,0,0,.55), 0 0 12px rgba(160,200,30,.6); }
  50%     { box-shadow: 0 2px 4px rgba(0,0,0,.55), 0 0 20px rgba(215,255,60,1); transform: scale(1.08); }
}

/* Contador junto a MAZO / CEMENT. / FATIGA. Se queda como refuerzo (lleva el
   rótulo VIRUS, que es lo que explica la chapa), pero con más peso que sus
   vecinos: la cifra manda y el fondo tira a verde. */
.zone.virus {
  background: linear-gradient(180deg, rgba(150,190,25,.16), rgba(60,80,10,.28));
  border-color: rgba(160,200,35,.45);
}
.zone.virus .zicon  { color: #cfe83f; }
.zone.virus .zcount { color: #eaff6a; font-size: 17px; text-shadow: 0 0 8px rgba(170,210,25,.7); }
.zone.virus .zlbl   { color: #b9cf72; }
.zone.virus.doubled { animation: virusDoubleBlink 1.15s ease-in-out infinite; }
.zone.virus.doubled .zcount { color: #eaff6a; }
@keyframes virusDoubleBlink {
  0%,100% { text-shadow: none; }
  50% { text-shadow: 0 0 9px rgba(200,240,60,.95); }
}

/* Menos movimiento: se conservan color y estado (informan) y se quitan las
   particulas, el balanceo y el latido continuo (decoran). */
@media (prefers-reduced-motion: reduce) {
  .virus-spore { display: none; }
  .battlefield.bf-sway, .battlefield.bf-sway-strong { animation: none; }
  /* El latido continuo se cambia por un anillo fijo: informa igual sin moverse. */
  .hero-portrait.infected:not(.target-legal) { animation: none;
    box-shadow: 0 0 0 3px rgba(160,195,35,.7),
                inset 0 2px 5px rgba(255,255,255,.25), inset 0 -4px 8px rgba(0,0,0,.6); }
  .zone.virus.doubled { animation: none; }
  /* Y las puntuales se acortan, pero NO se quitan: son las que dicen qué acaba
     de pasar. Van después del `animation: none` de arriba y con su misma
     especificidad (0,3,0), que si no se las llevaría por delante igual que
     fuera del media query. */
  .hero-portrait.infected.infecting { animation: infectingFlash .3s ease-out; }
  .hero-portrait.infected.shaking   { animation: shakeHit .2s ease; }
  .hero-portrait.infected.healed    { animation: healedPulse .3s ease-out; }
  /* El tic, el último, por lo mismo que fuera del media query. */
  .hero-portrait.infected.corroding,
  .hero-portrait.corroding        { animation: corrode .3s ease-in-out; }
  .hero-portrait.infected.corroding-strong,
  .hero-portrait.corroding-strong { animation: corrodeStrong .3s ease-in-out; }
}

/* ============================================================
   AVISO DE SOLO VERTICAL (V0.11)
   El juego se diseñó en vertical. Girado, la mano y la franja del héroe propio
   caen por debajo del borde y `body{overflow:hidden}` impide llegar a ellas:
   medido, en 844x390 la mano se dibuja en y=478 y tu héroe queda 82px fuera; en
   740x360 el END TURN se monta encima del tablero rival y tocar a un esbirro
   enemigo pasa el turno. Se bloquea con un aviso en vez de dejarlo roto.

   La condición lleva `max-height` a propósito: un escritorio también es
   "landscape" pero tiene alto de sobra, así que sin el tope se bloquearía el
   juego en ordenador. 500px separa un móvil girado (390, 360) de un portátil
   pequeño (640+).
   ============================================================ */
#rotate-notice { display: none; }

@media (orientation: landscape) and (max-height: 500px) {
  #rotate-notice {
    display: flex; align-items: center; justify-content: center;
    position: fixed; inset: 0;
    /* Por encima del grano de película (9000) y de los overlays (9100). */
    z-index: 9500;
    padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
             max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
    background:
      radial-gradient(120% 100% at 50% 40%, rgba(178,138,67,.07), transparent 60%),
      linear-gradient(180deg, #0b0a08, #030302);
    text-align: center;
  }
  .rn-inner {
    max-width: 420px; padding: 20px 26px;
    border: 1px solid var(--gold); border-radius: 2px;
    background: linear-gradient(180deg, var(--soot), var(--void));
    box-shadow:
      inset 0 1px 0 rgba(217,181,109,.4),
      inset 0 -1px 0 rgba(107,79,34,.8),
      0 10px 30px rgba(0,0,0,.8);
  }
  .rn-icon {
    font-size: 40px; line-height: 1; color: var(--gold-hi);
    text-shadow: 0 2px 6px #000;
    animation: rnGirar 2.4s ease-in-out infinite;
  }
  .rn-title {
    margin: 12px 0 6px; font-family: var(--font-display);
    font-size: clamp(18px, 4.5vw, 26px); font-weight: 700; letter-spacing: 3px;
    color: var(--bone); text-shadow: 0 2px 0 #000;
  }
  .rn-sub {
    margin: 0; font-size: 13px; letter-spacing: .5px; color: var(--bone-dim);
  }
  /* Con el aviso puesto, lo de debajo no debe poder tocarse ni leerse. */
  .screen, #deck-modal, #overlay-gameover, .music-btn { visibility: hidden; }
}
@keyframes rnGirar {
  0%, 60%, 100% { transform: rotate(0deg); }
  80%           { transform: rotate(-90deg); }
}
@media (prefers-reduced-motion: reduce) {
  .rn-icon { animation: none; }
}

/* ---------- Cierre de la vista ampliada (V0.11.2) ----------
   El contenedor tiene `pointer-events: none` para no estorbar al juego, así que
   la X los recupera explícitamente. Solo aparece cuando la vista está FIJADA
   —es decir, abierta con pulsación larga en táctil—: con el ratón se cierra
   sola al quitar el cursor y una X ahí sobraría. */
.card-preview .cp-close { display: none; }
.card-preview.fijado .cp-close {
  display: flex; align-items: center; justify-content: center;
  position: absolute; top: -14px; right: -14px; z-index: 2;
  width: 46px; height: 46px; /* zona táctil cómoda, por encima de los 44px */
  pointer-events: auto; cursor: pointer;
  font-family: var(--font-ui); font-size: 26px; line-height: 1; font-weight: 700;
  color: var(--bone);
  background: linear-gradient(180deg, var(--soot), var(--void));
  border: 2px solid var(--gold); border-radius: 50%;
  box-shadow:
    inset 0 1px 0 rgba(217,181,109,.5),
    0 0 0 1px rgba(0,0,0,.9),
    0 4px 14px rgba(0,0,0,.8);
}
.card-preview.fijado .cp-close:active { transform: scale(.94); filter: brightness(.9); }

/* ---------- Visor de cementerio (V0.11.2) ---------- */
.grave-box {
  width: min(96vw, 1000px); max-height: 92vh;
  /* 92vh son 784 px en un iPhone 16 con 659 a la vista: con el cementerio lleno
     el borde de abajo de la caja se iba debajo de la barra de Safari. */
  max-height: 92dvh;
  padding: 16px 18px 18px; display: flex; flex-direction: column;
}
.grave-box .collection-grid { flex: 1 1 auto; min-height: 0; }
/* Más pequeñas que en la colección: un cementerio se OJEA —«¿qué ha caído?»— y
   ahí lo que importa es ver muchas de un vistazo, no leerlas una a una. Para eso
   está la colección. */
#gv-grid .col-row {
  gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(clamp(78px, 8vw, 104px), 1fr));
}
@media (max-width: 480px) {
  #gv-grid .col-row { gap: 7px; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); }
}
/* El contador de cementerio es pulsable cuando hay algo dentro. Se marca con el
   filo de latón para que se vea que se puede tocar, y con zona táctil cómoda. */
.zone.grave.abrible {
  cursor: pointer; border-radius: 2px;
  border: 1px solid var(--gold-lo);
  min-width: 44px; min-height: 44px;
  transition: border-color .14s, background .14s;
}
.zone.grave.abrible:hover,
.zone.grave.abrible:focus-visible {
  border-color: var(--gold); background: rgba(178,138,67,.1); outline: none;
}
.zone.grave.abrible:focus-visible { box-shadow: 0 0 0 2px var(--gold-hi); }
.zone.grave.abrible .zicon { color: var(--gold); }
