/* Beyond the Last Hearth — card animation library, styles.
   Pairs with anim.js. Rules: animate only transform-family properties
   (transform, translate, rotate, scale) and opacity; nothing here animates
   layout, box-shadow, filter or colour. Look: hearth-oil — warm amber hearth
   light rises from the player's side (bottom), cold blue-grey fog sits over
   the wild (top). Warm means safe. See design/style-bible/animation.md. */

:root {
  /* easing: weighted, hand-placed motion; overshoot only when a card lands */
  --anim-out: cubic-bezier(.22, 1, .36, 1);
  --anim-in: cubic-bezier(.55, 0, .75, .2);
  --anim-land: cubic-bezier(.3, 1.35, .55, 1);
  --anim-std: cubic-bezier(.4, 0, .2, 1);
  /* hearth-oil palette (design/art/STYLE-SHEET.md) */
  --anim-amber: #e8a868;
  --anim-amber-deep: #be865b;
  --anim-bone: #efe4cc;
  --anim-fog: #b7cad1;
  --anim-slate: #9fb0b8;
  --anim-ash: #716e67;
  --anim-soot: #0c0d0f;
  /* layer order; the game may override */
  --anim-z-light: 15;
  --anim-z-fx: 75;
  --anim-z-veil: 79;
}

/* ---------- layers (created by anim.js) ---------- */
.anim-light, .anim-fx, .anim-veil { position: fixed; inset: 0; pointer-events: none; contain: strict; }
.anim-light { z-index: var(--anim-z-light); overflow: hidden; }
.anim-fx { z-index: var(--anim-z-fx); overflow: hidden; }
.anim-veil { z-index: var(--anim-z-veil); opacity: 0; visibility: hidden;
  background: radial-gradient(120% 80% at 50% 100%, #1c1612 0%, var(--anim-soot) 70%); }
.anim-fx > *, .anim-light > * { position: absolute; left: 0; top: 0; }

/* hearth light: a low warm glow at the bottom that flickers on two irregular
   rhythms, cold fog at the top, a vignette, a drifting fog band. Every mood
   only changes these layers' opacity (a transition, so the compositor does it). */
.anim-light > i { display: block; transition: opacity 1.2s var(--anim-std); will-change: opacity; }
.anim-warm, .anim-warm2 { left: -20%; right: -20%; top: auto; bottom: -30vh; height: 85vh; width: auto; transform-origin: 50% 100%;
  background: radial-gradient(50% 50% at 50% 60%, rgba(232, 150, 80, .30) 0%, rgba(190, 110, 60, .14) 45%, rgba(190, 110, 60, 0) 72%); }
.anim-warm2 { bottom: -22vh; height: 60vh; left: 10%; right: 10%;
  background: radial-gradient(50% 50% at 50% 65%, rgba(255, 190, 120, .20) 0%, rgba(255, 170, 90, 0) 70%); }
.anim-cold { left: 0; right: 0; width: auto; height: 55vh;
  background: linear-gradient(to bottom, rgba(70, 92, 104, .34), rgba(70, 92, 104, 0)); }
.anim-vig { inset: 0; width: auto; height: auto;
  background: radial-gradient(140% 100% at 50% 60%, rgba(12, 13, 15, 0) 50%, rgba(12, 13, 15, .62) 100%); }
.anim-fogband { top: 6vh; left: -60%; width: 220%; height: 34vh;
  background: radial-gradient(40% 50% at 30% 50%, rgba(183, 202, 209, .11), rgba(183, 202, 209, 0) 70%),
              radial-gradient(35% 45% at 72% 40%, rgba(183, 202, 209, .09), rgba(183, 202, 209, 0) 70%); }

.anim-idle .anim-warm { animation: anim-flick-a 3.7s steps(1, end) infinite, anim-hearth-breath 6.3s ease-in-out infinite; }
.anim-idle .anim-warm2 { animation: anim-flick-b 2.3s linear infinite; }
.anim-idle .anim-fogband { animation: anim-fog-drift 38s linear infinite alternate; }
/* firelight: irregular, never a sine. Two layers with prime-ish periods so the sum does not visibly repeat. */
@keyframes anim-flick-a {
  0% { opacity: 1; } 9% { opacity: .86; } 17% { opacity: .97; } 26% { opacity: .9; } 38% { opacity: 1; }
  47% { opacity: .82; } 55% { opacity: .95; } 68% { opacity: .88; } 79% { opacity: 1; } 91% { opacity: .91; }
}
@keyframes anim-flick-b {
  0%, 100% { opacity: .7; } 13% { opacity: 1; } 29% { opacity: .55; } 41% { opacity: .9; } 57% { opacity: .65; } 72% { opacity: 1; } 86% { opacity: .6; }
}
@keyframes anim-hearth-breath { 50% { transform: scale(1.05, 1.08); } }
@keyframes anim-fog-drift { to { transform: translateX(22%); } }

/* moods: refuge (hearth), wild, ruin, depths (torch only), fight, boss, death */
.anim-light.mood-refuge .anim-warm { opacity: 1; }   .anim-light.mood-refuge .anim-cold { opacity: .15; } .anim-light.mood-refuge .anim-vig { opacity: .55; } .anim-light.mood-refuge .anim-fogband { opacity: 0; }
.anim-light.mood-wild .anim-warm { opacity: .45; }   .anim-light.mood-wild .anim-cold { opacity: .8; }    .anim-light.mood-wild .anim-vig { opacity: .7; }    .anim-light.mood-wild .anim-fogband { opacity: 1; }
.anim-light.mood-ruin .anim-warm { opacity: .4; }    .anim-light.mood-ruin .anim-cold { opacity: .6; }    .anim-light.mood-ruin .anim-vig { opacity: .85; }   .anim-light.mood-ruin .anim-fogband { opacity: .5; }
.anim-light.mood-depths .anim-warm { opacity: .35; } .anim-light.mood-depths .anim-cold { opacity: .25; } .anim-light.mood-depths .anim-vig { opacity: 1; }    .anim-light.mood-depths .anim-fogband { opacity: 0; }
.anim-light.mood-fight .anim-warm { opacity: .5; }   .anim-light.mood-fight .anim-cold { opacity: .85; }  .anim-light.mood-fight .anim-vig { opacity: .85; }  .anim-light.mood-fight .anim-fogband { opacity: .35; }
.anim-light.mood-boss .anim-warm { opacity: .35; }   .anim-light.mood-boss .anim-cold { opacity: 1; }     .anim-light.mood-boss .anim-vig { opacity: 1; }     .anim-light.mood-boss .anim-fogband { opacity: .6; }
.anim-light.mood-death .anim-warm { opacity: 0; }    .anim-light.mood-death .anim-cold { opacity: 1; }    .anim-light.mood-death .anim-vig { opacity: 1; }    .anim-light.mood-death .anim-fogband { opacity: .8; }
.anim-light.mood-depths .anim-warm2, .anim-light.mood-death .anim-warm2 { opacity: .4; }
.anim-light.mood-death .anim-warm2 { opacity: 0; }

/* embers: amber sparks rising from the hearth; anim.js drives them with WAAPI */
.anim-ember { width: 3px; height: 3px; border-radius: 50%; top: auto; bottom: -6px; opacity: 0;
  background: radial-gradient(circle, #fff1c8 0%, #f0a050 45%, rgba(240, 140, 60, 0) 72%); will-change: transform, opacity; }
.anim-ember.big { width: 5px; height: 5px; }

/* ---------- cards inside an attached root (.anim-on) ---------- */
.anim-on .card { transition: transform .18s ease, opacity .3s ease, filter .3s ease, translate .16s var(--anim-out), scale .16s var(--anim-out); }
/* per-card firelight: a warm wash from below that flickers; opacity only */
.anim-on.anim-cardlight .card::after { content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit; z-index: 3;
  background: linear-gradient(to top, rgba(240, 160, 90, .26) 0%, rgba(240, 160, 90, .08) 38%, rgba(240, 160, 90, 0) 62%);
  opacity: .55; transition: opacity .18s ease; }
.anim-idle .anim-on.anim-cardlight .card::after { animation: anim-cardflick 2.9s steps(1, end) infinite; }
.anim-idle .anim-on.anim-cardlight .card:nth-child(3n+2)::after { animation-duration: 3.4s; animation-delay: -1.1s; }
.anim-idle .anim-on.anim-cardlight .card:nth-child(3n)::after { animation-duration: 2.5s; animation-delay: -.6s; }
@keyframes anim-cardflick { 0% { opacity: .55; } 14% { opacity: .42; } 31% { opacity: .62; } 46% { opacity: .5; } 63% { opacity: .66; } 81% { opacity: .46; } }
/* creatures breathe: the painting inside the frame swells slowly; the frame stays still */
.anim-idle .anim-on.anim-breathe .card.t-creature .card-art > img,
.anim-idle .anim-on.anim-breathe .card.t-creature .card-art > svg,
.anim-idle .anim-on.anim-breathe .card.anim-alive .card-art > img { transform-origin: 50% 92%; animation: anim-breathe 4.6s ease-in-out infinite; }
.anim-idle .anim-on.anim-breathe .card.t-creature:nth-child(2n) .card-art > img { animation-duration: 5.3s; animation-delay: -2s; }
.anim-idle .anim-on.anim-breathe .card.t-creature:nth-child(3n) .card-art > img { animation-duration: 3.9s; animation-delay: -1s; }
@keyframes anim-breathe { 0%, 100% { transform: scale(1); } 45% { transform: scale(1.028) translateY(-.6%); } }

/* touch: only a drag source ([data-drag], as in core.js) blocks panning; every other card lets a swipe
   scroll the view (v17 note: the portrait room view did not scroll because cards covered it) */
.anim-on .card:not([data-drag]) { touch-action: pan-x pan-y; }

/* hover (mouse only): the card lifts toward the fire and catches more light */
@media (hover: hover) and (pointer: fine) {
  .anim-on .card:hover:not(.sel):not(.held) { transform: none; translate: 0 -6px; scale: 1.03; }
  .anim-on.anim-cardlight .card:hover::after { opacity: 1; animation-play-state: paused; }
}
/* press (touch and mouse): a short give under the finger */
.anim-on .card:active { scale: .97; transition-duration: .08s; }
/* drag targets: no paint-heavy outline pulse; the target warms instead */
.anim-on .card.can, .anim-on .card.anim-can { animation: none; }
.anim-on.anim-cardlight .card.can::after, .anim-on.anim-cardlight .card.anim-can::after { opacity: .95; animation: none; }
.anim-on .card.over, .anim-on .card.anim-over { transform: none; scale: 1.06; }
/* a foe that threatens: an opacity glow instead of the old box-shadow pulse */
.anim-on .card.anim-threat::before { content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none; border-radius: inherit;
  box-shadow: inset 0 0 18px rgba(183, 202, 209, .55); opacity: 0; animation: anim-threat 1.6s ease-in-out infinite; }
@keyframes anim-threat { 50% { opacity: 1; } }

/* the base demo's paint-heavy card animations (box-shadow pulses, outline blinks, CSS deal)
   are replaced inside an attached root: Anim.deal deals, .foe/.glow use opacity-only glows */
.anim-on .card.deal, .anim-on .card.landed, .anim-on .card.shake { animation: none; }
.anim-on .card.foe, .anim-on .card.glow { animation: none; }
.anim-on .card.foe::before, .anim-on .card.glow::before { content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none; border-radius: inherit; opacity: 0; }
.anim-on .card.foe::before { box-shadow: inset 0 0 18px rgba(183, 202, 209, .5); }
.anim-on .card.glow::before { box-shadow: inset 0 0 22px rgba(232, 168, 104, .6); }
.anim-idle .anim-on .card.foe::before, .anim-idle .anim-on .card.glow::before { animation: anim-threat 2.2s ease-in-out infinite; }
.anim-reduced .anim-on .card.foe::before, .anim-reduced .anim-on .card.glow::before { opacity: .6; }

/* the base stage's rain layer animates background-position forever, also while invisible:
   120 style recalcs and a board-sized repaint per second on a phone (measured, animation.md).
   Inside an attached root it runs only while it rains. */
body:not(.rain) .board.anim-on::after, body:not(.rain) .anim-on .board::after { animation: none; }

/* ---------- pieces anim.js puts into a card for a moment ---------- */
.anim-flash, .anim-glow, .anim-shade, .anim-back { position: absolute; inset: 0; pointer-events: none; border-radius: inherit; z-index: 6; opacity: 0; }
.anim-flash { background: radial-gradient(70% 60% at var(--fx, 50%) var(--fy, 40%), rgba(239, 228, 204, .95), rgba(239, 228, 204, .25) 60%, rgba(239, 228, 204, 0) 90%); }
.anim-shade { background: radial-gradient(80% 70% at 50% 45%, rgba(12, 13, 15, 0) 20%, rgba(12, 13, 15, .85) 100%); }
.anim-glow { background: radial-gradient(70% 60% at 50% 75%, rgba(240, 170, 100, .75), rgba(240, 170, 100, .18) 60%, rgba(240, 170, 100, 0) 95%); }
/* card back: worn umber leather with a hearth mark; painted in CSS so it needs no asset */
.anim-back { opacity: 1; z-index: 8;
  background:
    radial-gradient(18% 13% at 50% 47%, rgba(232, 168, 104, .9), rgba(190, 134, 91, .35) 55%, rgba(190, 134, 91, 0) 100%),
    radial-gradient(34% 26% at 50% 50%, rgba(0, 0, 0, 0) 60%, rgba(190, 134, 91, .45) 63%, rgba(0, 0, 0, 0) 67%),
    repeating-linear-gradient(115deg, rgba(255, 255, 255, .025) 0 2px, rgba(0, 0, 0, 0) 2px 7px),
    radial-gradient(120% 90% at 50% 40%, #3a2a1e 0%, #22180f 60%, #140d08 100%);
  box-shadow: inset 0 0 0 3px #2a1d13, inset 0 0 0 4px rgba(190, 134, 91, .5); }

/* ---------- things in the fx layer ---------- */
.anim-proxy { margin: 0 !important; transition: none !important; will-change: transform, opacity; }
.anim-proxy::after { animation: none !important; }
.anim-ghost { box-shadow: 0 26px 48px rgba(0, 0, 0, .72), 0 0 0 1px rgba(232, 168, 104, .25) !important; }
.anim-char { position: absolute; left: -2px; right: -2px; top: 0; height: 100%; z-index: 9; pointer-events: none;
  background: linear-gradient(to bottom, rgba(255, 210, 140, .95) 0, rgba(240, 130, 50, .85) 3px, #2a1d16 10px, #1c1e1f 30%, #0c0d0f 100%); }
.anim-num { font: 700 26px/1 var(--title, Georgia, serif); color: var(--anim-bone); white-space: nowrap; letter-spacing: .02em;
  text-shadow: 0 0 2px #000, 0 2px 0 var(--anim-soot), 0 0 12px rgba(0, 0, 0, .9); will-change: transform, opacity; }
.anim-num.k-crit { font-size: 36px; color: #fff4dc; text-shadow: 0 0 2px #000, 0 2px 0 var(--anim-soot), 0 0 16px rgba(232, 168, 104, .8); }
.anim-num.k-heal, .anim-num.k-gain { color: var(--anim-amber); }
.anim-num.k-block { color: var(--anim-slate); }
.anim-num.k-ward { color: #f3d6a6; }
.anim-num.k-mana { color: var(--anim-fog); }
.anim-num.k-miss { color: var(--anim-ash); font-style: italic; font-size: 20px; }
.anim-spark { width: 4px; height: 4px; border-radius: 50%; will-change: transform, opacity;
  background: radial-gradient(circle, #fff6dc 0%, #f0b060 50%, rgba(240, 160, 80, 0) 75%); }
.anim-spark.ash { background: radial-gradient(circle, #b9b2a6 0%, rgba(113, 110, 103, .8) 50%, rgba(113, 110, 103, 0) 75%); }
.anim-spark.mote { width: 6px; height: 6px; background: radial-gradient(circle, rgba(255, 220, 160, .95) 0%, rgba(232, 168, 104, .5) 45%, rgba(232, 168, 104, 0) 75%); }
.anim-spark.splinter { width: 7px; height: 2px; border-radius: 1px; background: linear-gradient(90deg, rgba(239, 228, 204, 0), #efe4cc); }
.anim-streak { height: 3px; border-radius: 2px; will-change: transform, opacity;
  background: linear-gradient(90deg, rgba(255, 240, 210, 0), #fff2d8 40%, #f0c080 60%, rgba(240, 192, 128, 0)); }
/* the ward is the player's own spell, so it is warm (art.md pillar 1: warm means yours) */
.anim-ring { border-radius: 50%; will-change: transform, opacity;
  background: radial-gradient(circle, rgba(232, 168, 104, 0) 52%, rgba(232, 168, 104, .5) 63%, rgba(255, 226, 180, .9) 67%, rgba(232, 168, 104, .2) 71%, rgba(232, 168, 104, 0) 76%),
              repeating-conic-gradient(rgba(255, 226, 180, .35) 0 4deg, rgba(255, 226, 180, 0) 4deg 22deg);
  -webkit-mask: radial-gradient(circle, transparent 58%, #000 60%, #000 74%, transparent 76%); mask: radial-gradient(circle, transparent 58%, #000 60%, #000 74%, transparent 76%); }
.anim-ring.plain { -webkit-mask: none; mask: none; background: radial-gradient(circle, rgba(232, 168, 104, 0) 55%, rgba(255, 226, 180, .7) 66%, rgba(232, 168, 104, 0) 76%); }
.anim-press { border: 2px solid var(--anim-amber); border-radius: 12px; will-change: transform, opacity; box-shadow: 0 0 10px rgba(232, 168, 104, .45); }
.anim-puff { border-radius: 50%; will-change: transform, opacity;
  background: radial-gradient(closest-side, rgba(70, 80, 88, .65), rgba(40, 46, 52, .3) 60%, rgba(12, 13, 15, 0)); }

/* ---------- pause and reduced motion ---------- */
.anim-paused .anim-light *, .anim-paused .anim-on .card::after, .anim-paused .anim-on .card::before, .anim-paused .anim-on .card .card-art > * { animation-play-state: paused !important; }
/* anim.js also reads prefers-reduced-motion and swaps every animation for a short fade; this keeps CSS idle life still too */
.anim-reduced .anim-light *, .anim-reduced .anim-on .card::after, .anim-reduced .anim-on .card .card-art > *, .anim-reduced .anim-on .card::before { animation: none !important; }
.anim-reduced .anim-on .card { transition-duration: .01s; }
.anim-reduced .anim-ember { display: none; }
/* low quality: anim.js makes at most 5 embers. Never hide a running ember with display: none: a hidden
   element cannot run on the compositor and costs a main-thread style pass every frame (measured, animation.md). */
.anim-low .anim-fogband { display: none; }
.anim-low .anim-on.anim-cardlight .card::after { animation: none; }
