/*
 * The game's motion vocabulary, on the web.
 *
 * theme_tokens.gd names four motion classes and the whole app moves in them.
 * The site uses the same four, at the same timings, so it feels like the game
 * rather than like a generic scroll-animation template:
 *
 *   SETTLE  0.22s, 40ms stagger, 12dp rise  - entrances
 *   REACT   0.09s down / 0.18s up, x0.97    - press and hover feedback
 *   BLOOM   0.6s                            - celebration flashes
 *   BREATHE 4-7s loop                       - ambient idle
 *
 * Everything animates transform and opacity only. Nothing animates width,
 * height, top or left, so no animation here can cause layout shift.
 */

/* --- Settle: the scroll entrance ---------------------------------------- */

.settle {
  opacity: 0;
  transform: translateY(var(--settle-rise));
}

.settle.is-in {
  animation: settle var(--settle-duration) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--settle-stagger));
}

@keyframes settle {
  from {
    opacity: 0;
    transform: translateY(var(--settle-rise));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Settle, but arriving from the side - used by the worlds rail and any
   two-column section where the direction carries meaning. */
.settle--left {
  transform: translate3d(calc(var(--settle-rise) * -2), 0, 0);
}
.settle--right {
  transform: translate3d(calc(var(--settle-rise) * 2), 0, 0);
}
.settle--left.is-in,
.settle--right.is-in {
  animation-name: settle-x;
}

@keyframes settle-x {
  to {
    opacity: 1;
    transform: none;
  }
}

/* A scale-in for things that should feel like they popped into place, with
   the gummy overshoot the app's buttons use. */
.settle--pop {
  transform: scale(0.9);
}
.settle--pop.is-in {
  animation: settle-pop 320ms var(--ease-gummy) both;
  animation-delay: calc(var(--i, 0) * var(--settle-stagger));
}

@keyframes settle-pop {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --- React: press feedback ----------------------------------------------- */

.react {
  transition: transform var(--react-up) var(--ease-out);
}

.react:active {
  transform: scale(var(--react-scale));
  transition-duration: var(--react-down);
}

/* --- Bloom: the celebration flash ---------------------------------------- */

.bloom {
  position: relative;
}

.bloom.is-blooming::after {
  content: '';
  position: absolute;
  inset: -10%;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0) 68%);
  animation: bloom var(--bloom-duration) var(--ease-out) forwards;
}

@keyframes bloom {
  from {
    opacity: 0.9;
    transform: scale(0.45);
  }
  to {
    opacity: 0;
    transform: scale(1.35);
  }
}

/* --- Breathe: ambient idle ----------------------------------------------- */

.breathe {
  animation: breathe var(--breathe-slow) ease-in-out infinite;
}

.breathe--fast {
  animation-duration: var(--breathe-fast);
}

@keyframes breathe {
  0%,
  100% {
    transform: translateY(0) rotate(0deg);
  }
  50% {
    transform: translateY(-10px) rotate(1.2deg);
  }
}

/* A loose piece drifting on the table. Each instance gets its own duration
   and delay inline so a field of them never beats in unison. */
.drift {
  animation: drift var(--drift-dur, 9s) ease-in-out infinite;
  animation-delay: var(--drift-delay, 0s);
  will-change: transform;
}

@keyframes drift {
  0%,
  100% {
    transform: translate3d(0, 0, 0) rotate(var(--drift-rot, 0deg));
  }
  50% {
    transform: translate3d(var(--drift-x, 8px), var(--drift-y, -14px), 0)
      rotate(calc(var(--drift-rot, 0deg) + 6deg));
  }
}

/* --- Tilt: pointer-tracking on Pal cards --------------------------------- */

.tilt {
  transition: transform var(--react-up) var(--ease-out);
  transform: perspective(700px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg))
    translateZ(0);
}

/* --- Shimmer: the hold-to-peek ring -------------------------------------- */

.hold-ring {
  animation: hold-ring 1.6s ease-in-out infinite; /* PEEK_RING_DURATION */
}

@keyframes hold-ring {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.9;
  }
  50% {
    transform: scale(1.18);
    opacity: 0.35;
  }
}

/* --- Reduced motion ------------------------------------------------------
   The app honours its own reduce_motion setting (ThemeTokens.reduce_motion);
   the site honours the OS one. Note that .settle starts at opacity 0, so it
   is not enough to cancel the animation - the resting state has to be forced
   back to visible, or a reduced-motion visitor gets a blank page. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .settle,
  .settle--left,
  .settle--right,
  .settle--pop {
    opacity: 1;
    transform: none;
  }

  .drift,
  .breathe,
  .hold-ring {
    animation: none;
    transform: none;
  }
}
