/* REFERENCE
 * - ui/mote-animation/mote-anim-action.css (ma-attack / ma-hurt — the one-shot family this extends)
 * - ui/mote-animation/mote-anim-vars.css (--squash-depth / --stretch-peak personality routing)
 * - ui/mote-animation/mote-anim-idle.css (per-instance desync precedent; --combat-delay follows it)
 * - ui/overlay/mote-avatar.js (v2 rig — .mote-body-anim carries the INLINE ground anchor:
 *   transform-box:view-box; transform-origin:50px 78px. Every keyframe below is designed
 *   for that origin: squashes plant at the ground, KO tips over the contact point. This
 *   file declares NO transform-box/origin on .mote-body-anim — the inline style is truth.)
 * - ui/companion/mote-work.css (phase comments, launch bezier, impact conventions)
 *
 * Combat choreography (TERÄS): party bonk-lunge, boss haymaker, directional hurt-react,
 * opacity-only hit flash, KO fall, target-lock pulse.
 *
 * IMPACT TIMESTAMPS the conductor syncs to (hurt-reacts, HP ticks, particles, sfx):
 *   party attack  impact at 32%  →  --combat-delay + 0.32 × --combat-dur  (default 128ms)
 *   boss attack   impact at 58%  →  0.58 × 680ms = 394ms  (the boss takes no jitter)
 *   hurt          peak at 0%     →  add the class exactly AT the attacker's impact
 *   ko            thud at 55%    →  495ms; fade settled by 900ms
 *
 * HARD RULES: transform/opacity ONLY — filter is BANNED (5 concurrent actors on one OBS
 * browser source; the old ma-hurt-flash brightness() path cost a filter pass per mote).
 * All one-shots are `forwards` + a class the JS conductor toggles. Cascade order in this
 * file is load-bearing: attack < hurt < ko — a later class interrupts an earlier one on
 * the same node (getting hit cancels your swing; dying cancels your flinch). */

/* ── Resting states for renderer-provided combat nodes ─────────── */
.mote-flash { opacity: 0; }
.mote-target-ring {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

/* ═══ PARTY ATTACK — the bonk lunge ═══════════════════════════════
   Phases: 0–12% coil (gather back + down) · 12–32% launch (house launch
   bezier) · 32% IMPACT (peak stretch, eye pop) · 32–55% recoil past
   origin · 55–100% settle wobble.
   Direction: every translateX is × --attack-dir (1 = +X; the boss stamps
   -1). No scaleX(-1) flip anywhere — faces/glints never mirror. */
.anim-fight-attack.mote-body-anim {
  --fa-dir: var(--attack-dir, 1);
  --fa-lunge:  calc(10px * var(--stretch-peak, 1.12) * var(--fa-dir));
  --fa-coil-x: calc(var(--squash-depth, 0.85) + 0.05);
  --fa-coil-y: calc(1.98 - var(--fa-coil-x));
  --fa-hit-x:  calc(var(--stretch-peak, 1.12) + 0.02);
  --fa-hit-y:  calc(2.04 - var(--fa-hit-x));
  animation: ma-fight-attack var(--combat-dur, 400ms) var(--combat-delay, 0ms) forwards;
}
@keyframes ma-fight-attack {
  0%   { transform: translateX(0) scaleX(1) scaleY(1);
         animation-timing-function: ease-in-out; }
  12%  { transform: translateX(calc(-4px * var(--fa-dir))) scaleX(var(--fa-coil-x)) scaleY(var(--fa-coil-y));
         animation-timing-function: cubic-bezier(0.7, 0, 1, 0.6); }  /* coil → launch */
  32%  { transform: translateX(var(--fa-lunge)) scaleX(var(--fa-hit-x)) scaleY(var(--fa-hit-y));
         animation-timing-function: ease-out; }                       /* IMPACT */
  55%  { transform: translateX(calc(-1.5px * var(--fa-dir))) scaleX(0.97) scaleY(1.03);
         animation-timing-function: ease-in-out; }                    /* recoil overshoot */
  78%  { transform: translateX(calc(0.5px * var(--fa-dir))) scaleX(1.005) scaleY(0.995);
         animation-timing-function: ease-out; }                       /* settle wobble */
  100% { transform: translateX(0) scaleX(1) scaleY(1); }
}

/* Attacker eyes: squint into the coil, pop to 1.25 AT the 32% impact. */
.anim-fight-attack .mote-eye {
  animation: ma-fight-attack-eyes var(--combat-dur, 400ms) var(--combat-delay, 0ms) ease-out forwards;
}
@keyframes ma-fight-attack-eyes {
  0%   { transform: scale(1); }
  12%  { transform: scale(0.9); }    /* coil squint */
  32%  { transform: scale(1.25); }   /* impact pop */
  60%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}

/* ═══ TAKE-DAMAGE — directional squash, zero windup ═══════════════
   The EXTREME sits at 0% — no ramp-up, ever; that asymmetry is what
   reads as reaction, not action. rotate(A) … rotate(-A) compresses
   along --hit-angle (the angle the blow TRAVELS: 0deg party→boss,
   180deg boss→party). Function list identical in every frame so CSS
   interpolates per-function, never via matrix decomposition. */
.anim-fight-hurt.mote-body-anim {
  --fh-a:     var(--hit-angle, 0deg);
  --fh-in:    var(--squash-depth, 0.85);
  --fh-bulge: calc(2 - var(--fh-in));
  --fh-out:   var(--stretch-peak, 1.12);
  --fh-thin:  calc(2 - var(--fh-out));
  animation: ma-fight-hurt var(--hurt-dur, 350ms) forwards;
}
@keyframes ma-fight-hurt {
  0%   { transform: rotate(var(--fh-a)) translateX(3px) scaleX(var(--fh-in)) scaleY(var(--fh-bulge)) rotate(calc(-1 * var(--fh-a)));
         animation-timing-function: ease-out; }      /* EXTREME at t=0 */
  38%  { transform: rotate(var(--fh-a)) translateX(-1px) scaleX(var(--fh-out)) scaleY(var(--fh-thin)) rotate(calc(-1 * var(--fh-a)));
         animation-timing-function: ease-in-out; }   /* rebound */
  62%  { transform: rotate(var(--fh-a)) translateX(0.6px) scaleX(0.965) scaleY(1.035) rotate(calc(-1 * var(--fh-a)));
         animation-timing-function: ease-in-out; }   /* wobble 2 */
  82%  { transform: rotate(var(--fh-a)) translateX(-0.2px) scaleX(1.012) scaleY(0.988) rotate(calc(-1 * var(--fh-a)));
         animation-timing-function: ease-out; }      /* wobble 3, nearly dead */
  100% { transform: rotate(var(--fh-a)) translateX(0) scaleX(1) scaleY(1) rotate(calc(-1 * var(--fh-a))); }
}

/* Victim eyes: slammed shut AT 0%, reopen by 60%. */
.anim-fight-hurt .mote-eye {
  animation: ma-fight-hurt-eyes var(--hurt-dur, 350ms) ease-out forwards;
}
@keyframes ma-fight-hurt-eyes {
  0%        { transform: scaleY(0.1) scaleX(1.3); }
  60%, 100% { transform: scaleY(1) scaleX(1); }
}

/* HURT FLASH — opacity-only (brightness() replacement). */
.anim-fight-hurt .mote-flash,
.anim-fight-ko .mote-flash {   /* the killing blow still flashes */
  animation: ma-fight-flash var(--hurt-dur, 350ms) linear forwards;
}
@keyframes ma-fight-flash {
  0%        { opacity: 0.85; }  /* pop — full silhouette at the hit frame */
  30%       { opacity: 0; }
  45%       { opacity: 0.5; }   /* echo pulse */
  70%, 100% { opacity: 0; }
}

/* ═══ BOSS SCOPE — heavier physics ═════════════════════════════════
   Flat squash/stretch exist FOR THE HURT: a boss barely wobbles
   (.93/1.06) but takes 550ms to settle — and since ma-fight-hurt keeps
   its extreme at 0%, stretching the duration never grows a ramp-up. */
.mote-fight--boss,
.mote-fight--boss [data-mote-type] {
  --combat-dur: 680ms;
  --hurt-dur: 550ms;
  --squash-depth: 0.93;
  --stretch-peak: 1.06;
}

/* Boss attack — the telegraphed haymaker. Own keyframe block because
   the PHASE LAYOUT changes (percents can't route through vars):
   0–18% rise into coil · 18–45% loaded HOLD-CREEP (the telegraph) ·
   45–58% fast launch · 58% IMPACT (394ms) · 58–66% bury · 66–100%
   recoil + settle. Amplitudes literal — the flat boss vars would
   starve the haymaker to a poke. */
.mote-fight--boss .anim-fight-attack.mote-body-anim {
  animation-name: ma-fight-attack-boss;
}
@keyframes ma-fight-attack-boss {
  0%   { transform: translateX(0) scaleX(1) scaleY(1);
         animation-timing-function: ease-in-out; }
  18%  { transform: translateX(calc(-6px * var(--fa-dir))) scaleX(0.88) scaleY(1.10);
         animation-timing-function: linear; }                          /* coil reached */
  45%  { transform: translateX(calc(-7.5px * var(--fa-dir))) scaleX(0.86) scaleY(1.12);
         animation-timing-function: cubic-bezier(0.7, 0, 1, 0.6); }    /* hold-creep → launch */
  58%  { transform: translateX(calc(15px * var(--fa-dir))) scaleX(1.16) scaleY(0.88);
         animation-timing-function: ease-out; }                        /* IMPACT */
  66%  { transform: translateX(calc(16px * var(--fa-dir))) scaleX(1.10) scaleY(0.92);
         animation-timing-function: ease-in-out; }                     /* bury / follow-through */
  82%  { transform: translateX(calc(-2px * var(--fa-dir))) scaleX(0.97) scaleY(1.03);
         animation-timing-function: ease-out; }                        /* recoil */
  100% { transform: translateX(0) scaleX(1) scaleY(1); }
}

/* Boss flash: a mountain shrugging off chip damage never whites out —
   soft ember-tinted pulse instead of the party's full silhouette pop.
   (Full-strength flash on 5-attacker cadence washed the black body grey.) */
.mote-fight--boss .anim-fight-hurt .mote-flash,
.mote-fight--boss .anim-fight-ko .mote-flash {
  animation-name: ma-fight-flash-boss;
}
@keyframes ma-fight-flash-boss {
  0%        { opacity: 0.35; }
  40%       { opacity: 0.1; }
  55%       { opacity: 0.18; }
  75%, 100% { opacity: 0; }
}

/* Boss eyes: menace squint held through the whole 18–45% coil (the tell). */
.mote-fight--boss .anim-fight-attack .mote-eye {
  animation-name: ma-fight-attack-eyes-boss;
}
@keyframes ma-fight-attack-eyes-boss {
  0%       { transform: scale(1); }
  18%, 45% { transform: scale(0.85); }  /* held menace squint */
  58%      { transform: scale(1.25); }  /* impact pop */
  100%     { transform: scale(1); }
}

/* ═══ KO / DEATH — party only ══════════════════════════════════════
   0–14% counter-lean (the last gasp) · 14–55% gravity slam to 80°
   about the GROUND anchor · 55–68% one small bounce · 68–100% fade.
   --ko-dir: 1 tips clockwise (+X); boss victims stamp -1. */
.anim-fight-ko.mote-body-anim {
  --fk-dir: var(--ko-dir, 1);
  animation: ma-fight-ko 900ms forwards;
}
@keyframes ma-fight-ko {
  0%   { transform: rotate(0deg); opacity: 1;
         animation-timing-function: ease-in-out; }
  14%  { transform: rotate(calc(-7deg * var(--fk-dir)));
         animation-timing-function: cubic-bezier(0.62, 0.04, 0.96, 0.36); }  /* counter-lean */
  55%  { transform: rotate(calc(80deg * var(--fk-dir)));
         animation-timing-function: ease-out; }                              /* THUD */
  68%  { transform: rotate(calc(73deg * var(--fk-dir))); opacity: 1;
         animation-timing-function: ease-in-out; }                           /* bounce */
  80%  { transform: rotate(calc(80deg * var(--fk-dir)));
         animation-timing-function: linear; }
  100% { transform: rotate(calc(80deg * var(--fk-dir))); opacity: 0.35; }    /* fade out */
}
/* KO'd motes don't blink. Static held pose. */
.anim-fight-ko .mote-eye { animation: none; transform: scaleY(0.12); }

/* ═══ TARGET-LOCK — the boss's next victim ═════════════════════════ */
.mote-fight--targeted .mote-target-ring {
  animation: ma-fight-target 1.1s ease-in-out infinite;
}
@keyframes ma-fight-target {
  0%, 100% { opacity: 0.30; transform: scale(0.96); }
  50%      { opacity: 0.70; transform: scale(1.05); }
}

/* === V2 SKILLS (fight-script 2026-07-16) =========================== */
/* Wild Swing flurry: double lunge - two impact pulses in one shot */
.anim-fight-flurry.mote-body-anim {
  animation: ma-fight-flurry var(--combat-dur, 640ms) cubic-bezier(0.5, 0, 0.75, 0.4) forwards;
}
@keyframes ma-fight-flurry {
  0%   { transform: translateX(0) scaleY(1); }
  38%  { transform: translateX(calc(6px * var(--attack-dir, 1))) scaleY(0.94); }
  52%  { transform: translateX(calc(15px * var(--attack-dir, 1))) scaleY(1.05); }
  62%  { transform: translateX(calc(9px * var(--attack-dir, 1))) scaleY(0.97); }
  78%  { transform: translateX(calc(17px * var(--attack-dir, 1))) scaleY(1.06); }
  100% { transform: translateX(0) scaleY(1); }
}

/* Boss sweep: wide horizontal arc - bigger travel, longer coil */
.anim-fight-sweep-boss.mote-body-anim {
  animation: ma-fight-sweep var(--combat-dur, 560ms) cubic-bezier(0.55, 0, 0.8, 0.5) forwards;
}
@keyframes ma-fight-sweep {
  0%   { transform: translateX(0) rotate(0deg) scaleY(1); }
  40%  { transform: translateX(calc(-6px * var(--attack-dir, -1))) rotate(calc(8deg * var(--attack-dir, -1))) scaleY(0.9); }
  62%  { transform: translateX(calc(22px * var(--attack-dir, -1))) rotate(calc(-10deg * var(--attack-dir, -1))) scaleY(1.06); }
  100% { transform: translateX(0) rotate(0deg) scaleY(1); }
}

/* Boss ultimate: long coil, huge lunge - the telegraph did the warning */
.anim-fight-ultimate-boss.mote-body-anim {
  animation: ma-fight-ultimate var(--combat-dur, 560ms) cubic-bezier(0.6, 0, 0.85, 0.45) forwards;
}
@keyframes ma-fight-ultimate {
  0%   { transform: translateX(0) scale(1); }
  45%  { transform: translateX(calc(-10px * var(--attack-dir, -1))) scale(0.92, 1.1); }
  64%  { transform: translateX(calc(30px * var(--attack-dir, -1))) scale(1.12, 0.94); }
  100% { transform: translateX(0) scale(1); }
}

/* Dodge: quick hop back - the false-finish whiff read */
.anim-fight-dodge.mote-body-anim {
  animation: ma-fight-dodge 460ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes ma-fight-dodge {
  0%   { transform: translate(0, 0); }
  35%  { transform: translate(calc(-14px * var(--attack-dir, 1)), -10px) rotate(calc(-6deg * var(--attack-dir, 1))); }
  100% { transform: translate(0, 0); }
}

/* Boss recoil off a block: bounce back, no trauma on the blocker */
.anim-fight-recoil.mote-body-anim {
  animation: ma-fight-recoil 420ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes ma-fight-recoil {
  0%   { transform: translateX(0); }
  40%  { transform: translateX(calc(-12px * var(--attack-dir, -1))) rotate(calc(4deg * var(--attack-dir, -1))); }
  100% { transform: translateX(0); }
}

/* Victory bounce: survivors celebrate while the boss topples */
.anim-fight-victory.mote-body-anim { animation: ma-fight-victory 900ms cubic-bezier(0.34, 1.56, 0.64, 1) infinite; }
@keyframes ma-fight-victory {
  0%, 100% { transform: translateY(0) scale(1); }
  40%      { transform: translateY(-9px) scale(1.04, 0.98); }
  60%      { transform: translateY(-6px) scale(1); }
}

/* -- Reduced motion: opacity beats only, zero transforms ---------- */
@media (prefers-reduced-motion: reduce) {
  .anim-fight-attack .mote-eye,
  .anim-fight-hurt .mote-eye { animation: none !important; }

  .anim-fight-flurry.mote-body-anim,
  .anim-fight-sweep-boss.mote-body-anim,
  .anim-fight-ultimate-boss.mote-body-anim,
  .anim-fight-dodge.mote-body-anim,
  .anim-fight-recoil.mote-body-anim {
    animation: ma-fight-rm-act var(--combat-dur, 400ms) ease-in-out forwards !important;
  }
  .anim-fight-victory.mote-body-anim { animation: none !important; }

  .anim-fight-attack.mote-body-anim {
    animation: ma-fight-rm-act var(--combat-dur, 400ms) var(--combat-delay, 0ms) ease-in-out forwards !important;
  }
  .anim-fight-hurt.mote-body-anim {
    animation: ma-fight-rm-hurt var(--hurt-dur, 350ms) ease-out forwards !important;
  }
  .anim-fight-hurt .mote-flash,
  .anim-fight-ko .mote-flash {
    animation: ma-fight-rm-flash var(--hurt-dur, 350ms) ease-out forwards !important;
  }
  .anim-fight-ko.mote-body-anim {
    animation: ma-fight-rm-ko 900ms ease-out forwards !important;
  }
  .mote-fight--targeted .mote-target-ring {
    animation: none !important;
    opacity: 0.5;
  }
}
@keyframes ma-fight-rm-act  { 0%, 100% { opacity: 1; } 32% { opacity: 0.55; } }
@keyframes ma-fight-rm-hurt { 0% { opacity: 0.45; } 100% { opacity: 1; } }
@keyframes ma-fight-rm-flash{ 0% { opacity: 0.4; } 60%, 100% { opacity: 0; } }
@keyframes ma-fight-rm-ko   { to { opacity: 0.35; } }
