/* Sprite-sheet playback and battle effects (Adventure).
   Per-state sprite rules (row, frame count, timing) are generated from the frame
   table in src/battle-art.js, so art and CSS cannot drift apart. */

/* ------------------------------------------------------------ sprite sheets */
.px-sprite {
  --row: 0; --n: 4; --last: 3;
  display: inline-block; flex: none;
  width: calc(var(--fw) * var(--px) * 1px);
  height: calc(var(--fh) * var(--px) * 1px);
  background-repeat: no-repeat;
  background-size: calc(var(--sw) * var(--px) * 1px) calc(var(--sh) * var(--px) * 1px);
  background-position: 0 calc(var(--row) * var(--fh) * var(--px) * -1px);
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.px-missing { width: 96px; height: 96px; display: grid; place-items: center; background: #8993a3; color: #fff; font-size: 3rem; }
.px-summoned { animation: px-summon-in .42s steps(4) both; }
@keyframes px-summon-in { 0% { opacity: 0; } 25% { opacity: .25; } 50% { opacity: .5; } 75% { opacity: .75; } 100% { opacity: 1; } }

/* ------------------------------------------------------------ effect nodes */
.px-fx {
  position: absolute; z-index: 6; pointer-events: none;
  width: calc(var(--fw) * var(--px) * 1px); height: calc(var(--fh) * var(--px) * 1px);
  background-repeat: no-repeat; background-position: 0 0;
  background-size: calc(var(--sw) * var(--px) * 1px) calc(var(--sh) * var(--px) * 1px);
  image-rendering: pixelated; image-rendering: crisp-edges;
  transform: translate(-50%, -50%) scale(var(--scale, 1));
  animation: px-fx var(--dur) steps(var(--n)) forwards;
}
.px-fx--flip { transform: translate(-50%, -50%) scale(calc(var(--scale, 1) * -1), var(--scale, 1)); }
.px-fx--loop { animation: px-fx 240ms steps(var(--n)) infinite; }
.px-proj { z-index: 7; animation: px-fx 220ms steps(var(--n)) infinite, px-travel var(--travel) steps(8) forwards; }
@keyframes px-fx { to { background-position-x: calc(var(--n) * var(--fw) * var(--px) * -1px); } }
@keyframes px-travel { to { translate: var(--dx) var(--dy); } }

.px-streak {
  position: absolute; z-index: 5; pointer-events: none; transform-origin: 0 50%;
  height: calc(var(--px) * 5px); margin-top: calc(var(--px) * -2.5px);
  background: repeating-linear-gradient(90deg, #fff4b0 0 calc(var(--px) * 6px), #fff calc(var(--px) * 6px) calc(var(--px) * 8px), transparent calc(var(--px) * 8px) calc(var(--px) * 10px));
  animation: px-streak .34s steps(4) forwards;
}
.px-streak--shadow {
  background: repeating-linear-gradient(90deg, #3e1478 0 calc(var(--px) * 5px), #c98cff calc(var(--px) * 5px) calc(var(--px) * 7px), #1c0838 calc(var(--px) * 7px) calc(var(--px) * 9px), transparent calc(var(--px) * 9px) calc(var(--px) * 11px));
  box-shadow: 0 calc(var(--px) * 2px) 0 #6a24b855, 0 calc(var(--px) * -2px) 0 #6a24b855;
}
@keyframes px-streak { 0% { opacity: 1; clip-path: inset(0 100% 0 0); } 40% { clip-path: inset(0 0 0 0); } 75% { opacity: 1; } 100% { opacity: 0; clip-path: inset(0 0 0 70%); } }

/* The standing barrier dome sits on the hero's feet. */
.battle-hero .px-fx-barrier { position: absolute; left: 50%; top: auto; bottom: calc(var(--px) * -3px); transform: translateX(-50%); animation: px-fx 480ms steps(var(--n)) infinite; }

/* ------------------------------------------------------ attacker movement */
.battle-art-wrap.fx-dash { animation: fx-dash var(--dash-dur, 420ms) steps(8) both; z-index: 5; }
@keyframes fx-dash { 0% { translate: 0 0; } 30%, 72% { translate: var(--dash-x) 0; } 100% { translate: 0 0; } }

/* ------------------------------------------------------------ field camera */
.battle-field.fx-shake-s { animation: fx-shake-s .24s steps(4); }
.battle-field.fx-shake-m { animation: fx-shake-m .36s steps(6); }
.battle-field.fx-shake-l { animation: fx-shake-l .52s steps(8); }
@keyframes fx-shake-s { 25% { translate: calc(var(--px) * -1px) 0; } 50% { translate: calc(var(--px) * 1px) 0; } 75% { translate: calc(var(--px) * -1px) 0; } }
@keyframes fx-shake-m { 16% { translate: calc(var(--px) * -2px) 0; } 33% { translate: calc(var(--px) * 2px) calc(var(--px) * 1px); } 50% { translate: calc(var(--px) * -2px) 0; } 66% { translate: calc(var(--px) * 1px) calc(var(--px) * -1px); } 83% { translate: calc(var(--px) * -1px) 0; } }
@keyframes fx-shake-l { 12% { translate: calc(var(--px) * -3px) calc(var(--px) * 1px); } 25% { translate: calc(var(--px) * 3px) calc(var(--px) * -1px); } 37% { translate: calc(var(--px) * -3px) calc(var(--px) * 2px); } 50% { translate: calc(var(--px) * 2px) 0; } 62% { translate: calc(var(--px) * -2px) calc(var(--px) * 1px); } 75% { translate: calc(var(--px) * 1px) 0; } 87% { translate: calc(var(--px) * -1px) 0; } }
.battle-field.fx-punch > .battle-hero, .battle-field.fx-punch > .battle-enemies { animation: fx-punch .38s steps(5); }
@keyframes fx-punch { 30% { scale: 1.045; } 100% { scale: 1; } }
.battle-field.fx-flash-soft .battle-light { opacity: .35; background: #fff; }
.battle-field.fx-warn { outline-color: #ff5a4a; }

/* -------------------------------------------------------- lighting shifts */
.battle-light { transition: none; }
.battle-light[class*="fx-light--"] { animation: fx-light var(--light-dur, 700ms) steps(10) forwards; }
.fx-light--fire { background: radial-gradient(ellipse at 70% 40%, #ffb04066, #e84a1455 45%, #5a120866); mix-blend-mode: normal; }
.fx-light--shadow { background: radial-gradient(ellipse at 60% 50%, #6a24b855, #1c0838aa 70%); }
.fx-light--blood { background: radial-gradient(ellipse at 70% 30%, #e82a4a55, #5c061aaa 70%); }
.fx-light--white { background: #ffffff; animation-name: fx-flash !important; }
.fx-light--red { background: #ff4a3a; animation-name: fx-flash !important; }
.fx-light--warn { background: #ff5a1c; animation-name: fx-warn !important; }
@keyframes fx-light { 0% { opacity: 0; } 15% { opacity: 1; } 80% { opacity: 1; } 100% { opacity: 0; } }
@keyframes fx-flash { 0% { opacity: .5; } 100% { opacity: 0; } }
.fx-light--red { animation-name: fx-flash-red !important; }
@keyframes fx-flash-red { 0% { opacity: .38; } 100% { opacity: 0; } }
@keyframes fx-warn { 0%, 50% { opacity: .35; } 25%, 75% { opacity: 0; } 100% { opacity: 0; } }

/* ---------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  .px-sprite, .hero-card .px-sprite { animation: none !important; }
  .px-sprite { background-position-x: calc(var(--last) * var(--fw) * var(--px) * -1px); }
  .px-fx:not(.px-fx--static):not(.px-fx-barrier), .px-streak { display: none; }
  .px-fx--static { animation-duration: 1ms; }
  .battle-art-wrap.fx-dash, .battle-field[class*="fx-shake"], .battle-field.fx-punch > * { animation: none !important; }
  .battle-light[class*="fx-light--"] { animation: none !important; opacity: 0; }
}
