/* ==========================================================================
   animations.css — Keyframes & utility animation classes
   Blackjack Solitaire
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Keyframe Definitions
   -------------------------------------------------------------------------- */

/* ---- 1a. shake — invalid placement feedback (300ms, ±3px horizontal) ---- */
@keyframes shake {
  0%,
  100% { transform: translateX(0); }
  20%  { transform: translateX(-3px); }
  40%  { transform: translateX(3px); }
  60%  { transform: translateX(-3px); }
  80%  { transform: translateX(2px); }
}

/* ---- 1b. glowPulse — hand reaching 21, accent box-shadow pulse (600ms).
        Was hardcoded Casino Gold on every theme until #151; --accent-rgb is
        substituted against the animated element, so it picks up the theme. ---- */
@keyframes glowPulse {
  0%   { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0); }
  30%  { box-shadow: 0 0 16px 4px rgba(var(--accent-rgb), 0.55); }
  60%  { box-shadow: 0 0 24px 6px rgba(var(--accent-rgb), 0.7); }
  100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0); }
}

/* ---- 1c. scorePop — score update scale 1 → 1.2 → 1 (300ms) ---- */
@keyframes scorePop {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.2); }
  100% { transform: scale(1); }
}

/* --------------------------------------------------------------------------
   2. Supporting Keyframes (used by layout.css / cards.css classes)
   -------------------------------------------------------------------------- */

/* Hand clear: cards fly up and fade out */
@keyframes handClear {
  0%   { transform: scale(1)    translateY(0);    opacity: 1; }
  50%  { transform: scale(1.08) translateY(-8px); opacity: 0.8; }
  100% { transform: scale(1.15) translateY(-20px); opacity: 0; }
}

/* Drag-over border pulse — valid drop target */
@keyframes dragoverPulse {
  0%, 100% { border-color: var(--border-hand-valid); box-shadow: 0 0 12px 2px var(--border-hand-valid); }
  50%       { border-color: var(--border-hand-valid); box-shadow: 0 0 6px 1px transparent; }
}

/* Card flip (Y-axis, used with .card-flip-inner) */
@keyframes cardFlipIn {
  from { transform: rotateY(90deg); opacity: 0; }
  to   { transform: rotateY(0deg);  opacity: 1; }
}

/* Burn card slide + red tint */
@keyframes burnSlide {
  from {
    transform: translate(0, 0) scale(1);
    opacity: 1;
    filter: none;
  }
  to {
    transform: translate(var(--burn-target-x, -60px), var(--burn-target-y, 20px)) scale(0.85);
    opacity: 0;
    filter: brightness(0.4) sepia(1) hue-rotate(-30deg) saturate(3);
  }
}

/* Score count tick (number value animation, driven by JS) */
@keyframes countTick {
  0%   { transform: translateY(0); opacity: 1; }
  50%  { transform: translateY(-4px); opacity: 0.6; }
  100% { transform: translateY(0); opacity: 1; }
}

/* Deck hover scale (CSS transition handles this, but keyframe for JS trigger) */
@keyframes deckPulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(var(--deck-hover-scale, 1.05)); }
  100% { transform: scale(1); }
}

/* Dust mote drift — used by `.living-mote` (src/ui/livingSurface.js).
   These keyframes are the design's `drift` verbatim: fade in as the mote rises,
   hold, then fade out, over a 50px climb. Because every mote is a real element
   with its own duration and delay, the fade to 0 at both ends is correct here —
   the motes are individually phased, so nothing blinks in unison. (The previous
   single-layer version had to avoid opacity 0 for exactly that reason.) */
@keyframes drift {
  0%   { opacity: 0;  transform: translateY(10px); }
  20%  { opacity: .5; }
  80%  { opacity: .4; }
  100% { opacity: 0;  transform: translateY(-40px); }
}

/* Neon ambient glow — used by living surface body::after on the neon themes,
   which replaced the scan-grid (a second square pattern competing with the
   cross-hatch `body` already paints on every theme).

   Transform and opacity only, so this composites on the GPU and repaints
   nothing: the "cheap to run" half of the design's brief. The layer is inset
   past the viewport on all sides, so the drift can never walk an edge into
   view. Slow on purpose — a glow that reads as pulsing competes with the
   cards, which is the one thing this whole surface must not do. */
@keyframes glowDrift {
  from { transform: translate3d(0, 0, 0) scale(1);           opacity: .65; }
  to   { transform: translate3d(6%, -5%, 0) scale(1.18);     opacity: 1; }
}

/* --------------------------------------------------------------------------
   Blackjack burst keyframes
   -------------------------------------------------------------------------- */

@keyframes burstRing {
  0%   { transform: scale(1);   opacity: 1; }
  100% { transform: scale(7);   opacity: 0; }
}

@keyframes burstSpark {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--spark-dx, 0), var(--spark-dy, 0)) scale(0); opacity: 0; }
}

@keyframes burstConfetti {
  0% {
    transform: translate(0, 0) rotate(0deg) scale(1);
    opacity: 1;
  }
  100% {
    transform: translate(var(--conf-dx, 0), var(--conf-dy, 80px)) rotate(var(--conf-rot, 360deg)) scale(0.5);
    opacity: 0;
  }
}

@keyframes burstLabel {
  0%   { transform: translateX(-50%) scale(0.5); opacity: 0; }
  25%  { transform: translateX(-50%) scale(1.15); opacity: 1; }
  60%  { transform: translateX(-50%) scale(1); opacity: 1; }
  80%  { transform: translateX(-50%) translateY(-20px) scale(0.95); opacity: 1; }
  100% { transform: translateX(-50%) translateY(-20px) scale(0.9); opacity: 0; }
}

@keyframes burstGain {
  0%   { transform: translateX(-50%) translateY(0); opacity: 0; }
  15%  { opacity: 1; }
  80%  { transform: translateX(-50%) translateY(-36px); opacity: 1; }
  100% { transform: translateX(-50%) translateY(-44px); opacity: 0; }
}

/* Final tally card fan */
@keyframes tallyCardIn {
  from {
    transform: var(--tally-card-start, translateX(-50%) rotate(0deg) translateY(10px));
    opacity: 0;
  }
  to {
    transform: var(--tally-card-end, translateX(-50%) rotate(0deg) translateY(0));
    opacity: 1;
  }
}

/* New best spring */
@keyframes spring {
  0%   { transform: scale(0.5); opacity: 0; }
  55%  { transform: scale(1.12); opacity: 1; }
  75%  { transform: scale(0.95); }
  100% { transform: scale(1); opacity: 1; }
}

/* Soft bust — red ring pulse (single throb) */
@keyframes redRingThrob {
  0%   { box-shadow: 0 0 0 0 transparent; }
  35%  { box-shadow: 0 0 0 6px var(--color-alert); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Soft bust — gentle horizontal nudge */
@keyframes softNudge {
  0%   { transform: translateX(0); }
  18%  { transform: translateX(-6px); }
  44%  { transform: translateX(5px); }
  72%  { transform: translateX(-3px); }
  100% { transform: translateX(0); }
}

/* Soft bust label fade-in-hold-out */
@keyframes bustLabelFade {
  0%   { opacity: 0; transform: translateX(-50%) translateY(4px); }
  14%  { opacity: 1; transform: translateX(-50%) translateY(0); }
  55%  { opacity: 1; transform: translateX(-50%) translateY(0); }
  100% { opacity: 0; transform: translateX(-50%) translateY(-8px); }
}

/* Header title Y-axis flip — fires once per new game */
@keyframes headerFlip {
  0%   { transform: rotateY(0deg); }
  100% { transform: rotateY(360deg); }
}

/* Empty-slot ambient breathe — 4s border glow pulse */
@keyframes slotBreathe {
  0%, 100% {
    border-color: var(--border-hand-empty);
    box-shadow: none;
  }
  50% {
    border-color: var(--color-accent);
    box-shadow: 0 0 10px 2px rgba(var(--accent-rgb), 0.25);
  }
}

/* --------------------------------------------------------------------------
   3. Utility Animation Classes
   -------------------------------------------------------------------------- */

/* ---- shake: applied by JS on invalid card placement ---- */
.animate-shake {
  animation: shake 300ms ease-in-out forwards;
  animation-fill-mode: forwards;
}

/* ---- animate-glow: applied by JS when hand reaches 21 ---- */
.animate-glow {
  animation: glowPulse 600ms ease-in-out forwards;
  animation-fill-mode: forwards;
}

/* ---- animate-score-pop: applied by JS on score update ---- */
.animate-score-pop {
  animation: scorePop 300ms ease-out forwards;
  animation-fill-mode: forwards;
  display: inline-block; /* transform needs block context */
}

/* ---- Card animations ---- */
.animate-hand-clear {
  animation: handClear 400ms ease-in forwards;
  animation-fill-mode: forwards;
}

.animate-card-burn {
  animation: burnSlide 300ms ease-in forwards;
  animation-fill-mode: forwards;
}

.animate-card-flip {
  animation: cardFlipIn 300ms ease-in-out forwards;
  animation-fill-mode: forwards;
}

/* ---- Drag-over pulse: continuous while dragging over valid slot ---- */
.animate-dragover-pulse {
  animation: dragoverPulse 800ms ease-in-out infinite;
}

/* ---- Count tick ---- */
.animate-count-tick {
  animation: countTick 80ms linear;
}

/* ---- Score/burst overlay layer — see createFxAnchor() in animations.js.
   Fixed to the viewport so transient labels/bursts always render above the
   board and are immune to `.hands-area`'s mobile scroll clipping. Sits above
   the board but below the open header dropdown (z-index 70) and the modal
   overlay (z-index 100), so a menu or modal still wins if one opens
   mid-animation. ---- */
#score-fx-layer {
  position: fixed;
  inset: 0;
  z-index: 65;
  pointer-events: none;
}

.score-fx-anchor {
  position: absolute;
  pointer-events: none;
}

/* ---- Blackjack burst elements ---- */
.burst-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 80px;
  height: 80px;
  margin: -40px 0 0 -40px;
  border-radius: 50%;
  border: 2px solid var(--color-accent);
  pointer-events: none;
  animation: burstRing 900ms cubic-bezier(.2,.7,.3,1) forwards;
}

.burst-spark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--color-accent);
  pointer-events: none;
  animation: burstSpark 720ms ease-out forwards;
}

.burst-confetti {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  pointer-events: none;
  animation: burstConfetti 1200ms ease-out forwards;
}

.burst-label {
  position: absolute;
  top: -24px;
  left: 50%;
  font-family: var(--font-sans);
  font-size: 1.5rem;
  font-weight: 900;
  color: var(--color-accent);
  /* Dark halo + crisp stroke keep the label legible over dense confetti. */
  text-shadow:
    0 0 4px rgba(0,0,0,0.95),
    0 0 12px var(--color-accent),
    0 2px 5px rgba(0,0,0,0.85);
  -webkit-text-stroke: 0.5px rgba(0,0,0,0.7);
  pointer-events: none;
  z-index: 30;
  white-space: nowrap;
  animation: burstLabel 1700ms cubic-bezier(.2,.8,.3,1) forwards;
}

/* Burst label scales with the score tier (set by animateBlackjackBurst). */
.burst-label--tiny  { font-size: 1.1rem; }
.burst-label--small { font-size: 1.3rem; }
.burst-label--medium { font-size: 1.5rem; }
.burst-label--large { font-size: 1.9rem; }
.burst-label--epic  { font-size: 2.4rem; }

/* Above the slot, not below it: every hint and score indicator on this board
   reads upward, away from the hands, so nothing a player is about to click is
   covered by feedback about what they just did. */
.burst-gain {
  position: absolute;
  top: -46px;
  left: 50%;
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 800;
  color: #fff;
  /* Solid dark halo so the +points stays readable through falling confetti. */
  text-shadow:
    0 0 4px rgba(0,0,0,0.95),
    0 1px 4px rgba(0,0,0,0.85);
  -webkit-text-stroke: 0.4px rgba(0,0,0,0.6);
  pointer-events: none;
  z-index: 30;
  white-space: nowrap;
  opacity: 0;
  animation: burstGain 1500ms ease-out 220ms forwards;
}

/* ---- Soft bust ---- */
.animate-red-ring {
  animation: redRingThrob 700ms ease-out forwards;
}

.animate-soft-nudge {
  animation: softNudge 520ms cubic-bezier(.36,.07,.19,.97) forwards;
}

/* ---- Header wink — one-shot Y-axis flip on new game ---- */
.animate-header-wink {
  animation: headerFlip 700ms cubic-bezier(.2,.8,.3,1) forwards;
  display: inline-block;
}

/* ---- Empty-slot breathe (CSS-only, no JS needed) ---- */
.animate-slot-breathe {
  animation: slotBreathe 4s ease-in-out infinite;
  animation-delay: var(--breathe-delay, 0s);
}

/* --------------------------------------------------------------------------
   4. Transition Utilities
   -------------------------------------------------------------------------- */

/* Standard transition for colour/border changes */
.transition-colors {
  transition:
    color var(--duration-normal) var(--ease-out),
    background-color var(--duration-normal) var(--ease-out),
    border-color var(--duration-normal) var(--ease-out),
    box-shadow var(--duration-normal) var(--ease-out);
}

/* Standard transition for transforms */
.transition-transform {
  transition: transform var(--duration-fast) var(--ease-out);
}

/* --------------------------------------------------------------------------
   5. prefers-reduced-motion — Disable all animations & transitions
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  /*
   * Blanket disable: set duration to effectively zero.
   * This satisfies WCAG 2.1 SC 2.3.3 (Animation from Interactions).
   * Using 0.01ms rather than 0 to avoid breaking JS that reads duration.
   */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }

  /* Explicit overrides for the named utility classes */
  .animate-shake,
  .animate-glow,
  .animate-score-pop,
  .animate-hand-clear,
  .animate-card-burn,
  .animate-card-flip,
  .animate-dragover-pulse,
  .animate-count-tick,
  .animate-slot-breathe,
  .animate-header-wink,
  .animate-red-ring,
  .animate-soft-nudge,
  .burst-ring,
  .burst-spark,
  .burst-confetti,
  .burst-label,
  .burst-gain {
    animation: none !important;
    transition: none !important;
  }
}

/* ---- Penalty burst (wasted Ace) ----
   Red counterparts to the win burst, reusing its geometry and keyframes so
   only the colour differs. */
.burst-ring--penalty {
  border-color: var(--color-alert);
}

.burst-gain--penalty {
  color: var(--color-alert);
}

/* ---- Tempo transfer (Speed Run) ----
   A broken chain's takings fanning from the Tempo box into Points (issue #161).
   The flight itself is animated from JS (Web Animations), because the arc is
   computed from the two boxes' real rects — only the chips' own appearance is
   authored here. The whole thing is pure feedback: the number it carries has
   already landed in the score, so a reduced-motion player loses nothing when
   animations.js declines to fire it. */
.tempo-chip {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: var(--color-accent);
  pointer-events: none;
}

/* The chip that carries the figure. Sized as text rather than as a dot, and
   given the same dark halo the burst's +points uses, so it stays readable
   crossing whatever the board happens to be showing underneath. */
.tempo-chip--lead {
  width: auto;
  height: auto;
  /* No centring transform here: the flight below sets `transform` outright, so
     anything declared would be dropped the moment it starts. The keyframes
     carry the -50%/-50% themselves. */
  margin: 0;
  border-radius: 0;
  background: none;
  white-space: nowrap;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 800;
  color: var(--color-accent);
  text-shadow:
    0 0 4px rgba(0,0,0,0.95),
    0 1px 4px rgba(0,0,0,0.85);
}

/* --------------------------------------------------------------------------
   6. Burn treatments — Ash and Void

   Two, not one, and the split is deliberate. A voluntary burn is the most
   deliberate thing a player does and happens at most three times a game, so it
   gets the largest single-card event on the board (Ash). Everything else that
   sends cards to the pile arrives in fives and elevens — a busted hand, the
   end-game reckoning — and eighteen embers per card there is eight seconds of
   fire, unreadable, and it would hold up the next draw. Those use Void.

   Ash NEVER repeats: voluntary burn only.
   -------------------------------------------------------------------------- */

/* The flying card itself is animated from JS (Web Animations) so the arc can be
   computed from the real source/target rects. Only the impact is authored here. */
.ash-card {
  position: absolute;
  inset: 0;
  pointer-events: none;
  will-change: transform, filter;
}

/* Contact flare — 120px, the moment the card lands on the pile. */
.ash-flare {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 120px;
  height: 120px;
  margin: -60px 0 0 -60px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(
    circle,
    rgba(255, 196, 92, 0.85) 0%,
    rgba(214, 88, 34, 0.55) 38%,
    rgba(120, 30, 12, 0) 72%
  );
  animation: ashFlare 520ms var(--ease-out, ease-out) forwards;
}

@keyframes ashFlare {
  0%   { transform: scale(0.25); opacity: 0; }
  22%  { transform: scale(1);    opacity: 1; }
  100% { transform: scale(1.5);  opacity: 0; }
}

/* Embers — 18 of them, each with its own drift, throw and duration, set from JS
   as custom properties. The tail runs to ~1700ms. */
.ash-ember {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--ember-size, 3px);
  height: var(--ember-size, 3px);
  border-radius: 50%;
  pointer-events: none;
  background: var(--ember-color, #ffb347);
  box-shadow: 0 0 4px var(--ember-color, #ffb347);
  animation: emberRise var(--ember-dur, 1000ms) var(--ease-out, ease-out) var(--ember-delay, 0ms) forwards;
  opacity: 0;
}

@keyframes emberRise {
  0%   { transform: translate(0, 0) scale(1);   opacity: 0; }
  12%  { opacity: 1; }
  100% {
    transform: translate(var(--ember-dx, 0px), var(--ember-dy, -60px)) scale(0.2);
    opacity: 0;
  }
}

/* Void — the quiet counterpart. A card implodes into a thin ring and is gone.
   300ms, no colour, no tail: it has to survive being run eleven times in a row. */
.void-card {
  position: absolute;
  inset: 0;
  pointer-events: none;
  animation: voidImplode 300ms var(--ease-in, ease-in) forwards;
}

@keyframes voidImplode {
  0%   { transform: scale(1)    rotate(0deg);   opacity: 1; filter: none; }
  100% { transform: scale(0.34) rotate(-8deg);  opacity: 0; filter: grayscale(1) brightness(0.5); }
}

.void-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border: 1px solid var(--color-alert);
  border-radius: 50%;
  pointer-events: none;
  animation: voidRing 300ms var(--ease-out, ease-out) forwards;
}

@keyframes voidRing {
  0%   { transform: scale(1);    opacity: 0.7; }
  100% { transform: scale(0.15); opacity: 0; }
}

/* --------------------------------------------------------------------------
   7. Bust — the completed-hand burst, mirrored in red

   Same gesture as a completed 21, in alert red instead of accent gold, which is
   what makes the mode read as authored rather than punishing at random. The
   sequence holds on the label for 460ms before a single card moves, so the
   player reads WHY the hand went before watching it go.
   -------------------------------------------------------------------------- */

.bust-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border: 2px solid var(--color-alert);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0.6;
  animation: bustRing 820ms var(--ease-out, ease-out) forwards;
}

.bust-ring--slow {
  animation-duration: 1060ms;
  animation-delay: 110ms;
}

@keyframes bustRing {
  0%   { transform: scale(0.3); opacity: 0.6; }
  100% { transform: scale(3.4); opacity: 0; }
}

.bust-spark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  border-radius: 50%;
  pointer-events: none;
  background: var(--color-alert);
  box-shadow: 0 0 5px var(--color-alert);
  animation: bustSpark 680ms var(--ease-out, ease-out) forwards;
}

@keyframes bustSpark {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--spark-dx, 0px), var(--spark-dy, 0px)) scale(0.2); opacity: 0; }
}

/* Soft shock — a wide, low-opacity ellipse under the burst, so the whole slot
   registers the hit rather than just its centre. */
.bust-shock {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 130px;
  height: 170px;
  margin: -85px 0 0 -65px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(
    ellipse,
    rgba(192, 57, 43, 0.42) 0%,
    rgba(192, 57, 43, 0.14) 45%,
    rgba(192, 57, 43, 0) 72%
  );
  animation: bustShock 620ms var(--ease-out, ease-out) forwards;
}

@keyframes bustShock {
  0%   { transform: scale(0.5); opacity: 0; }
  25%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(1.2); opacity: 0; }
}

/* The slot recovers WHILE it already accepts cards — a visible recovery rather
   than a dead beat where the player is locked out of a playable slot. */
.animate-slot-recover { animation: slotRecover 420ms var(--ease-out, ease-out); }

@keyframes slotRecover {
  0%   { border-color: var(--color-alert); filter: grayscale(0.8) brightness(0.7); }
  60%  { border-color: var(--color-alert); filter: grayscale(0.2) brightness(0.95); }
  100% { border-color: var(--border-hand-empty, #444); filter: none; }
}

/* --------------------------------------------------------------------------
   8. The reckoning — the end of the game as a tally on the board
   -------------------------------------------------------------------------- */

/* Beat 1: everything that is not a stranded card drops back. */
.reckoning-dim { transition: opacity 400ms var(--ease-out, ease-out); opacity: 0.28; }
.reckoning-stranded { box-shadow: inset 0 0 0 2px var(--color-alert); }

/* Beat 5: the board recedes and the score is CARRIED into the panel — it is not
   reset and re-presented. */
.reckoning-recede {
  transition: transform 520ms var(--ease-out, ease-out),
              opacity 520ms var(--ease-out, ease-out),
              filter 520ms var(--ease-out, ease-out);
  transform: scale(0.94);
  opacity: 0.12;
  filter: blur(3px);
}

/* Beat 2/4: the banner. Letter-spacing tightens as it lands, so it reads as
   arriving rather than fading in. */
.reckoning-banner {
  position: fixed;
  left: 50%;
  top: 12%;
  z-index: 60;
  transform: translateX(-50%);
  padding: var(--space-sm, 8px) var(--space-lg, 20px);
  border: 1px solid var(--color-alert);
  border-radius: var(--border-radius-card, 8px);
  background: var(--bg-table-solid, #10101c);
  color: var(--color-alert);
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  animation: egBannerIn 420ms var(--ease-out, ease-out) forwards;
}

@keyframes egBannerIn {
  0%   { letter-spacing: 0.5em;  opacity: 0; transform: translateX(-50%) translateY(-10px); }
  100% { letter-spacing: 0.24em; opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* Beat 3: each card's own value floats off the pile as it leaves. */
.reckoning-tick {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 61;
  color: var(--color-alert);
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 700;
  pointer-events: none;
  animation: reckoningTick 620ms var(--ease-out, ease-out) forwards;
}

@keyframes reckoningTick {
  0%   { transform: translate(-50%, -50%) scale(0.7); opacity: 0; }
  25%  { transform: translate(-50%, -70%) scale(1);   opacity: 1; }
  100% { transform: translate(-50%, -170%) scale(1);  opacity: 0; }
}

/* Beat 4: the pile's total, one large red figure. */
.reckoning-total {
  font-size: 2rem;
  animation-duration: 900ms;
}

/* Deductions animate on the Points box itself, at the moment they are charged —
   there is no running "points lost" figure to read instead. */
.score-value--losing { color: var(--color-alert); }
.animate-points-hit { animation: pointsHit 340ms var(--ease-out, ease-out); }
.animate-score-box-flash { animation: scoreBoxFlash 620ms var(--ease-out, ease-out); }

@keyframes pointsHit {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.18) translateY(2px); }
  100% { transform: scale(1); }
}

@keyframes scoreBoxFlash {
  0%   { box-shadow: 0 0 0 0 rgba(192, 57, 43, 0.7); }
  100% { box-shadow: 0 0 0 10px rgba(192, 57, 43, 0); }
}

/* Beat 6: the panel's lines drop in, deductions before the final. */
.reckoning-line { opacity: 0; animation: reckoningLineIn 260ms var(--ease-out, ease-out) forwards; }

@keyframes reckoningLineIn {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  /* All burn treatments collapse to: card disappears, pile top swaps, badge
     ticks. The reckoning is skipped outright by the JS, not merely stilled —
     a four-second sequence with no motion is four seconds of nothing. */
  .ash-card,
  .ash-flare,
  .ash-ember,
  .void-card,
  .void-ring,
  .bust-ring,
  .bust-spark,
  .bust-shock,
  .animate-slot-recover,
  .reckoning-banner,
  .reckoning-tick,
  .reckoning-line,
  .animate-points-hit,
  .animate-score-box-flash {
    animation: none !important;
    transition: none !important;
  }
  .reckoning-dim,
  .reckoning-recede { transition: none !important; }
}
