/* QuestKeep · fotogramas clave y transiciones de vista */

@keyframes drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(4vw, 3vh, 0) scale(1.14); }
}

@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(0.72); }
}

/* La medalla cae como un sello: grande y girada, y se asienta de golpe. */
@keyframes stamp {
  0%   { opacity: 0; transform: scale(2.4) rotate(-24deg); }
  55%  { opacity: 1; transform: scale(0.88) rotate(6deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

/* Entrada de los paneles flotantes. NO anima la opacidad a proposito: con la
   pestaña en segundo plano las animaciones se congelan en el primer fotograma,
   y un panel que naciera transparente no llegaria a verse nunca. Congelada,
   esta solo deja el panel unos pixeles desplazado. */
@keyframes panel-in {
  from { transform: translateY(-8px) scale(0.97); }
}

/* Brillo de gema para las notas de 90 en adelante. Solo mueve el degradado; el
   numero nunca depende de la animacion para verse. */
@keyframes brillo-joya {
  from { background-position: 0% 50%; }
  to   { background-position: 320% 50%; }
}
@keyframes faceta-joya {
  0%, 62%  { background-position: 130% 0; }
  86%, 100% { background-position: -30% 0; }
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes shimmer {
  from { background-position: 220% 0; }
  to   { background-position: -20% 0; }
}

/* Barrido platino de los juegos completados. Pasa, descansa y vuelve. */
@keyframes sweep {
  0%, 62%  { transform: translateX(-100%); }
  86%, 100% { transform: translateX(100%); }
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(22px) scale(0.965); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(18px) scale(0.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes toast-out {
  to { opacity: 0; transform: translateY(10px) scale(0.96); }
}

@keyframes slot-in {
  from { transform: scaleX(0.3); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}

/* Entrada escalonada de las tarjetas al asomar por el viewport. El retardo lo
   fija el JS en --i, para que la cascada siga el orden de lectura.

   El estado por defecto es VISIBLE. Solo se oculta cuando el JS ha confirmado
   que va a animar la entrada, marcando `.reveal-pending`. Asi, si el
   observador no llega a dispararse -- pestaña en segundo plano, JS a medias,
   navegador sin IntersectionObserver -- el contenido se ve igualmente en vez
   de quedarse invisible para siempre. */
.reveal { opacity: 1; }

.reveal-pending {
  opacity: 0;
  transform: translateY(20px);
}
.reveal-pending.shown {
  animation: reveal-in var(--dur-slow) var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 34ms);
}
@keyframes reveal-in {
  to { opacity: 1; transform: translateY(0); }
}

/* Aterrizaje: la ficha rebota al llegar a su columna. */
@keyframes land {
  0%   { transform: scale(1.16); }
  55%  { transform: scale(0.95); }
  100% { transform: scale(1); }
}
.landing { animation: land 460ms var(--ease-spring); }

/* Avanzar: el borde de la columna pega un golpe de luz y se apaga. Dos
   escalones al principio para que parezca un flash real, que nunca decae de
   forma lineal. */
@keyframes col-flash {
  0%   { box-shadow: 0 0 0 2px var(--col-accent), 0 0 60px -2px var(--col-accent); }
  14%  { box-shadow: 0 0 0 1px var(--col-accent), 0 0 26px -6px var(--col-accent); }
  30%  { box-shadow: 0 0 0 2px var(--col-accent), 0 0 46px -4px var(--col-accent); }
  100% { box-shadow: 0 0 0 1px transparent, 0 0 0 -4px transparent; }
}
.column.flash { animation: col-flash 700ms var(--ease-out); }

/* Retroceder: la columna se mece como si la hubiera cruzado una ola. El
   desplazamiento es minimo -unos pocos pixeles- porque lo que cuenta el
   movimiento es el vaiven, no la distancia. */
@keyframes col-ripple {
  0%   { transform: translateX(0) scaleY(1);
         box-shadow: 0 0 0 1px transparent; }
  22%  { transform: translateX(-7px) scaleY(1.012);
         box-shadow: 0 0 0 1px var(--col-accent), 0 0 34px -8px var(--col-accent); }
  48%  { transform: translateX(4px) scaleY(0.994); }
  72%  { transform: translateX(-2px) scaleY(1.004); }
  100% { transform: translateX(0) scaleY(1);
         box-shadow: 0 0 0 1px transparent; }
}
.column.rippling { animation: col-ripple 820ms var(--ease-in-out); }

/* Latido del contador cuando cambia. */
@keyframes count-bump {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.32); color: var(--col-accent); }
  100% { transform: scale(1); }
}
.column-head .n.bump { animation: count-bump 420ms var(--ease-spring); }

/* Estela del vuelo de una tarjeta al backlog. */
.flight-trail {
  position: fixed;
  z-index: 899;
  border-radius: 50%;
  pointer-events: none;
  animation: trail-out 620ms var(--ease-out) forwards;
}
@keyframes trail-out {
  from { opacity: 0.65; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.2); }
}

/* Cambio de vista: la saliente se va hacia arriba, la entrante llega de abajo.
   Se usan las View Transitions del navegador cuando estan disponibles. */
::view-transition-old(root) {
  animation: vt-out 200ms var(--ease-in-out) forwards;
}
::view-transition-new(root) {
  animation: vt-in var(--dur-slow) var(--ease-out);
}
@keyframes vt-out {
  to { opacity: 0; transform: translateY(-10px) scale(0.995); }
}
@keyframes vt-in {
  from { opacity: 0; transform: translateY(14px) scale(0.995); }
}

/* La recolocacion de las fichas al cambiar de columna la hace FLIP, en
   js/lib/flip.js: funciona en todos los navegadores y tambien cuando la
   pestaña esta en segundo plano, donde las View Transitions no llegan a
   ejecutarse. Aqui solo se anima el cambio de vista. */

@media (prefers-reduced-motion: reduce) {
  .aurora span,
  .chip[data-status="completed"] .chip-art::after,
  .hero-eyebrow::before { animation: none; }

  .reveal-pending { opacity: 1; transform: none; }
  .reveal-pending.shown { animation: fade-in var(--dur) ease-out forwards; animation-delay: 0ms; }

  .landing, .column.flash, .column.rippling,
  .chip-medal.stamping, .column-head .n.bump { animation: none; }

  /* La gema se queda quieta pero conserva su degradado: el numero se lee igual. */
  .publica-v strong[data-nota="joya"] { animation: none; }
  .publica-v strong[data-nota="joya"]::after { display: none; }
  .flight-trail { display: none; }

  ::view-transition-old(root),
  ::view-transition-new(root) { animation: fade-in var(--dur) ease-out; }
}

/* La mochila aparece fundiendo. No se anima el `transform` porque el JS lo usa
   para el escalado por tamaño, y la animacion lo pisaria. */
@keyframes carga-in {
  from { opacity: 0; }
}

/* El nivel se llena en vez de aparecer relleno. */
@keyframes barra-nivel {
  from { transform: scaleX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .mochila-cuerpo, .perfil-barra span { animation: none; }
}
