/* Pre-game-over revive countdown overlay — tunables.
   All visual knobs are CSS variables so polish iterates without touching JS.
   See issue #32. */

:root {
    /* Overlay scrim */
    --revive-scrim-bg: rgba(0, 0, 0, 0.72);
    --revive-scrim-fade-in: 0.25s;
    --revive-z: 9500;

    /* Dark circle containing the countdown */
    --revive-circle-size: 240px;
    --revive-circle-bg: #0b0f14;
    --revive-circle-border: 3px solid rgba(0, 229, 255, 0.65);
    --revive-circle-shadow: 0 0 40px rgba(0, 229, 255, 0.35),
                            inset 0 0 30px rgba(0, 229, 255, 0.15);

    /* Countdown digit */
    --revive-count-color: #ffffff;
    --revive-count-font: "Rubik Mono One", "Kanit", system-ui, sans-serif;
    --revive-count-size: 130px;
    --revive-count-shadow: 0 0 16px rgba(0, 229, 255, 0.8);

    /* Wayfinding banner ("Revive?") */
    --revive-banner-color: #00e5ff;
    --revive-banner-font: "Kanit", "Rubik Mono One", system-ui, sans-serif;
    --revive-banner-size: 28px;
    --revive-banner-weight: 900;
    --revive-banner-letterspacing: 2px;
    --revive-banner-margin-top: 18px;

    /* Action buttons row */
    --revive-buttons-gap: 18px;
    --revive-buttons-margin-top: 26px;

    /* Revive button */
    --revive-btn-bg: linear-gradient(180deg, #4CAF50 0%, #2E7D32 100%);
    --revive-btn-color: #ffffff;
    --revive-btn-border: 2px solid rgba(255, 255, 255, 0.2);
    --revive-btn-padding: 12px 22px;
    --revive-btn-font-size: 16px;
    --revive-btn-font-weight: 800;
    --revive-btn-radius: 12px;

    /* Skip button */
    --skip-btn-bg: rgba(255, 255, 255, 0.12);
    --skip-btn-color: #e7e7e7;
    --skip-btn-border: 2px solid rgba(255, 255, 255, 0.25);
    --skip-btn-padding: 12px 22px;
    --skip-btn-font-size: 16px;
    --skip-btn-font-weight: 800;
    --skip-btn-radius: 12px;
}

#revive-countdown-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--revive-z);
    background: var(--revive-scrim-bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity var(--revive-scrim-fade-in) ease-out;
    pointer-events: auto;
    padding: 20px;
    box-sizing: border-box;
}

#revive-countdown-overlay.visible {
    opacity: 1;
}

#revive-countdown-overlay.hidden {
    display: none;
}

#revive-countdown-overlay .revive-circle {
    width: var(--revive-circle-size);
    height: var(--revive-circle-size);
    border-radius: 50%;
    background: var(--revive-circle-bg);
    border: var(--revive-circle-border);
    box-shadow: var(--revive-circle-shadow);
    display: flex;
    align-items: center;
    justify-content: center;
}

#revive-countdown-overlay .revive-count {
    color: var(--revive-count-color);
    font-family: var(--revive-count-font);
    font-size: var(--revive-count-size);
    line-height: 1;
    text-shadow: var(--revive-count-shadow);
}

#revive-countdown-overlay .revive-banner {
    color: var(--revive-banner-color);
    font-family: var(--revive-banner-font);
    font-size: var(--revive-banner-size);
    font-weight: var(--revive-banner-weight);
    letter-spacing: var(--revive-banner-letterspacing);
    margin-top: var(--revive-banner-margin-top);
    text-align: center;
}

#revive-countdown-overlay .revive-buttons {
    display: flex;
    gap: var(--revive-buttons-gap);
    margin-top: var(--revive-buttons-margin-top);
}

#revive-countdown-overlay .revive-btn {
    background: var(--revive-btn-bg);
    color: var(--revive-btn-color);
    border: var(--revive-btn-border);
    padding: var(--revive-btn-padding);
    font-size: var(--revive-btn-font-size);
    font-weight: var(--revive-btn-font-weight);
    border-radius: var(--revive-btn-radius);
    cursor: pointer;
    font-family: inherit;
}

#revive-countdown-overlay .skip-btn {
    background: var(--skip-btn-bg);
    color: var(--skip-btn-color);
    border: var(--skip-btn-border);
    padding: var(--skip-btn-padding);
    font-size: var(--skip-btn-font-size);
    font-weight: var(--skip-btn-font-weight);
    border-radius: var(--skip-btn-radius);
    cursor: pointer;
    font-family: inherit;
}

/* =========================================================
   Post-reward 4x4 center-clear explosion — issues #33 + #44.
   All visual knobs are CSS variables so polish iterates
   without touching JS.
      #33 shipped the clearing logic, the overlay scaffold, a
           single shockwave ring, generic particles, and a cell
           flash — that primitive set is kept below untouched.
      #44 layers a blast/vaporize composition on top:
           - per-cell scale-pop staggered from the center
           - color-matched particle bursts per cell
           - region-wide white flash at peak impact
           - screen shake on #grid-wrapper
           - prefers-reduced-motion fallback
           - haptic tick (JS side, see revive-countdown.js)
   ========================================================= */

:root {
    /* Explosion overlay container positioned over the center 4x4. */
    --explosion-z: 9400;               /* below revive overlay (9500) */
    --explosion-duration: 620ms;
    --explosion-ease: cubic-bezier(0.22, 1, 0.36, 1);

    /* Expanding shockwave ring. */
    --explosion-ring-color: #ffd65c;
    --explosion-ring-glow: 0 0 32px rgba(255, 214, 92, 0.85),
                           0 0 64px rgba(255, 120, 0, 0.55);
    --explosion-ring-border-width: 5px;
    --explosion-ring-scale-start: 0.35;
    --explosion-ring-scale-end: 1.25;
    --explosion-ring-opacity-start: 1;
    --explosion-ring-opacity-end: 0;

    /* Radiating particles. */
    --explosion-particle-count: 16;    /* reference; count is set in JS */
    --explosion-particle-size: 14px;
    --explosion-particle-distance: 140px;
    --explosion-particle-rotation: 0deg;
    --explosion-particle-color-a: #ff6a00;
    --explosion-particle-color-b: #ffd65c;
    --explosion-particle-color-c: #ff2a68;

    /* Per-cell cleared flash (the 16 cells in the 4x4 region). */
    --center-clear-flash-bg: rgba(255, 214, 92, 0.95);
    --center-clear-flash-duration: 420ms;
    --center-clear-flash-ease: ease-out;

    /* --- #44 polish layer ------------------------------------- */

    /* Per-cell blast: each of the 16 cells scale-pops in a ripple
       outward from the region center. Read by JS (stagger) +
       @keyframes bloxplode-center-blast (scale curve). */
    --center-blast-duration: 160ms;           /* 120-180ms per issue */
    --center-blast-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --center-blast-stagger-ms: 22;            /* JS multiplies by ring index (0..3) */
    --center-blast-peak-scale: 1.35;          /* scale-up peak before collapse */
    --center-blast-end-scale: 0;              /* scale-to-zero on exit */
    --center-blast-rotation: 12deg;           /* subtle spin on exit */
    --center-blast-border-radius: 6px;        /* matches placed-block rad */
    --center-blast-brightness: 1.8;           /* peak glow */

    /* Per-cell color-matched particle burst. Reuses the same
       keyframe as the base ring particles but with its own count
       and lifetime so polish can cap them independently of the
       central ring burst. */
    --center-cell-particle-count: 6;          /* JS reads this; cap for perf */
    --center-cell-particle-size: 6px;
    --center-cell-particle-distance: 46px;    /* radius from the cell center */
    --center-cell-particle-duration: 320ms;   /* 250-400ms per issue */
    --center-cell-particle-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --center-cell-particle-end-scale: 0.2;
    --center-cell-particle-glow: 0 0 6px currentColor;

    /* Region-wide white flash — one-shot, briefly whites-out the
       4x4 footprint at peak impact. */
    --center-flash-region-bg: rgba(255, 255, 255, 0.88);
    --center-flash-region-duration: 70ms;     /* 50-80ms per issue */
    --center-flash-region-ease: ease-out;

    /* Screen shake applied to #grid-wrapper. Amplitude + duration
       both tunable. Set amplitude to 0 to disable without removing
       the class binding. */
    --center-shake-amplitude: 3px;            /* 2-4px per issue */
    --center-shake-duration: 150ms;           /* ~150ms per issue */
    --center-shake-ease: cubic-bezier(0.33, 1, 0.68, 1);

    /* Issue #52 Part B: post-reward blast sound effect.
       Volume of the synthesized "impact + sparkle" cue played at
       blast apex. Read by revive-countdown.js / playBlastSound() —
       set this var to override the JS default (BLAST_SOUND_VOLUME).
       Range: 0 (silent) to 1 (full). Set to 0 to mute the cue
       without disabling the rest of the SFX. The global SFX mute
       (settings panel → SOUND toggle, localStorage
       blox_sfx_enabled) overrides this entirely. */
    --blast-sound-volume: 0.8;

    /* --- #64 four-phase celebratory layered sequence -----------------
       Issue #64 layers an anticipation pulse (Phase 1) before the
       existing detonation (Phase 2 = ring + per-cell blast + flash +
       outward shockwave + screen shake), keeps dissolution (Phase 3 =
       per-cell particles, now with a gravity arc) and adds a warm
       afterglow gradient (Phase 4) over the empty 4x4 footprint after
       the dissolution finishes. Total envelope ~900ms.

       Phase 1 — Anticipation (0 -> ~150ms). Cells STILL OCCUPIED so
       the pulse rides on the existing block colour; class is added
       in JS, removed when the keyframe + per-cell stagger ends. */
    --revive-anticipation-duration: 150ms;     /* Phase 1 keyframe length */
    --revive-anticipation-stagger-ms: 22;      /* JS multiplies by ringIndex (0..2) */
    --revive-anticipation-ease: cubic-bezier(0.33, 1, 0.68, 1);
    --revive-anticipation-scale-peak: 1.08;    /* peak scale during pulse */
    --revive-anticipation-glow-color: rgba(0, 229, 255, 0.85);
    --revive-anticipation-glow-blur: 18px;
    --revive-anticipation-brightness: 1.5;     /* peak brightness boost */

    /* Phase 2 — outward shockwave ring expanding past the 4x4 edges.
       Distinct from .bloxplode-explosion-ring (which is the inward
       radial-burst ring in the existing #33 implementation). */
    --revive-shockwave-color: rgba(255, 220, 100, 0.95);
    --revive-shockwave-glow: 0 0 22px rgba(255, 220, 100, 0.6);
    --revive-shockwave-border-width: 3px;
    --revive-shockwave-duration: 360ms;
    --revive-shockwave-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --revive-shockwave-scale-start: 0.4;
    --revive-shockwave-scale-end: 2.4;
    --revive-shockwave-opacity-start: 1;
    --revive-shockwave-opacity-end: 0;

    /* Phase 2 — per-cell random rotation range (deg). JS picks a value
       in [-range, +range] for each cell so tilt reads as organic. */
    --revive-blast-rotation-range: 8;          /* +/- 8deg per issue spec */

    /* Phase 3 — gravity-arc applied to per-cell particles. The
       midpoint Y offset is what makes particles arc up before falling
       below their final endpoint. 0 = no arc (flat). Negative =
       briefly LIFTS particles before pulling them down. */
    --revive-particle-arc-lift: -14px;         /* peak lift at 50% of keyframe */
    --revive-particle-arc-drop: 10px;          /* drop past dy at 100% */

    /* Phase 4 — warm-afterglow gradient on the now-empty 4x4. Painted
       with mix-blend-mode: screen so empty cells stay visible underneath
       (preserves the #57/PR #59 render-order fix). */
    --revive-afterglow-duration: 250ms;
    /* Inline JS sets this per-element with an ms suffix; the root default
       is parsed by JS via parseInt() so the bare-int value is correct.
       For polish iteration via CSS: append 'ms' (e.g. 500ms) when
       overriding directly in tunables.css — JS reads the integer prefix. */
    --revive-afterglow-delay-ms: 500;          /* starts at end of Phase 3 */
    --revive-afterglow-ease: ease-out;
    --revive-afterglow-color-a: rgba(255, 200, 90, 0.55);  /* gold */
    --revive-afterglow-color-b: rgba(0, 229, 255, 0.30);   /* cyan */
    --revive-afterglow-radius: 14px;

    /* Total envelope read by JS — controls when the rewind resume
       phase un-dims the grid + respawns the tray. Keep in sync with
       the longest visible animation (afterglow start + duration). */
    --revive-blast-envelope-ms: 900;
    --revive-overlay-cleanup-ms: 1000;         /* DOM cleanup buffer */

    /* --- #57 clear-out fade-shrink layer --------------------------
       Per-cell colored overlay that fades + scales out, revealing
       the empty navy cell ALREADY DRAWN beneath. Replaces the old
       .center-clear-flash keyframe (which animated the cell's own
       background to transparent and exposed the dark grid backdrop
       — the "black frame" Ripon saw on real APKs). Polish iterates
       these knobs without touching JS.

       Per-cell stagger reuses --center-blast-stagger-ms (above) so
       this fade-shrink rides the same ripple as the blast. Total
       per-cell envelope = duration + 3 × stagger; with the defaults
       below that's 250 + 3×22 = 316ms, comfortably under the
       500ms reward-flourish budget called out in the issue. */
    --clear-out-duration: 250ms;          /* 200-300ms per issue */
    --clear-out-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --clear-out-scale-start: 1.0;         /* full block at frame 0 */
    --clear-out-scale-end: 0.6;           /* shrunken on exit */
    --clear-out-opacity-start: 1;
    --clear-out-opacity-end: 0;
    --clear-out-border-radius: 6px;       /* matches placed-block radius */
    --clear-out-shadow: inset 3px 3px 0px 0px rgba(255, 255, 255, 0.4),
                        inset -3px -3px 0px 0px rgba(0, 0, 0, 0.2);

    /* Per-cell white flash overlaid on the fade-shrink, gives the
       reward-feel "pop" without depending on the region-wide flash.
       Hits peak brightness early (~25% of the keyframe) then decays
       with the rest of the fade. Set --clear-out-flash-strength to 0
       to disable per-cell flash without removing the layer. */
    --clear-out-flash-color: rgba(255, 255, 255, 0.85);
    --clear-out-flash-strength: 1.6;      /* filter brightness peak */
}

#bloxplode-explosion-overlay {
    position: absolute;
    pointer-events: none;
    z-index: var(--explosion-z);
    display: block;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

#bloxplode-explosion-overlay.hidden {
    display: none;
}

.bloxplode-explosion-ring {
    position: absolute;
    border-radius: 50%;
    border: var(--explosion-ring-border-width) solid var(--explosion-ring-color);
    box-shadow: var(--explosion-ring-glow);
    transform: translate(-50%, -50%) scale(var(--explosion-ring-scale-start));
    opacity: var(--explosion-ring-opacity-start);
    animation: bloxplode-explosion-ring var(--explosion-duration) var(--explosion-ease) forwards;
}

@keyframes bloxplode-explosion-ring {
    0%   {
        transform: translate(-50%, -50%) scale(var(--explosion-ring-scale-start));
        opacity: var(--explosion-ring-opacity-start);
    }
    100% {
        transform: translate(-50%, -50%) scale(var(--explosion-ring-scale-end));
        opacity: var(--explosion-ring-opacity-end);
    }
}

.bloxplode-explosion-particle {
    position: absolute;
    width: var(--explosion-particle-size);
    height: var(--explosion-particle-size);
    border-radius: 50%;
    background: var(--explosion-particle-color-a);
    box-shadow: 0 0 10px var(--explosion-particle-color-b);
    transform: translate(-50%, -50%) rotate(var(--explosion-particle-rotation));
    opacity: 1;
    animation: bloxplode-explosion-particle var(--explosion-duration) var(--explosion-ease) forwards;
}

@keyframes bloxplode-explosion-particle {
    0%   { transform: translate(-50%, -50%) translate(0, 0) scale(1); opacity: 1; }
    100% { transform: translate(-50%, -50%) translate(var(--dx, 0), var(--dy, 0)) scale(0.35); opacity: 0; }
}

/* Legacy .cell.center-clear-flash keyframe (pre-#57) used to animate
   background-color to `transparent` with `forwards`, exposing the dark
   grid-wrapper gradient underneath mid-animation AND leaving the cell
   transparent after the animation — the "black frame" Ripon saw on
   real APKs. The class is no longer added from JS (see #57 in
   revive-countdown.js / clearCenterCellsDom) but we keep the rule here
   as a defensive end-state: if any code path ever re-adds the class,
   the keyframe now lands on `var(--bg-cell)` (the empty navy the game
   already paints by default), so the black-frame regression cannot
   resurface from that code path. */
.cell.center-clear-flash {
    animation: bloxplode-center-clear-flash var(--center-clear-flash-duration) var(--center-clear-flash-ease) forwards;
}

@keyframes bloxplode-center-clear-flash {
    0%   { background-color: var(--center-clear-flash-bg); filter: brightness(1.6); }
    100% { background-color: var(--bg-cell, #262A35); filter: brightness(1); }
}

/* =========================================================
   #57 clear-out fade-shrink overlay.

   Spawned as a CHILD element inside each cell being cleared
   (revive-countdown.js / clearCenterCellsDom). The cell itself
   has already been stripped of `.occupied` + inline
   --block-color, so its painted state is the empty navy
   var(--bg-cell). This overlay sits ON TOP of that empty paint,
   carries the last colored-block appearance, and fades/shrinks
   out over --clear-out-duration to reveal the empty cell
   beneath. Zero frames of transparency on the cell itself.
   ========================================================= */
.bloxplode-clear-out {
    position: absolute;
    inset: 0;
    background-color: var(--clear-out-color, var(--explosion-ring-color));
    border-radius: var(--clear-out-border-radius);
    box-shadow: var(--clear-out-shadow);
    pointer-events: none;
    /* Sit ABOVE the empty cell paint but BELOW the explosion overlay
       (which is z-index 9400 on #bloxplode-explosion-overlay). Layered
       higher than .cell.occupied's z-index: 2 so it hides the moment
       the cell reverts to empty. */
    z-index: 10;
    transform: scale(var(--clear-out-scale-start));
    opacity: var(--clear-out-opacity-start);
    will-change: transform, opacity, filter;
    animation: bloxplode-clear-out var(--clear-out-duration) var(--clear-out-ease) forwards;
    animation-delay: var(--clear-out-delay-ms, 0ms);
}

@keyframes bloxplode-clear-out {
    0%   {
        transform: scale(var(--clear-out-scale-start));
        opacity: var(--clear-out-opacity-start);
        filter: brightness(1);
        background-color: var(--clear-out-color, var(--explosion-ring-color));
    }
    /* Brief reward-feel white flash overlaid on the fade, peaks at
       25% of the keyframe then decays with the rest of the shrink. */
    25%  {
        transform: scale(calc(var(--clear-out-scale-start) * 1.05));
        opacity: 1;
        filter: brightness(var(--clear-out-flash-strength));
        background-color: var(--clear-out-flash-color);
    }
    100% {
        transform: scale(var(--clear-out-scale-end));
        opacity: var(--clear-out-opacity-end);
        filter: brightness(1);
        background-color: var(--clear-out-color, var(--explosion-ring-color));
    }
}

/* =========================================================
   #44 polish layer — blast / vaporize composition.
   Elements below are spawned by revive-countdown.js on the
   reward path. Nothing here renders unless JS adds the
   matching class / node.
   ========================================================= */

/* Per-cell blast sprite — absolutely positioned over a single
   cell of the 4x4 region. JS sets --delay-ms per cell (ripple
   from center) and --color (the tile's original --block-color
   before it was cleared). */
.bloxplode-center-blast {
    position: absolute;
    width: var(--blast-w, 44px);
    height: var(--blast-h, 44px);
    background: var(--color, var(--explosion-ring-color));
    border-radius: var(--center-blast-border-radius);
    box-shadow: 0 0 12px var(--color, rgba(255, 214, 92, 0.7));
    transform: translate(-50%, -50%) scale(0.85);
    opacity: 0;
    pointer-events: none;
    z-index: 1;
    animation: bloxplode-center-blast var(--center-blast-duration) var(--center-blast-ease) forwards;
    animation-delay: var(--delay-ms, 0ms);
    will-change: transform, opacity, filter;
}

@keyframes bloxplode-center-blast {
    /* #64: per-cell --blast-rotation-deg (set by JS, range +/- of
       --revive-blast-rotation-range) overrides the shared
       --center-blast-rotation default so each cell tilts on its own
       random angle. The fallback to --center-blast-rotation keeps
       the pre-#64 behaviour intact when JS does not set the per-cell
       var (e.g. reduced-motion path). */
    0%   {
        transform: translate(-50%, -50%) scale(0.85) rotate(0deg);
        opacity: 1;
        filter: brightness(1);
    }
    45%  {
        transform: translate(-50%, -50%)
                   scale(var(--center-blast-peak-scale))
                   rotate(calc(var(--blast-rotation-deg, var(--center-blast-rotation)) * 0.5));
        opacity: 1;
        filter: brightness(var(--center-blast-brightness));
    }
    100% {
        transform: translate(-50%, -50%)
                   scale(var(--center-blast-end-scale))
                   rotate(var(--blast-rotation-deg, var(--center-blast-rotation)));
        opacity: 0;
        filter: brightness(1);
    }
}

/* Per-cell particle burst — small color-matched dots radiating
   from the cell center. JS sets --dx/--dy per particle, --color
   from the tile, and animation-delay to align with the cell's
   blast. */
.bloxplode-center-cell-particle {
    position: absolute;
    width: var(--center-cell-particle-size);
    height: var(--center-cell-particle-size);
    border-radius: 50%;
    background: var(--color, var(--explosion-particle-color-b));
    box-shadow: var(--center-cell-particle-glow);
    color: var(--color, var(--explosion-particle-color-b));
    transform: translate(-50%, -50%);
    opacity: 1;
    pointer-events: none;
    z-index: 2;
    animation: bloxplode-center-cell-particle var(--center-cell-particle-duration) var(--center-cell-particle-ease) forwards;
    animation-delay: var(--delay-ms, 0ms);
    will-change: transform, opacity;
}

/* #64 Phase 3 — gravity arc.
   Midpoint at 50% lifts the particle by --revive-particle-arc-lift
   (negative Y, so up) before it settles past its (dx, dy) endpoint by
   --revive-particle-arc-drop (positive Y, so down past the linear end).
   Reads as a small arc rather than a flat radial line. The endpoint
   (--dx, --dy) is unchanged so the existing JS spawn logic does not
   need to recompute distance. */
@keyframes bloxplode-center-cell-particle {
    0%   {
        transform: translate(-50%, -50%) translate(0, 0) scale(1);
        opacity: 1;
    }
    50%  {
        transform: translate(-50%, -50%)
                   translate(calc(var(--dx, 0px) * 0.55),
                             calc(var(--dy, 0px) * 0.55 + var(--revive-particle-arc-lift, 0px)))
                   scale(0.85);
        opacity: 1;
    }
    100% {
        transform: translate(-50%, -50%)
                   translate(var(--dx, 0px),
                             calc(var(--dy, 0px) + var(--revive-particle-arc-drop, 0px)))
                   scale(var(--center-cell-particle-end-scale));
        opacity: 0;
    }
}

/* Region-wide white flash at peak impact — covers the exact
   4x4 footprint computed by JS (left/top/width/height set
   inline from the same geometry the ring uses). */
.bloxplode-center-flash-region {
    position: absolute;
    background: var(--center-flash-region-bg);
    border-radius: 10px;
    opacity: 0;
    pointer-events: none;
    z-index: 3;
    animation: bloxplode-center-flash-region var(--center-flash-region-duration) var(--center-flash-region-ease) forwards;
    mix-blend-mode: screen;
    will-change: opacity;
}

@keyframes bloxplode-center-flash-region {
    0%   { opacity: 0; }
    40%  { opacity: 1; }
    100% { opacity: 0; }
}

/* Screen shake — additive to any transforms already on
   #grid-wrapper (scale from initSmartScaling stays intact
   because we only translate). JS adds + removes the class. */
#grid-wrapper.bloxplode-center-shake {
    animation: bloxplode-center-shake var(--center-shake-duration) var(--center-shake-ease);
}

@keyframes bloxplode-center-shake {
    0%   { transform: translate(0, 0); }
    20%  { transform: translate(calc(var(--center-shake-amplitude) * -1), calc(var(--center-shake-amplitude) * 0.5)); }
    40%  { transform: translate(var(--center-shake-amplitude), calc(var(--center-shake-amplitude) * -0.5)); }
    60%  { transform: translate(calc(var(--center-shake-amplitude) * -0.6), calc(var(--center-shake-amplitude) * 0.8)); }
    80%  { transform: translate(calc(var(--center-shake-amplitude) * 0.6), calc(var(--center-shake-amplitude) * -0.3)); }
    100% { transform: translate(0, 0); }
}

/* =========================================================
   #64 Phase 1 — Anticipation pulse on the still-occupied
   cells. Class is added by JS (applyAnticipationPulse) BEFORE
   the cells are stripped of .occupied / --block-color, so the
   pulse rides on the existing block colour. Per-cell delay is
   set inline (--revive-anticipation-delay-ms) for the center-
   outward stagger.

   The keyframe scales the cell up to --revive-anticipation-
   scale-peak and adds an outline glow + brightness boost. We
   do NOT change the cell's background — only filter + box-
   shadow + transform — so the underlying #57 render-order is
   preserved untouched.
   ========================================================= */
.cell.bloxplode-revive-anticipation {
    animation: bloxplode-revive-anticipation
               var(--revive-anticipation-duration)
               var(--revive-anticipation-ease) forwards;
    animation-delay: var(--revive-anticipation-delay-ms, 0ms);
    will-change: transform, box-shadow, filter;
    /* z-index lifts the pulsing block ABOVE neighbouring grid cells
       so the box-shadow glow does not get clipped by adjacent cells. */
    z-index: 5;
}

@keyframes bloxplode-revive-anticipation {
    /* Box-shadow includes the same inset bevel as .cell.occupied (so the
       3D block look is preserved through the pulse) PLUS an outer glow
       at peak. The inset bevel is repeated at 0% / 100% so the block
       does not visibly swap shadows mid-keyframe. */
    0%   {
        transform: scale(1);
        box-shadow: inset 3px 3px 0px 0px rgba(255, 255, 255, 0.4),
                    inset -3px -3px 0px 0px rgba(0, 0, 0, 0.2);
        filter: brightness(1);
    }
    60%  {
        transform: scale(var(--revive-anticipation-scale-peak));
        box-shadow: inset 3px 3px 0px 0px rgba(255, 255, 255, 0.5),
                    inset -3px -3px 0px 0px rgba(0, 0, 0, 0.2),
                    0 0 var(--revive-anticipation-glow-blur)
                          var(--revive-anticipation-glow-color);
        filter: brightness(var(--revive-anticipation-brightness));
    }
    100% {
        transform: scale(1);
        box-shadow: inset 3px 3px 0px 0px rgba(255, 255, 255, 0.4),
                    inset -3px -3px 0px 0px rgba(0, 0, 0, 0.2);
        filter: brightness(1);
    }
}

/* =========================================================
   #64 Phase 2 — Outward shockwave ring expanding past the
   4x4 edges. Distinct from .bloxplode-explosion-ring (which
   is the inward radial-burst ring from #33 that stays inside
   the region). This one travels OUT past the region.
   ========================================================= */
.bloxplode-revive-shockwave {
    position: absolute;
    border-radius: 50%;
    border: var(--revive-shockwave-border-width) solid var(--revive-shockwave-color);
    box-shadow: var(--revive-shockwave-glow);
    transform: translate(-50%, -50%) scale(var(--revive-shockwave-scale-start));
    opacity: var(--revive-shockwave-opacity-start);
    pointer-events: none;
    z-index: 4;
    animation: bloxplode-revive-shockwave
               var(--revive-shockwave-duration)
               var(--revive-shockwave-ease) forwards;
    will-change: transform, opacity;
}

@keyframes bloxplode-revive-shockwave {
    0%   {
        transform: translate(-50%, -50%) scale(var(--revive-shockwave-scale-start));
        opacity: var(--revive-shockwave-opacity-start);
    }
    100% {
        transform: translate(-50%, -50%) scale(var(--revive-shockwave-scale-end));
        opacity: var(--revive-shockwave-opacity-end);
    }
}

/* =========================================================
   #64 Phase 4 — Warm afterglow gradient on the now-empty
   4x4 footprint. Painted with mix-blend-mode: screen so the
   empty navy cells underneath remain visible the whole time
   the gradient is fading — preserves the #57/PR #59 render-
   order fix.

   Animation-delay (set via --revive-afterglow-delay-ms inline)
   pushes the start to the end of Phase 3 so the warm wash
   does not occlude the dissolution particles.
   ========================================================= */
.bloxplode-revive-afterglow {
    position: absolute;
    border-radius: var(--revive-afterglow-radius);
    background: radial-gradient(circle at 50% 50%,
                                var(--revive-afterglow-color-a) 0%,
                                var(--revive-afterglow-color-b) 60%,
                                rgba(0, 0, 0, 0) 100%);
    opacity: 0;
    pointer-events: none;
    z-index: 2;
    mix-blend-mode: screen;
    animation: bloxplode-revive-afterglow
               var(--revive-afterglow-duration)
               var(--revive-afterglow-ease) forwards;
    animation-delay: var(--revive-afterglow-delay-ms, 500ms);
    will-change: opacity;
}

@keyframes bloxplode-revive-afterglow {
    0%   { opacity: 0; }
    25%  { opacity: 1; }
    100% { opacity: 0; }
}

/* prefers-reduced-motion: keep the existing minimal fade-out
   path (the cell flash + ring) but strip the heavy layers:
   per-cell blast scales are flattened to a fade, screen shake
   is disabled entirely, particles are hidden (JS also short-
   circuits particle spawning for perf when reduced-motion is
   active). */
@media (prefers-reduced-motion: reduce) {
    .bloxplode-center-blast {
        animation: bloxplode-center-blast-reduced var(--center-blast-duration) linear forwards;
    }

    @keyframes bloxplode-center-blast-reduced {
        0%   { opacity: 1; transform: translate(-50%, -50%) scale(1); filter: brightness(1); }
        100% { opacity: 0; transform: translate(-50%, -50%) scale(1); filter: brightness(1); }
    }

    .bloxplode-center-cell-particle {
        display: none;
    }

    #grid-wrapper.bloxplode-center-shake {
        animation: none;
    }

    .bloxplode-center-flash-region {
        animation-duration: calc(var(--center-flash-region-duration) * 0.5);
    }

    /* #57 clear-out: keep the colored overlay so the empty-cell
       reveal is still gated by an opaque layer (no black-frame
       regression under reduced-motion either) but flatten the
       scale/flash flourish to a plain fade. */
    .bloxplode-clear-out {
        animation: bloxplode-clear-out-reduced var(--clear-out-duration) linear forwards;
    }

    @keyframes bloxplode-clear-out-reduced {
        0%   { opacity: 1; transform: scale(1); filter: brightness(1); }
        100% { opacity: 0; transform: scale(1); filter: brightness(1); }
    }

    /* #64 Phase 1 anticipation: keep the brightness boost (a non-
       motion cue) but flatten the scale flourish so the cell does
       not pump in size. Reduced-motion users still see the energy
       gather, just without the bounce. */
    .cell.bloxplode-revive-anticipation {
        animation: bloxplode-revive-anticipation-reduced
                   var(--revive-anticipation-duration) linear forwards;
    }

    @keyframes bloxplode-revive-anticipation-reduced {
        0%   { transform: scale(1); filter: brightness(1); }
        50%  { transform: scale(1); filter: brightness(var(--revive-anticipation-brightness)); }
        100% { transform: scale(1); filter: brightness(1); }
    }

    /* #64 Phase 2 shockwave + Phase 4 afterglow are pure motion-
       flourish layers — strip them under reduced-motion. JS also
       short-circuits the spawn so we do not attach the DOM nodes
       at all under reduced-motion (saves cleanup churn). */
    .bloxplode-revive-shockwave {
        display: none;
    }

    .bloxplode-revive-afterglow {
        display: none;
    }

    /* #64 Phase 3 particle gravity arc is gated off because
       .bloxplode-center-cell-particle is already hidden above. */
}
