/*
 * Shared keyframes + selectors for level-map animated foreground layers.
 *
 * Consumed by both the in-game runtime (src/components/game/level-map/
 * animated-map-layer.tsx) and tools/walk-poc.html. Per memory rule
 * feedback_walk_poc_game_parity: this file is the load-bearing parity
 * guarantee. Both surfaces apply the SAME selectors and SAME CSS
 * custom properties — no duplicated keyframes anywhere.
 *
 * Element contract: <div class="anim-layer" data-anim="<preset>"
 *                        data-trigger="idle|click"
 *                        style="--anim-amplitude: …; --anim-duration: …;
 *                               --anim-delay: …;  (+ --move-* for `moving`)">
 *
 * `--anim-amplitude` is UNITLESS — each preset applies its own unit via
 * calc() (deg for wiggle/sway, px for float/bounce/shake, percent for pulse).
 *
 * Every preset is defined for BOTH triggers so no editor combination is a
 * dead cell:
 *   - data-trigger="idle"  → animation loops continuously on mount.
 *   - data-trigger="click" → plays once each time `.anim-playing` is toggled
 *                            on (pointerdown), cleared on `animationend`.
 */

.anim-layer {
  position: absolute;
  background-repeat: no-repeat;
  background-position: top left;
  background-size: contain;
  pointer-events: none;
  --anim-amplitude: 4;
  --anim-duration: 2s;
  --anim-delay: 0s;
}

.anim-layer[data-trigger="click"] {
  pointer-events: auto;
  cursor: pointer;
}

/* Per-preset transform origins — apply to both the idle loop and the click
   one-shot so rotation/scale pivots stay consistent across triggers. */
.anim-layer[data-anim="wiggle"],
.anim-layer[data-anim="pop-in"] { transform-origin: 50% 100%; }
.anim-layer[data-anim="sway"]   { transform-origin: 50% 95%; }
.anim-layer[data-anim="pulse"],
.anim-layer[data-anim="moving"] { transform-origin: 50% 50%; }

/* -------- Keyframes (declared once, shared by idle + click) ----------- */

@keyframes anim-wiggle {
  0%, 100% { transform: rotate(calc(var(--anim-amplitude) * -1deg)); }
  50%      { transform: rotate(calc(var(--anim-amplitude) * 1deg)); }
}

/* Same shape as anim-wiggle but kept separate so sway can diverge in
   curve/easing later without coupled edits (it already differs in origin). */
@keyframes anim-sway {
  0%, 100% { transform: rotate(calc(var(--anim-amplitude) * -1deg)); }
  50%      { transform: rotate(calc(var(--anim-amplitude) * 1deg)); }
}

@keyframes anim-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(calc(var(--anim-amplitude) * -1px)); }
}

@keyframes anim-pop-in {
  0%   { transform: scale(0.6); opacity: 0; }
  60%  { transform: scale(1.05); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes anim-slide-in-then-settle {
  0%   { transform: translateY(40px); opacity: 0; }
  70%  { transform: translateY(-4px); opacity: 1; }
  100% { transform: translateY(0); opacity: 1; }
}

@keyframes anim-bounce {
  0%, 100% { transform: translateY(0); }
  30%      { transform: translateY(calc(var(--anim-amplitude) * -3px)); }
  60%      { transform: translateY(0); }
  80%      { transform: translateY(calc(var(--anim-amplitude) * -1px)); }
}

@keyframes anim-shake {
  0%, 100%      { transform: translateX(0); }
  10%, 30%, 50% { transform: translateX(calc(var(--anim-amplitude) * -1px)); }
  20%, 40%, 60% { transform: translateX(calc(var(--anim-amplitude) * 1px)); }
  70%           { transform: translateX(calc(var(--anim-amplitude) * -0.5px)); }
  80%           { transform: translateX(calc(var(--anim-amplitude) * 0.5px)); }
}

/* Pulse — amplitude is percent grow (15 → scales to 1.15×). */
@keyframes anim-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(calc(1 + var(--anim-amplitude) * 0.01)); }
}

/* Moving — ping-pong between endpoint A (the element's anchor) and endpoint
   B (anchor + (--move-dx, --move-dy)), composing translate + scaleX in one
   keyframe so a single element handles both motion and the facing flip:
   A→B facing --move-fwd, instant flip at B, B→A facing --move-back, then it
   flips back at the A endpoint on loop. scaleX mirrors the box in place
   (origin centre) before the translate, so position is unaffected by the
   flip. */
/* `moving` is two composed animations on one element with INDEPENDENT
   durations, so travel speed and wobble speed are separate knobs:
   - anim-move-travel drives the straight A→B→A trip via the `translate`
     property + the facing flip via the `scale` property (duration =
     --anim-duration). Instant flip at B and A.
   - anim-move-wobble drives a perpendicular bob via the `transform` property
     (duration = --move-wobble-duration), looping independently.
   The browser composes `translate`/`scale`/`transform` (Transforms L2), so
   the net path is a ~~~~ sine whose hump count = travel ÷ wobble duration.
   The bob is along the unit vector perpendicular to travel (--move-perp-x/y),
   so it waves correctly whatever direction the layer flies. */
@keyframes anim-move-travel {
  0%     { translate: 0 0;                                 scale: var(--move-fwd, 1) 1; }
  50%    { translate: var(--move-dx, 0) var(--move-dy, 0); scale: var(--move-fwd, 1) 1; }
  50.01% { translate: var(--move-dx, 0) var(--move-dy, 0); scale: var(--move-back, -1) 1; }
  100%   { translate: 0 0;                                 scale: var(--move-back, -1) 1; }
}
@keyframes anim-move-wobble {
  0%, 100% { transform: translate(0, 0); }
  25%      { transform: translate(calc(var(--move-perp-x, 0) * var(--move-wobble, 0px)),      calc(var(--move-perp-y, 0) * var(--move-wobble, 0px))); }
  75%      { transform: translate(calc(var(--move-perp-x, 0) * var(--move-wobble, 0px) * -1), calc(var(--move-perp-y, 0) * var(--move-wobble, 0px) * -1)); }
}
/* One-way moving: A→B once and HOLD at B (no return leg). Paired with
   animation-fill-mode: forwards in the selectors below. Facing stays --move-fwd
   the whole trip (no flip — there's no return). */
@keyframes anim-move-oneway {
  0%   { translate: 0 0;                                 scale: var(--move-fwd, 1) 1; }
  100% { translate: var(--move-dx, 0) var(--move-dy, 0); scale: var(--move-fwd, 1) 1; }
}

/* -------- Idle: loops continuously on mount (data-trigger="idle") ----- */

.anim-layer[data-trigger="idle"][data-anim="wiggle"] { animation: anim-wiggle var(--anim-duration) ease-in-out var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="sway"]   { animation: anim-sway   var(--anim-duration) ease-in-out var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="float"]  { animation: anim-float  var(--anim-duration) ease-in-out var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="bounce"] { animation: anim-bounce var(--anim-duration) ease-out     var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="shake"]  { animation: anim-shake  var(--anim-duration) ease-in-out var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="pulse"]  { animation: anim-pulse  var(--anim-duration) ease-in-out var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="moving"]:not([data-move-oneway]) {
  animation: anim-move-travel var(--anim-duration) linear var(--anim-delay) infinite,
             anim-move-wobble var(--move-wobble-duration, 1.2s) ease-in-out var(--anim-delay) infinite;
}
/* One-way idle: flies A→B once on mount and holds at B (fill forwards). */
.anim-layer[data-trigger="idle"][data-anim="moving"][data-move-oneway] {
  animation: anim-move-oneway var(--anim-duration) linear var(--anim-delay) 1 forwards,
             anim-move-wobble var(--move-wobble-duration, 1.2s) ease-in-out var(--anim-delay) infinite;
}
/* pop-in / slide are entrance shapes; as idle they replay on a loop so the
   editor still shows visible motion for the combination. */
.anim-layer[data-trigger="idle"][data-anim="pop-in"] { animation: anim-pop-in var(--anim-duration) ease-out var(--anim-delay) infinite; }
.anim-layer[data-trigger="idle"][data-anim="slide-in-then-settle"] { animation: anim-slide-in-then-settle var(--anim-duration) ease-out var(--anim-delay) infinite; }

/* -------- Click: plays once per tap (.anim-playing toggled by JS) ----- */

.anim-layer[data-trigger="click"][data-anim="wiggle"].anim-playing { animation: anim-wiggle var(--anim-duration) ease-in-out 1; }
.anim-layer[data-trigger="click"][data-anim="sway"].anim-playing   { animation: anim-sway   var(--anim-duration) ease-in-out 1; }
.anim-layer[data-trigger="click"][data-anim="float"].anim-playing  { animation: anim-float  var(--anim-duration) ease-in-out 1; }
.anim-layer[data-trigger="click"][data-anim="bounce"].anim-playing { animation: anim-bounce var(--anim-duration) ease-out 1; }
.anim-layer[data-trigger="click"][data-anim="shake"].anim-playing  { animation: anim-shake  var(--anim-duration) ease-in-out 1; }
.anim-layer[data-trigger="click"][data-anim="pulse"].anim-playing  { animation: anim-pulse  var(--anim-duration) ease-in-out 1; }
.anim-layer[data-trigger="click"][data-anim="moving"]:not([data-move-oneway]).anim-playing {
  animation: anim-move-travel var(--anim-duration) linear 1,
             anim-move-wobble var(--move-wobble-duration, 1.2s) ease-in-out infinite;
}
/* One-way click: flies A→B once on tap and holds at B (fill forwards). The
   runtime leaves .anim-playing on for one-way so the forwards fill persists. */
.anim-layer[data-trigger="click"][data-anim="moving"][data-move-oneway].anim-playing {
  animation: anim-move-oneway var(--anim-duration) linear 1 forwards,
             anim-move-wobble var(--move-wobble-duration, 1.2s) ease-in-out infinite;
}
.anim-layer[data-trigger="click"][data-anim="pop-in"].anim-playing { animation: anim-pop-in var(--anim-duration) ease-out 1; }
.anim-layer[data-trigger="click"][data-anim="slide-in-then-settle"].anim-playing { animation: anim-slide-in-then-settle var(--anim-duration) ease-out 1; }

/* -------- Journey flight (multi-step layers; see animated-map-layer.tsx) ----
   A journey layer rests at a stop playing its normal `animation` preset via the
   idle selectors above (data-trigger="idle"), and flies between stops via a
   left/top CSS transition set inline by the runtime. While flying it carries
   data-trigger="flying" so NO parked idle selector matches (motion is the
   transition, not a keyframe). `straight` flight adds no keyframe; `wobble`
   flight adds the shared perpendicular sine — the runtime sets --move-wobble +
   --move-perp-x/y for the active leg, exactly like `moving`. */
.anim-layer[data-trigger="flying"] { pointer-events: none; }
.anim-layer[data-trigger="flying"].journey-wobble {
  animation: anim-move-wobble var(--move-wobble-duration, 1.2s) ease-in-out infinite;
}

/* `none` preset — present so the editor dropdown has a no-op default. */
.anim-layer[data-anim="none"] { animation: none; }
