/* =============================================================================
   fx.css — shared "juice" keyframes + .fx-* classes for the FX effects layer.

   Rules (docs/how-to-make-effects.md):
     - Motion animates transform/opacity ONLY (GPU-cheap, no layout/paint).
     - Flashes animate color/opacity — safe under reduced motion.
     - Mount-driven classes (fx-flash / fx-enter / loops) auto-play on innerHTML insert, so
       they're gated by html[data-fx] (FXSettings mirrors the setting there) and the OS
       prefers-reduced-motion media query.
   Palette falls back to the literal --ui-* hexes so this file works even if loaded first.
   ============================================================================= */

/* ── Floating "+N" popup (rise + fade), spawned on #fx-layer ───────────────── */
@keyframes fx-float-up {
    0%   { transform: translate(-50%, 0) scale(0.8); opacity: 0; }
    15%  { transform: translate(-50%, -6px) scale(1.1); opacity: 1; }
    100% { transform: translate(-50%, -48px) scale(1); opacity: 0; }
}
.fx-float {
    position: absolute;
    pointer-events: none;
    font-weight: 700;
    font-size: 0.95rem;
    white-space: nowrap;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
    will-change: transform, opacity;
    color: var(--ui-teal, #4ecdc4);
    animation: fx-float-up 900ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.fx-float--gain { color: var(--ui-teal, #4ecdc4); }
.fx-float--gold { color: var(--ui-gold, #ffd700); }
.fx-float--loss { color: var(--ui-danger, #ff6b6b); }
.fx-float--big  { font-size: 1.35rem; }

/* ── Mount-driven flash on value change (via FXDiff), color only ───────────── */
@keyframes fx-flash-bg {
    0%   { background-color: rgba(255, 215, 0, 0.40); }
    100% { background-color: transparent; }
}
@keyframes fx-flash-bg-loss {
    0%   { background-color: rgba(255, 107, 107, 0.40); }
    100% { background-color: transparent; }
}
.fx-flash        { animation: fx-flash-bg 700ms ease-out; }
.fx-flash--loss  { animation: fx-flash-bg-loss 700ms ease-out; }

/* ── Enter pop for intentional appear animations (guard with a diff!) ──────── */
@keyframes fx-show {
    from { opacity: 0; transform: translateY(8px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.fx-enter { animation: fx-show 400ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }

/* ── Cheap glow pulse (animate an opacity, never box-shadow) ───────────────── */
.fx-glow-host { position: relative; }
.fx-glow-host::after {
    content: '';
    position: absolute;
    inset: -4px;
    border-radius: inherit;
    box-shadow: 0 0 12px 4px rgba(78, 205, 196, 0.9);   /* --ui-teal glow, static */
    opacity: 0;
    pointer-events: none;
}
@keyframes fx-glow-pulse { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
.fx-pulse.fx-glow-host::after { animation: fx-glow-pulse 600ms ease-out; }
/* Bare .fx-pulse (no glow host) → a compact scale bump. */
@keyframes fx-pulse-scale { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
.fx-pulse:not(.fx-glow-host) {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    animation: fx-pulse-scale 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* ── Shake (CSS variant; JS shake() uses WAAPI for re-rendered targets) ────── */
@keyframes fx-shake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-6px); } 40% { transform: translateX(5px); }
    60% { transform: translateX(-3px); } 80% { transform: translateX(2px); }
}
.fx-shake { animation: fx-shake 400ms cubic-bezier(.36, .07, .19, .97); }

/* ── SVG-resident helpers (idle "aliveness" baked into template strings) ───── */
.fx-breathe {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    animation: fx-breathe-kf 2.4s ease-in-out infinite;
}
@keyframes fx-breathe-kf {
    0%, 100% { transform: scale(1);    opacity: .9; }
    50%      { transform: scale(1.08); opacity: 1;  }
}
.fx-spin {
    transform-box: fill-box;            /* origin = element's own bbox, not the SVG viewport */
    transform-origin: 50% 50%;
    animation: fx-spin-kf 6s linear infinite;
}
@keyframes fx-spin-kf { to { transform: rotate(360deg); } }
@keyframes fx-draw { to { stroke-dashoffset: 0; } }

/* ── Epic realm breakthrough: heavenly-tribulation lightning (T3, one-shot on #fx-layer) ──
   Bolt flicker — opacity only. Spawned solely by FX.lightningStrike(), which already gates on
   FXSettings.motionOK() (so nothing mounts under reduced/off); the off rule below is belt-and-braces. */
@keyframes fx-strike {
    0%   { opacity: 0; }
    8%   { opacity: 1; }
    18%  { opacity: 0.45; }
    28%  { opacity: 1; }
    50%  { opacity: 0.8; }
    100% { opacity: 0; }
}

/* =============================================================================
   In-game effects level (mirrored to <html data-fx="...">) + OS reduced motion.
   'off'     → kill every fx class outright.
   'reduced' → keep color flashes; neutralize transform-driven motion & loops.
   ============================================================================= */
html[data-fx="off"] .fx-float,
html[data-fx="off"] .fx-flash,
html[data-fx="off"] .fx-flash--loss,
html[data-fx="off"] .fx-enter,
html[data-fx="off"] .fx-pulse,
html[data-fx="off"] .fx-shake,
html[data-fx="off"] .fx-breathe,
html[data-fx="off"] .fx-spin,
html[data-fx="off"] #fx-layer * {
    animation: none !important;
}
html[data-fx="off"] #fx-layer { display: none !important; }

html[data-fx="reduced"] .fx-float,
html[data-fx="reduced"] .fx-enter,
html[data-fx="reduced"] .fx-pulse,
html[data-fx="reduced"] .fx-shake,
html[data-fx="reduced"] .fx-breathe,
html[data-fx="reduced"] .fx-spin {
    animation: none !important;
    transform: none !important;
}
/* Color flashes stay legible under reduced motion (they convey state, not just decoration). */

@media (prefers-reduced-motion: reduce) {
    .fx-float, .fx-enter, .fx-pulse, .fx-shake, .fx-breathe, .fx-spin {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transform: none !important;
    }
}

/* ── Flying "card played" ghost, spawned on #fx-layer (movement via WAAPI) ──── */
.fx-flycard {
    position: absolute;
    left: 0; top: 0;
    width: 74px;
    border-radius: 10px;
    overflow: hidden;
    pointer-events: none;
    background: #0b1220;
    border: 2px solid var(--el, #64748b);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.55), 0 0 14px color-mix(in srgb, var(--el, #64748b) 60%, transparent);
    will-change: transform, opacity;
}
.fx-flycard img {
    display: block;
    width: 100%;
    height: 74px;
    object-fit: cover;
}
.fx-flycard-name {
    display: block;
    padding: 2px 4px 3px;
    font-size: 9px;
    font-weight: 700;
    text-align: center;
    color: #e5e7eb;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Soul Kingdom absorption cinematic (Hell Map king slain → soul flies to its throne) ──
   Spawned on #fx-layer only when FX.motionOK() (so reduced/off never mount these). Movement
   is WAAPI transform/opacity; the dim ring collapse is SMIL; the slot landing is a class. */
.sk-cine-orb {
    position: absolute;
    left: 0; top: 0;
    pointer-events: none;
    will-change: transform, opacity;
    filter: drop-shadow(0 0 14px rgba(179, 157, 219, 0.9)) drop-shadow(0 0 34px rgba(155, 89, 182, 0.65));
}
.sk-cine-dim {
    position: fixed;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(circle at 50% 52%, rgba(24, 8, 32, 0.12), rgba(5, 3, 10, 0.86));
    will-change: opacity;
}
.sk-cine-hero {
    position: absolute;
    left: 0; top: 0;
    width: 190px;
    pointer-events: none;
    will-change: transform, opacity;
    filter: drop-shadow(0 0 18px rgba(199, 139, 255, 0.7));
}
.sk-cine-hero svg { width: 100%; height: auto; display: block; }
.sk-cine-rings { position: fixed; inset: 0; pointer-events: none; }
@keyframes sk-arrive-glow {
    0%   { filter: drop-shadow(0 0 0 rgba(179, 157, 219, 0));   transform: translate(-50%, -50%) scale(0.55); }
    45%  { filter: drop-shadow(0 0 20px rgba(232, 218, 239, 1)); transform: translate(-50%, -50%) scale(1.2); }
    100% { filter: drop-shadow(0 0 6px rgba(179, 157, 219, 0.4)); transform: translate(-50%, -50%) scale(1); }
}
.sk-node.sk-node-arrive { animation: sk-arrive-glow 1100ms cubic-bezier(0.34, 1.56, 0.64, 1); }

html[data-fx="off"] .sk-cine-orb,
html[data-fx="off"] .sk-cine-dim,
html[data-fx="off"] .sk-cine-hero,
html[data-fx="off"] .sk-cine-rings,
html[data-fx="off"] .sk-node-arrive { animation: none !important; }
