/* =============================================================================
   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;
    }
}
