/* juego.css · El "game feel" del estudiante (encargo de Christiam, 2026-10-06: "animación plena;
   quienes la van a usar son los chicos"). Referencia de solo lectura: coins-mvp/student.html y
   styles.css (ver docs/INVENTARIO_game_feel.md). Reglas de esta hoja:
   - solo var(--token) de tokens.css o color-mix() con transparent/white/black (V3);
   - solo se anima transform y opacity (nada que bloquee el hilo principal);
   - el apagador global de base.css (@media prefers-reduced-motion) deja TODO en 0,001 ms: aquí no
     se repite esa lógica, pero sí se garantiza que el estado final sea legible sin animación;
   - la animación plena es del estudiante: el panel del profe y del admin no carga nada de esto
     (los selectores cuelgan de .juego, que solo ponen las vistas del estudiante). */

/* ---------- Monedas que vuelan (ui/monedas.js) ---------- */
.ficha-moneda {
  position: fixed;
  left: 0;
  top: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--oro) 35%, white), var(--oro) 65%);
  border: 2px solid color-mix(in srgb, var(--oro) 65%, black);
  pointer-events: none;
  z-index: 40;
  will-change: transform, opacity;
}
.ficha-moneda::after {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--oro) 60%, black);
  opacity: 0.45;
}

/* ---------- Golpe de oro sobre un contador (ui/monedas.js: pulsoOro) ---------- */
.pulso-oro {
  position: relative;
  transform-origin: left center;
  animation: pulso-oro 800ms cubic-bezier(0.2, 1.5, 0.4, 1) 1;
}
.pulso-oro::before {
  content: "";
  position: absolute;
  inset: -6px -10px;
  border-radius: var(--radio-md);
  box-shadow: 0 0 0 3px var(--oro), 0 0 20px color-mix(in srgb, var(--oro) 70%, transparent);
  pointer-events: none;
  animation: halo-oro 800ms ease-out 1 both;
}
@keyframes pulso-oro {
  0% { transform: scale(1); }
  35% { transform: scale(1.4); }
  60% { transform: scale(0.94); }
  100% { transform: scale(1); }
}
@keyframes halo-oro {
  0% { opacity: 0; }
  30% { opacity: 1; }
  100% { opacity: 0; }
}

/* ---------- Confeti (ui/confeti.js): tres fuerzas. El de verdad lo dibuja canvas-confetti en UN <canvas> que cubre la
   pantalla (canvas-confetti toma el tamaño de lo que mide el elemento); el de abajo, de piezas de CSS, es el respaldo si
   la librería no cargara. Cada pieza trae su posición, deriva, giro, retraso, duración y caída por variables CSS
   que pone el módulo (nada es azar). ---------- */
.confeti-lienzo {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 60;
}
.confeti {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 50;
}
.confeti-pieza {
  position: absolute;
  top: -14px;
  left: var(--x, 50%);
  width: var(--tam, 9px);
  height: calc(var(--tam, 9px) * 1.5);
  border-radius: 2px;
  opacity: 0.95;
  animation: confeti-cae var(--dur, 1800ms) cubic-bezier(0.25, 0.6, 0.5, 1) var(--retraso, 0ms) both;
}
@keyframes confeti-cae {
  0% { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
  80% { opacity: 1; }
  100% { transform: translate3d(var(--dx, 0px), var(--caida, 400px), 0) rotate(var(--giro, 360deg)); opacity: 0; }
}
.confeti-pieza:nth-child(6n+1) { background: var(--oro); }
.confeti-pieza:nth-child(6n+2) { background: var(--primario); }
.confeti-pieza:nth-child(6n+3) { background: var(--secundario); }
.confeti-pieza:nth-child(6n+4) { background: var(--correcto); }
.confeti-pieza:nth-child(6n+5) { background: color-mix(in srgb, var(--oro) 50%, white); }
.confeti-pieza:nth-child(6n) { background: color-mix(in srgb, var(--primario) 45%, white); }

/* ---------- La llama de la constancia (ui/racha.js): dibujada con CSS en oro, nunca con un emoji.
   Sin animación (reduced-motion) queda como una llama quieta, igual de legible. ---------- */
.llama {
  position: relative;
  display: inline-block;
  width: 0.95em;
  height: 1.3em;
  vertical-align: -0.2em;
  margin-right: 4px;
  transform-origin: 50% 100%;
  animation: llama-parpadea 1.15s ease-in-out infinite;
}
.llama-cuerpo,
.llama-nucleo {
  position: absolute;
  inset: 0;
  clip-path: polygon(50% 0%, 66% 24%, 90% 50%, 94% 76%, 74% 98%, 26% 98%, 6% 76%, 10% 50%, 34% 26%, 40% 12%);
}
.llama-cuerpo {
  background: linear-gradient(to top, var(--oro), color-mix(in srgb, var(--oro) 55%, white));
}
.llama-nucleo {
  inset: 38% 22% 0;
  background: linear-gradient(to top, color-mix(in srgb, var(--oro) 25%, white), color-mix(in srgb, var(--oro) 4%, white));
  transform-origin: 50% 100%;
  animation: llama-nucleo 0.7s ease-in-out infinite;
}
@keyframes llama-parpadea {
  0%, 100% { transform: scale(1, 1) rotate(-2deg); }
  25% { transform: scale(1.07, 0.93) rotate(2deg); }
  50% { transform: scale(0.94, 1.09) rotate(-1deg); }
  75% { transform: scale(1.04, 0.97) rotate(3deg); }
}
@keyframes llama-nucleo {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(0.78); }
}

/* Constancia que sube: la insignia salta con rebote y la llama se agranda un momento. */
.constancia-sube {
  transform-origin: left center;
  animation: constancia-salta 1100ms cubic-bezier(0.2, 1.6, 0.4, 1) 1;
}
.constancia-sube .llama {
  animation: llama-parpadea 0.35s ease-in-out infinite;
}
@keyframes constancia-salta {
  0% { transform: scale(1); }
  30% { transform: scale(1.45) rotate(-4deg); }
  55% { transform: scale(0.92) rotate(2deg); }
  80% { transform: scale(1.12); }
  100% { transform: scale(1); }
}

/* Aviso "¡Constancia N!": un renglón en el FLUJO de la pantalla, en oro suave (racha = logro). No flota: flotando tapaba el saludo. */
.celebra-racha {
  display: flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  max-width: 100%;
  margin: 8px 0 12px;
  padding: 8px 14px;
  background: var(--sx-oro-suave);
  color: var(--sx-sobre-oro-suave);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--oro) 45%, transparent);
  border-radius: var(--radio-sm);
  font-family: var(--fuente-titulos);
  font-weight: 800;
  font-size: 1.05rem;
  line-height: 1.25;
  animation: racha-entra 400ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
.celebra-racha .llama {
  flex: none;
  font-size: 1.4rem;
}
/* La llama del aviso va quieta: parpadea solo la de la cabecera (medido con fluidez). */
.celebra-racha .llama,
.celebra-racha .llama * {
  animation: none;
}
.sello-zona .celebra-racha {
  margin: 0;
}
@keyframes racha-entra {
  from { opacity: 0; transform: scale(0.9); }
}

/* ---------- Entrada de las pantallas del estudiante: los bloques entran escalonados con rebote, nunca
   un corte seco. `.juego` solo lo ponen las vistas del estudiante. La navegación fija queda fuera
   (un transform en su padre la sacaría de `position: fixed`); con reduced-motion el apagador de
   base.css la deja en su estado final. `backwards`: antes de empezar se ve el "desde", al terminar
   queda el estado normal (si la animación no corre, nada queda invisible). ---------- */
body {
  overflow-x: clip;
}
.juego > :not(.nav-inferior) {
  animation: entra-sube 520ms cubic-bezier(0.2, 1.3, 0.4, 1) backwards;
}
.juego > :nth-child(2) { animation-delay: 70ms; }
.juego > :nth-child(3) { animation-delay: 140ms; }
.juego > :nth-child(4) { animation-delay: 210ms; }
.juego > :nth-child(5) { animation-delay: 280ms; }
.juego > :nth-child(6) { animation-delay: 350ms; }
.juego > :nth-child(n+7) { animation-delay: 420ms; }
.juego-desliza > :not(.nav-inferior) {
  animation-name: entra-desliza;
}
.juego-quieta > :not(.nav-inferior) {
  animation: none;
}
@keyframes entra-sube {
  from { opacity: 0; transform: translateY(28px) scale(0.97); }
}
@keyframes entra-desliza {
  from { opacity: 0; transform: translateX(56px); }
}

/* ---------- Botones con resorte: al soltar rebotan; al presionar bajan rápido. ---------- */
.juego button {
  transition: transform 190ms cubic-bezier(0.2, 1.8, 0.4, 1);
}
.juego button:active:not(:disabled) {
  transition-duration: 50ms;
}

/* ---------- Drako en reposo. El PERSONAJE (svg.drako-rig, ui/drako_animado.js) se mueve él solo por partes con anime.js:
   respira, parpadea, mueve la cola, saluda, salta. Estas reglas de CSS son solo para la IMAGEN estática (img.drako: la
   del encabezado de la pregunta), que respira y se ladea apenas (referencia: floatMascot de Lingo). Al saltar,
   el svg se sale de su caja: por eso `overflow: visible`. ---------- */
.drako-rig {
  overflow: visible;
}
.juego img.drako {
  transform-origin: 50% 100%;
  animation: drako-respira 3.4s ease-in-out infinite;
}
@keyframes drako-respira {
  0%, 100% { transform: translateY(0) rotate(-1.5deg) scale(1); }
  50% { transform: translateY(-5px) rotate(1.5deg) scale(1.04); }
}

/* ---------- La pregunta: fichas que se HUNDEN al tocar (mismo lenguaje que el botón principal). La
   elegida queda "presionada" en navy; el pop de elegir es transform. `#vista .juego` gana en
   especificidad a las reglas base de componentes.css sin tocarlas. ---------- */
#vista .juego [data-testid^="opcion-"] {
  transition: transform 170ms cubic-bezier(0.2, 1.6, 0.4, 1);
}
@media (hover: hover) {
  #vista .juego [data-testid^="opcion-"]:hover:not(:disabled):not([aria-pressed="true"]) {
    transform: translateX(6px);
  }
}
#vista .juego [data-testid^="opcion-"]:active:not(:disabled) {
  transform: translateY(3px);
  border-bottom-width: 1px;
  transition-duration: 50ms;
}
#vista .juego [data-testid^="opcion-"][aria-pressed="true"] {
  transform: translateY(2px);
  border-color: var(--primario);
  border-bottom-width: 2px;
  color: var(--primario);
}
#vista .juego [data-testid^="opcion-"][aria-pressed="true"] .escudo-letra {
  background: var(--primario);
  color: var(--superficie);
}
#vista .juego .opcion-recien {
  animation: opcion-pop 460ms cubic-bezier(0.2, 1.8, 0.4, 1) 1;
}
#vista .juego .opcion-recien .escudo-letra {
  animation: letra-gira 520ms cubic-bezier(0.2, 1.8, 0.4, 1) 1;
}
@keyframes opcion-pop {
  0% { transform: translateY(0) scale(1); }
  35% { transform: translateY(2px) scale(1.06); }
  70% { transform: translateY(2px) scale(0.98); }
  100% { transform: translateY(2px) scale(1); }
}
@keyframes letra-gira {
  0% { transform: rotate(0deg) scale(1); }
  50% { transform: rotate(-14deg) scale(1.35); }
  100% { transform: rotate(0deg) scale(1); }
}

/* ---------- Barra de acción de abajo: el botón nace PLANO y "despierta" al elegir; el panel neutro sube
   desde abajo. Fija como la navegación, con el espacio reservado para que nada quede tapado. ---------- */
.barra-accion {
  position: fixed;
  left: 50%;
  bottom: 0;
  translate: -50% 0;
  width: 100%;
  max-width: 480px;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--superficie);
  border-top: 2px solid var(--borde);
  border-radius: var(--radio-md) var(--radio-md) 0 0;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
}
.barra-accion button {
  width: 100%;
  min-height: 52px;
  font-size: 1.1rem;
}
#vista:has(.barra-accion) {
  padding-bottom: calc(190px + env(safe-area-inset-bottom));
}
.juego > .barra-accion {
  animation: none;
}
.panel-resultado {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border-radius: var(--radio-md);
  padding: 10px 14px;
  font-family: var(--fuente-titulos);
}
.panel-resultado p {
  margin: 0;
}
.panel-titulo {
  font-weight: 800;
}
.panel-detalle {
  font-family: var(--fuente-cuerpo);
  font-size: 0.85rem;
}
.panel-icono {
  flex: none;
  display: flex;
}
.panel-neutro {
  background: color-mix(in srgb, var(--primario) 8%, white);
  color: var(--primario);
}
.panel-correcto {
  background: var(--correcto-fondo);
  color: var(--correcto);
}
.panel-incorrecto {
  background: var(--incorrecto-fondo);
  color: var(--incorrecto);
}
.panel-oculto {
  display: none;
}
.panel-sube {
  animation: panel-sube 520ms cubic-bezier(0.2, 1.4, 0.4, 1) backwards;
}
@keyframes panel-sube {
  from { opacity: 0; transform: translateY(70px); }
}
/* El botón de acción despierta: sube con un pop y se pinta de oro (antes: plano y gris). */
.boton-principal.despierta {
  animation: boton-despierta 520ms cubic-bezier(0.2, 1.8, 0.4, 1) 1;
}
@keyframes boton-despierta {
  0% { transform: translateY(14px) scale(0.96); }
  45% { transform: translateY(-8px) scale(1.04); }
  75% { transform: translateY(2px) scale(0.99); }
  100% { transform: translateY(0) scale(1); }
}
.progreso-reto {
  height: 12px;
  background: color-mix(in srgb, var(--primario) 10%, white);
  border-radius: 999px;
  overflow: hidden;
  margin: 8px 0 12px;
}
.progreso-relleno {
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, var(--oro), color-mix(in srgb, var(--oro) 60%, white));
  border-radius: 999px;
  transform-origin: left center;
  transform: scaleX(var(--avance, 0));
  animation: progreso-llena 650ms cubic-bezier(0.2, 1.2, 0.4, 1) backwards;
}
@keyframes progreso-llena {
  from { transform: scaleX(var(--desde, 0)); }
}

/* Mientras el servidor revisa: Drako piensa (nunca un botón mudo). */
.revisando {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 16px;
}
.revisando p {
  margin: 0;
  font-family: var(--fuente-titulos);
  font-weight: 700;
}
.juego .revisando img.drako {
  animation: drako-piensa 1.1s ease-in-out infinite;
}
@keyframes drako-piensa {
  0%, 100% { transform: rotate(-7deg) translateY(0); }
  50% { transform: rotate(7deg) translateY(-5px); }
}

/* ---------- Botones "como los del búho": gruesos, se hunden al tocar (<80 ms) y rebotan al soltar.
   Estados: normal, hover (solo con mouse), presionado, deshabilitado PLANO (se nota que "todavía
   no"), cargando (late, sin spinner) y éxito (destello oro). Solo transform y opacity. La mecánica
   es de ellos; el color y la forma son de ENGRAMA (Navy Real + oro, tokens). ---------- */
@media (hover: hover) {
  .juego button:hover:not(:disabled) {
    transform: translateY(-2px);
  }
}
.juego button:disabled:not(.cargando) {
  background: color-mix(in srgb, var(--texto) 9%, white);
  color: color-mix(in srgb, var(--texto) 42%, white);
  border-bottom: 4px solid color-mix(in srgb, var(--texto) 9%, white);
  opacity: 1;
  transform: none;
}
.barra-accion .boton-principal:not(:disabled) {
  background: var(--oro);
  color: var(--oro-texto);
  border-bottom: 4px solid color-mix(in srgb, var(--oro) 60%, black);
}
.barra-accion .boton-principal:active:not(:disabled) {
  border-bottom-width: 1px;
}
.juego button.cargando {
  opacity: 1;
  animation: boton-late 900ms ease-in-out infinite;
}
@keyframes boton-late {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.035); opacity: 0.82; }
}
.juego button.exito {
  position: relative;
}
.juego button.exito::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: var(--radio-md);
  box-shadow: 0 0 0 4px var(--oro), 0 0 22px color-mix(in srgb, var(--oro) 80%, transparent);
  pointer-events: none;
  animation: destello-oro 700ms ease-out 1 both;
}
@keyframes destello-oro {
  0% { opacity: 0; transform: scale(0.94); }
  30% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.08); }
}

/* ---------- Navegación inferior: el ícono de la pestaña activa rebota al llegar a la pantalla. W70 (docs/ESPEC_navegacion.md §5.6): SOLO dentro
   de `.juego` (las pantallas del estudiante). El profe y el admin llevan la misma barra, sobria: ahí el ícono no anima nunca. ---------- */
.nav-icono {
  display: grid;
}
.juego .nav-inferior a[aria-current="page"] .nav-icono {
  animation: nav-rebota 650ms cubic-bezier(0.2, 1.9, 0.4, 1) 1;
}
@keyframes nav-rebota {
  0% { transform: translateY(0) scale(1); }
  35% { transform: translateY(-9px) scale(1.25); }
  65% { transform: translateY(2px) scale(0.95); }
  100% { transform: translateY(0) scale(1); }
}

/* ---------- Fin de reto (ui/celebracion.js): la cabecera con Drako de PRESENTADOR, el puntaje que cuenta y
   la medalla de monedas. Proporcional: el perfecto lleva halo de oro y saltos; el de ánimo, un balanceo
   suave. Las filas de la revisión entran una tras otra; la correcta rebota y la que no tiembla apenas
   (siempre con ícono y texto: el movimiento nunca es la única señal). ---------- */
.solo-lectores {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.hero-resultado {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: 4px 0 8px;
}
.hero-drako {
  position: relative;
  display: grid;
  place-items: center;
  width: 124px;
  height: 124px;
}
.hero-drako .drako {
  position: relative;
  width: 108px;
  height: 108px;
}
.juego .hero-perfecto img.drako,
.juego .hero-bien img.drako {
  animation: drako-salta 1.1s cubic-bezier(0.2, 1.6, 0.4, 1) 3;
}
.juego .hero-animo img.drako {
  animation: drako-abraza 2.6s ease-in-out infinite;
}
.hero-perfecto .hero-drako::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--oro) 60%, transparent), transparent 70%);
  animation: halo-respira 1.6s ease-in-out infinite;
}
@keyframes drako-salta {
  0% { transform: translateY(0) scale(1); }
  30% { transform: translateY(-26px) scale(1.1, 0.95) rotate(-5deg); }
  55% { transform: translateY(0) scale(0.94, 1.06); }
  75% { transform: translateY(-9px) rotate(4deg); }
  100% { transform: translateY(0) scale(1); }
}
@keyframes drako-abraza {
  0%, 100% { transform: translateX(0) rotate(-2deg); }
  50% { transform: translateX(4px) rotate(2deg); }
}
@keyframes halo-respira {
  0%, 100% { transform: scale(0.9); opacity: 0.55; }
  50% { transform: scale(1.12); opacity: 1; }
}
.hero-titulo {
  margin: 0;
  font-size: var(--sx-t-titulo);
  line-height: 1.15;
  color: var(--primario);
  animation: titulo-pop 750ms cubic-bezier(0.2, 1.9, 0.4, 1) 250ms backwards;
}
@keyframes titulo-pop {
  from { opacity: 0; transform: scale(0.4) rotate(-6deg); }
}
.hero-mensaje {
  margin: 0;
  max-width: 34ch;
}
.hero-puntaje {
  margin: 4px 0 0;
  font-family: var(--fuente-titulos);
  font-weight: 800;
  font-size: 2.4rem;
  color: var(--primario);
}
.hero-puntaje-num {
  display: inline-block;
  transform-origin: center;
}
/* La medalla es la tarjeta-dato del sistema (`sx-dato`): acá solo su ancho y su entrada. */
.medalla-monedas {
  min-width: 11rem;
  margin-top: 8px;
  animation: medalla-pop 700ms cubic-bezier(0.2, 1.9, 0.4, 1) 450ms backwards;
}
.medalla-num {
  display: inline-block;
}
@keyframes medalla-pop {
  from { opacity: 0; transform: translateY(30px) scale(0.5); }
}
.juego > .sx-btn {
  margin-top: 16px;
}
.subtitulo-revision {
  margin: 14px 0 8px;
  font-size: 1.05rem;
}
.juego > ul {
  animation: none;
}
/* Las filas ENTRAN en la línea de tiempo del fin de reto (ui/linea_fin_reto.js, anime.js: opacity y translateX, una tras otra, después
   del puntaje y las monedas). Cuando una fila entra se marca `fila-lista` y recién ahí la correcta rebota y la que no tiembla. */
.juego .fila-lista.revision-ok .resultado-ok {
  animation: ok-rebota 650ms cubic-bezier(0.2, 1.9, 0.4, 1) 120ms backwards;
}
.juego .fila-lista.revision-mal .resultado-mal {
  animation: mal-tiembla 380ms ease-in-out 120ms backwards;
}
@keyframes ok-rebota {
  from { opacity: 0; transform: scale(0.7); }
  55% { transform: scale(1.1); }
}
@keyframes mal-tiembla {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}

/* ---------- Asistencia marcada (ui/sello.js): el sello se estampa con rebote y una onda; las monedas del
   chip cuentan y la llama de la constancia late. Sin movimiento queda un sello quieto, igual de claro. ---------- */
.sello-zona .drako {
  width: 72px;
  height: 72px;
}
/* W45b: la celebración es más compacta para que el resultado y su desglose queden a la vista, sobre la barra, en un celular de 375 × 812. */
.sello-zona {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin: 4px 0 8px;
}
.sello {
  position: relative;
  display: grid;
  place-items: center;
  align-content: center;
  width: 136px;
  height: 136px;
  border: 6px double var(--primario);
  border-radius: 50%;
  color: var(--primario);
  font-family: var(--fuente-titulos);
  font-weight: 800;
  background: color-mix(in srgb, var(--oro) 14%, white);
  rotate: -8deg;
  animation: sello-estampa 720ms cubic-bezier(0.2, 1.5, 0.35, 1) backwards;
}
.sello::after {
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 3px solid var(--oro);
  animation: sello-onda 900ms ease-out 420ms 1 both;
}
.sello-marca {
  font-size: 3rem;
  line-height: 1;
  display: grid;
  place-items: center;
}
/* B1: la marca es un SVG de ui/iconos.js (antes el glifo de texto), del tamaño que tenía la letra. */
.sello-marca .sx-icono {
  width: 1em;
  height: 1em;
}
.sello-texto {
  font-size: 1.15rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
@keyframes sello-estampa {
  0% { opacity: 0; transform: scale(2.6); }
  55% { opacity: 1; transform: scale(0.88); }
  75% { transform: scale(1.06); }
  100% { transform: scale(1); }
}
@keyframes sello-onda {
  0% { opacity: 0.9; transform: scale(0.9); }
  100% { opacity: 0; transform: scale(1.5); }
}
.chip-monedas {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  padding: 6px 20px;
  background: var(--primario);
  color: var(--oro);
  font-family: var(--fuente-titulos);
  font-weight: 800;
  border-radius: var(--radio-lg);
  border-bottom: 4px solid var(--primario-oscuro);
  animation: medalla-pop 600ms cubic-bezier(0.2, 1.9, 0.4, 1) 300ms backwards;
}
.chip-num {
  display: inline-block;
  font-size: 1.8rem;
  min-width: 2ch;
  text-align: right;
}
.constancia-asistencia {
  margin: 0;
  font-family: var(--fuente-titulos);
  font-weight: 800;
  color: var(--primario);
  animation: medalla-pop 600ms cubic-bezier(0.2, 1.9, 0.4, 1) 650ms backwards;
}

/* ---------- Cargas y vacíos con personalidad (ui/estados.js): Drako en espera y esqueletos con un brillo que
   pasa (solo transform). Siempre llevan su texto: el esqueleto es adorno. ---------- */
.estado-carga {
  padding: 8px 0;
}
.estado-cabecera {
  display: flex;
  align-items: center;
  gap: 12px;
}
.estado-cabecera .drako,
.estado-vacio .drako {
  width: 68px;
  height: 68px;
}
.estado-texto {
  margin: 0;
  font-family: var(--fuente-titulos);
  font-weight: 700;
}
.esqueletos {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 16px;
}
.esqueleto {
  position: relative;
  overflow: hidden;
  height: 56px;
  border-radius: var(--radio-md);
  background: color-mix(in srgb, var(--primario) 8%, white);
}
.esqueleto-1 { width: 88%; }
.esqueleto-2 { width: 68%; height: 40px; }
.esqueleto::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--superficie) 80%, transparent), transparent);
  transform: translateX(-100%);
  animation: esqueleto-brilla 1.3s ease-in-out infinite;
}
@keyframes esqueleto-brilla {
  to { transform: translateX(100%); }
}
.estado-vacio {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 18px 12px;
  text-align: center;
  background: var(--superficie);
  border: 2px dashed var(--borde);
  border-radius: var(--radio-md);
}
.estado-vacio .drako {
  width: 88px;
  height: 88px;
}
.estado-titulo {
  margin: 0;
  font-family: var(--fuente-titulos);
  font-weight: 800;
  color: var(--primario);
}

/* ---------- Inicio con vida: Drako saluda y luego respira; la tarjeta del reto de hoy late como invitación;
   el escudo brilla al aparecer (y deja lista la animación de "subiste", que hoy nada dispara). ---------- */
.juego [data-testid="vista-inicio"] img.drako,
[data-testid="vista-inicio"].juego img.drako {
  animation: drako-saluda 1.5s cubic-bezier(0.2, 1.6, 0.4, 1) 400ms 1, drako-respira 3.4s ease-in-out 1.9s infinite;
}
@keyframes drako-saluda {
  0% { transform: rotate(0deg) translateY(0); }
  20% { transform: rotate(-14deg) translateY(-10px) scale(1.1); }
  40% { transform: rotate(12deg) translateY(-4px) scale(1.08); }
  60% { transform: rotate(-10deg) translateY(-8px); }
  80% { transform: rotate(6deg) translateY(0); }
  100% { transform: rotate(0deg) translateY(0); }
}
.fila-invitacion {
  position: relative;
}
.fila-invitacion::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: calc(var(--radio-md) + 4px);
  border: 3px solid var(--oro);
  pointer-events: none;
  animation: invitacion-late 1.8s ease-in-out infinite;
}
@keyframes invitacion-late {
  0%, 100% { opacity: 0; transform: scale(0.985); }
  50% { opacity: 1; transform: scale(1.02); }
}
.fila-invitacion [data-testid="jugar-reto-hoy"] {
  animation: boton-menea 2.4s ease-in-out 1.2s infinite;
}
@keyframes boton-menea {
  0%, 82%, 100% { transform: translateX(0) rotate(0deg); }
  86% { transform: translateX(-4px) rotate(-3deg); }
  90% { transform: translateX(4px) rotate(3deg); }
  94% { transform: translateX(-3px) rotate(-2deg); }
  98% { transform: translateX(2px) rotate(1deg); }
}
.escudo-brillo {
  position: relative;
  overflow: hidden;
}
.escudo-brillo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 30%, color-mix(in srgb, var(--oro) 55%, transparent) 50%, transparent 70%);
  transform: translateX(-130%);
  animation: escudo-brilla 1.4s ease-out 700ms 1 forwards;
}
@keyframes escudo-brilla {
  to { transform: translateX(130%); }
}
.escudo-sube {
  animation: escudo-sube 900ms cubic-bezier(0.2, 1.9, 0.4, 1) 1;
}
@keyframes escudo-sube {
  0% { transform: scale(1) rotate(0deg); }
  30% { transform: scale(1.5) rotate(-6deg); }
  60% { transform: scale(0.9) rotate(3deg); }
  100% { transform: scale(1) rotate(0deg); }
}

/* ---------- Listas del estudiante (retos): las filas entran escalonadas. ---------- */
.juego li.fila {
  animation: fila-sube 440ms cubic-bezier(0.2, 1.4, 0.4, 1) backwards;
}
.juego li.fila:nth-child(1) { animation-delay: 120ms; }
.juego li.fila:nth-child(2) { animation-delay: 190ms; }
.juego li.fila:nth-child(3) { animation-delay: 260ms; }
.juego li.fila:nth-child(4) { animation-delay: 330ms; }
.juego li.fila:nth-child(5) { animation-delay: 400ms; }
.juego li.fila:nth-child(n+6) { animation-delay: 470ms; }
@keyframes fila-sube {
  from { opacity: 0; transform: translateY(26px) scale(0.97); }
}
.juego li.fila .insignia {
  animation: insignia-pop 520ms cubic-bezier(0.2, 1.9, 0.4, 1) 600ms backwards;
}
@keyframes insignia-pop {
  from { opacity: 0; transform: scale(0.3); }
}

.boton-sonido {
  flex: none;
}

/* Adenda 17.8: un aviso que informa y no califica (la bolsa de monedas de la institución se agotó). Ícono ℹ y texto, sin rojo ni verde. */
.juego .resultado-info {
  background: var(--superficie);
  border: 2px solid var(--borde);
  color: var(--texto);
}

/* W45b (A7, docs/ESPEC_juego_oleada1.md §17.1.4): la línea del desglose de la asistencia ("5 por asistir + 5 por llegar a tiempo") entra con opacidad
   y escala, una sola vez. Su renglón está reservado desde que hay monedas: al llegar el dato nada salta. Sin movimiento, aparece. */
.juego .renglon-desglose {
  min-height: 1.6em;
  margin-top: 8px;
  text-align: center;
}
.juego .asistencia-desglose {
  margin: 0;
  font-weight: 700;
  color: var(--primario);
  animation: desglose-entra 260ms ease-out backwards;
}
@keyframes desglose-entra {
  from { opacity: 0; transform: scale(0.8); }
}

/* ---------- W47 · "Mis monedas" (`#/monedas`, vistas/estudiante/monedas.js) y su enlace desde Inicio. Aquí no se gana nada: el saldo no cuenta ni
   vuelan monedas. Las filas entran escalonadas (A6: solo las 12 primeras; `--i` es su turno). Sin movimiento, están puestas. ---------- */
/* El enlace de Inicio a "Mis monedas": un renglón de texto pegado bajo la barra del saldo, a la derecha, con 44 px para el dedo. No es un chip
   (la especificidad le gana a la regla de los enlaces con data-testid de componentes.css), para no competir con "Jugar". */
#vista a.enlace-monedas[data-testid]:not([data-testid="sesion-enlace"]) {
  display: flex;
  width: fit-content;
  min-height: 44px;
  margin: -16px 0 -8px auto; /* pegado a la barra y sin empujar el saludo: los tres botones de "Ahora" caben tambien en 360 x 640 mientras la pantalla entra */
  padding: 0 0 0 12px;
  background: none;
  border: none;
  font-size: 0.95rem;
}
#vista a.enlace-monedas[data-testid]:not([data-testid="sesion-enlace"])::after {
  content: "\203A";
}
#vista a.enlace-monedas[data-testid]:not([data-testid="sesion-enlace"]):active {
  transform: none;
}
.saldo-tarjeta {
  display: flex;
  align-items: center;
  gap: 16px;
  margin: 12px 0 20px;
  padding: 18px 20px;
  background: var(--primario);
  color: var(--superficie);
  border-radius: var(--radio-lg);
  border-bottom: 4px solid var(--primario-oscuro);
}
.saldo-moneda {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--oro);
  border: 5px solid color-mix(in srgb, var(--oro) 70%, black);
  outline: 3px solid color-mix(in srgb, var(--oro) 35%, transparent);
}
.saldo-texto {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.saldo-etiqueta {
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--superficie) 80%, transparent);
}
.saldo-total {
  margin: 0;
  font-family: var(--fuente-titulos);
  font-weight: 800;
  font-size: 1.35rem;
  line-height: 1.1;
  color: var(--oro);
}
.movimientos-titulo {
  margin: 0 0 8px;
}
.movimientos {
  margin: 0;
  padding: 0;
  list-style: none;
}
.fila.movimiento {
  flex-wrap: nowrap;
  align-items: center;
  gap: 12px;
}
.movimiento-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--oro) 16%, white);
  color: var(--oro-texto);
}
.movimiento .fila-texto {
  flex: 1;
  min-width: 0;
}
.movimiento-detalle,
.movimiento-fecha {
  display: block;
  font-size: 0.9rem;
  color: color-mix(in srgb, var(--texto) 70%, transparent);
}
.movimiento-monto {
  flex: none;
  font-family: var(--fuente-titulos);
  font-weight: 800;
  font-size: 1.3rem;
  color: var(--texto);
}
.movimiento-entra-monto {
  color: var(--primario);
}
.movimientos-tope {
  text-align: center;
  color: color-mix(in srgb, var(--texto) 70%, transparent);
}
.monedas-caminos {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}
.juego .movimiento-entra {
  animation: movimiento-entra 320ms ease-out backwards;
  animation-delay: calc(var(--i, 0) * 40ms);
}
@keyframes movimiento-entra {
  from { opacity: 0; transform: translateY(10px); }
}


/* ---------- W48 · La llama con vida (ui/racha.js, docs/ESPEC_juego_oleada1.md §4.4 y §5 A1 a A3). La etapa (0 a 3) sale del número del servidor;
   aquí solo se dibuja. La caja de la llama mide siempre lo mismo (la escala es `scale`, que no mueve lo de al lado) y reserva a los lados el
   sitio de la etapa mayor. Sin movimiento: la llama quieta a su escala, las chispas ocultas y el halo fijo. ---------- */
.llama {
  margin-left: 0.3em;
  margin-right: 0.5em;
}
.llama-etapa-1 { scale: 1.15; }
.llama-etapa-2 { scale: 1.3; }
.llama-etapa-3 { scale: 1.5; }
.llama-lengua {
  position: absolute;
  left: -22%;
  bottom: 0;
  width: 58%;
  height: 58%;
  clip-path: polygon(50% 0%, 66% 24%, 90% 50%, 94% 76%, 74% 98%, 26% 98%, 6% 76%, 10% 50%, 34% 26%, 40% 12%);
  background: linear-gradient(to top, var(--oro), color-mix(in srgb, var(--oro) 45%, white));
  transform-origin: 50% 100%;
  animation: llama-lengua 1.3s ease-in-out infinite;
}
@keyframes llama-lengua {
  0%, 100% { transform: scale(0.72) rotate(-16deg); opacity: 0.6; }
  50% { transform: scale(1) rotate(-6deg); opacity: 1; }
}
.llama-chispa {
  position: absolute;
  bottom: 78%;
  width: 0.17em;
  height: 0.17em;
  border-radius: 50%;
  background: var(--oro);
  opacity: 0;
  animation: llama-chispa 1.6s ease-out infinite;
}
.llama-chispa-0 { left: 4%; }
.llama-chispa-1 { left: 80%; animation-delay: 0.8s; }
.llama-chispa-2 { left: 34%; animation-delay: 0.4s; }
.llama-chispa-3 { left: 60%; animation-delay: 1.2s; }
@keyframes llama-chispa {
  0% { transform: translateY(0) scale(1); opacity: 0; }
  15% { opacity: 1; }
  100% { transform: translateY(-14px) scale(0.4); opacity: 0; }
}
.llama-halo {
  position: absolute;
  inset: -14% -42% -12%;
  border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--oro) 75%, transparent), transparent 70%);
  opacity: 0.25;
  animation: llama-halo 2s ease-in-out infinite;
}
@keyframes llama-halo {
  0%, 100% { transform: scale(1); opacity: 0.25; }
  50% { transform: scale(1.12); opacity: 0.6; }
}
/* A2: al cruzar 3, 7 o 14 la llama crece (900 ms, con rebote) y el aviso lo dice. */
.constancia-crece .llama {
  animation: llama-crece 900ms cubic-bezier(0.2, 1.6, 0.4, 1) 1;
}
@keyframes llama-crece {
  0% { transform: scale(0.78); }
  55% { transform: scale(1.25); }
  100% { transform: scale(1); }
}
/* A3: la racha bajó. Una línea neutra, en el sitio: entra con opacidad y nada más (ni rojo, ni temblor, ni sonido). */
.racha-de-nuevo {
  width: fit-content;
  max-width: 100%;
  margin: 0 0 12px;
  padding: 8px 14px;
  background: var(--superficie);
  border: 2px solid var(--borde);
  border-radius: var(--radio-sm);
  font-weight: 700;
  color: var(--texto);
  animation: aparece-suave 200ms ease-out backwards;
}
.sello-zona .racha-de-nuevo {
  margin: 0 auto;
}
@keyframes aparece-suave {
  from { opacity: 0; }
}
/* Las etapas altas crecen desde el centro de la llama (no solo hacia arriba): caben en la barra del saldo sin salirse por el borde de la pantalla. */
.llama-etapa-2,
.llama-etapa-3 {
  transform-origin: 50% 62%;
}

/* ---------- W51 · "Tu semana" (ui/semana.js, docs/ESPEC_juego_oleada1.md §4.6 y §5 A5): 7 puntos de lunes a domingo. Lleno = hay asistencia ese día
   (oro, con su marca); sin asistencia es NEUTRO (un punto gris, nunca rojo ni "ausente"). En cada pintado los llenos entran uno tras otro (220 ms,
   cada 60 ms: la entrada normal del juego, no un premio). Si la semana se completó: el anillo cerrado y el sello. Sin movimiento, todo quieto. ---------- */
.semana-dias {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: 4px;
  margin: 16px 0 12px;
  padding: 0;
  list-style: none;
}
.semana-dia {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 1;
}
.semana-punto {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 2px solid var(--borde);
  background: var(--fondo);
  font-weight: 800;
  line-height: 1;
  color: var(--oro-texto);
}
/* B1: el visto es un SVG de ui/iconos.js (antes un glifo de texto). */
.semana-punto .sx-icono {
  width: 18px;
  height: 18px;
}
.semana-dia-lleno .semana-punto {
  border-color: color-mix(in srgb, var(--oro) 75%, black);
  background: var(--oro);
}
.semana-letra {
  font-family: var(--fuente-titulos);
  font-weight: 700;
  font-size: 0.8rem;
  color: color-mix(in srgb, var(--texto) 70%, transparent);
}
.semana-dia-lleno .semana-letra {
  color: var(--texto);
}
.semana-resumen {
  margin: 0;
  font-weight: 700;
}
.juego .semana-dia-lleno .semana-punto {
  animation: semana-punto-entra 220ms cubic-bezier(0.2, 1.6, 0.4, 1) backwards;
  animation-delay: calc(var(--i, 0) * 60ms);
}
@keyframes semana-punto-entra {
  from { opacity: 0; transform: scale(0.4); }
}
/* El anillo de la semana: se cierra (aparece) alrededor de los 7 puntos cuando se completó. */
.semana-completa .semana-dias::after {
  content: "";
  position: absolute;
  inset: -8px -8px -6px;
  border: 3px solid var(--oro);
  border-radius: var(--radio-md);
  pointer-events: none;
}
.juego .semana-completa .semana-dias::after {
  animation: semana-anillo 400ms ease-out backwards;
}
@keyframes semana-anillo {
  from { opacity: 0; transform: scale(0.9); }
}
.semana-sello {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 14px;
  padding: 10px 14px;
  background: var(--primario);
  color: var(--superficie);
  border-radius: var(--radio-md);
  border-bottom: 4px solid var(--primario-oscuro);
}
.semana-sello-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--oro);
  color: var(--oro-texto);
  font-size: 1.3rem;
}
/* B1: la estrella es un SVG de ui/iconos.js (antes un glifo de texto), llena como el sello. */
.semana-sello-icono .sx-icono,
.sello-merito-icono .sx-icono {
  width: 22px;
  height: 22px;
}
.semana-sello-icono .sx-icono[data-icono="estrella"],
.sello-merito-icono .sx-icono[data-icono="estrella"] {
  fill: currentColor;
}
.semana-sello-titulo {
  margin: 0;
  font-family: var(--fuente-titulos);
  font-weight: 800;
  font-size: 1.15rem;
  color: var(--oro);
}
.semana-sello-detalle {
  margin: 0;
  font-size: 0.92rem;
}
.juego .semana-sello-entra {
  animation: merito-estampa 520ms cubic-bezier(0.2, 1.5, 0.35, 1) 300ms backwards;
}
@keyframes merito-estampa {
  0% { opacity: 0; transform: scale(1.8) rotate(-8deg); }
  60% { opacity: 1; transform: scale(0.92) rotate(0deg); }
  100% { transform: scale(1) rotate(0deg); }
}


/* W52: "Tu reto de hoy" con su etiqueta "Hasta N monedas" (un tercer renglón de texto de apoyo). La tarjeta se aprieta un poco para que, aun con un
   nombre de 60 caracteres en el saludo, los tres botones de "Ahora" sigan sobre la barra sin desplazar a 375 × 812 (E31). Sin oro ni animación. */
.fila.fila-ahora.con-premio {
  padding-top: 9px;
  padding-bottom: 9px;
}
.fila-ahora.con-premio .fila-texto {
  gap: 1px;
}

/* ---------- W50 · El sello de mérito del fin de reto (ui/merito.js, docs/ESPEC_juego_oleada1.md §4.6 y §5 A4): "Primer reto perfecto" o "Reto
   superado", en oro, con su ícono y su texto. La línea de tiempo lo estampa entre las monedas y las filas: cae (escala 1,8 → 0,92 → 1, giro de −8°
   a 0 y opacidad, 520 ms) y su título da un golpe (180 ms). El primer perfecto suma 8 destellos que salen del sello (480 ms). Sin movimiento, el
   sello ya está puesto. Drako nunca va dentro. ---------- */
.sello-merito {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 14px auto 0;
  padding: 8px 18px 8px 8px;
  background: var(--oro);
  color: var(--oro-texto);
  border: 3px double color-mix(in srgb, var(--oro) 60%, black);
  border-radius: var(--radio-lg);
  font-family: var(--fuente-titulos);
  font-weight: 800;
  font-size: 1.1rem;
  rotate: -2deg;
}
.sello-merito-icono {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--primario);
  color: var(--oro);
  font-size: 1.3rem;
  line-height: 1;
}
.merito-espera {
  opacity: 0;
}
.juego .merito-estampado {
  animation: merito-estampa 520ms cubic-bezier(0.2, 1.5, 0.35, 1) backwards;
}
.juego .merito-estampado .sello-merito-texto {
  animation: merito-golpe 180ms ease-out 420ms backwards;
}
@keyframes merito-golpe {
  0% { transform: scale(1.18); }
  100% { transform: scale(1); }
}
.merito-destello {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 14px;
  margin: -7px 0 0 -3px;
  border-radius: 3px;
  background: var(--oro);
  opacity: 0;
  pointer-events: none;
  animation: merito-destello 480ms ease-out 380ms backwards;
}
@keyframes merito-destello {
  0% { opacity: 1; transform: rotate(var(--giro, 0deg)) translateY(-30px) scale(0.6); }
  100% { opacity: 0; transform: rotate(var(--giro, 0deg)) translateY(-78px) scale(1); }
}

