/* =====================================================================
   CALLER EFFECTS — one visual per thing the friend does to you
   Every effect gets a motion of its own so the banner text is a caption for
   something you already saw happen, not the only evidence it happened.
   ===================================================================== */

/* ---------- screenshot: shutter flash, then a pulsing answer ---------- */
.fx-shot .pkey.candidate,.fx-shot .qcard.candidate2{animation:shotPulse .5s ease-in-out infinite alternate}
@keyframes shotPulse{from{filter:brightness(1)}to{filter:brightness(1.28)}}
.fx-shutter{position:absolute;inset:0;z-index:12;pointer-events:none;background:#fff;
  animation:shutter .42s ease-out forwards}
@keyframes shutter{0%{opacity:0}12%{opacity:.92}100%{opacity:0}}

/* ---------- freeze: frost creeps over the modal ---------- */
.fx-frost{position:absolute;inset:0;z-index:11;pointer-events:none;border-radius:inherit;
  background:
    radial-gradient(60% 40% at 12% 0%,rgba(190,235,255,.55),transparent 70%),
    radial-gradient(50% 45% at 88% 100%,rgba(160,220,255,.5),transparent 70%),
    linear-gradient(180deg,rgba(215,245,255,.28),rgba(180,230,255,.14));
  animation:frostIn .5s ease-out, frostOut .6s ease-in forwards}
@keyframes frostIn{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:none}}
@keyframes frostOut{to{opacity:0}}
.fx-frost .fx-frost-cap{position:absolute;left:50%;top:14%;transform:translateX(-50%);
  font-size:2.6rem;filter:drop-shadow(0 3px 10px rgba(0,60,90,.4));animation:softPulse 1.4s ease-in-out infinite}

/* ---------- fire: the modal burns, faster ---------- */
.fx-fire .lock-modal{animation:fireShake .3s infinite;
  box-shadow:0 0 0 2px #ff5a2e, 0 18px 50px rgba(255,90,46,.42)}
@keyframes fireShake{0%,100%{transform:translateX(0)}25%{transform:translateX(-2px)}75%{transform:translateX(2px)}}
.fx-fire .beatbar .seg:not(.spent){background:linear-gradient(135deg,#ffb43d,#ff3d1f)!important}
/* fire should be visible on the surfaces too, not just the timer */
.fx-fire .qcard,.fx-fire .pkey:not(.black){background:linear-gradient(180deg,#ffd9c2,#ffb289)!important;
  color:#4a1c06!important}
.fx-fire .pkey.black{background:#5a2a12!important}
.lock-surface{position:relative}
.fx-fire .lock-surface::after{content:"🔥🔥🔥🔥🔥";position:absolute;left:0;right:0;bottom:-6px;
  text-align:center;font-size:1.5rem;letter-spacing:14px;pointer-events:none;animation:flicker .45s infinite alternate}
@keyframes flicker{from{opacity:.55;transform:translateY(0)}to{opacity:1;transform:translateY(-3px)}}
/* embers drifting up the modal */
.fx-embers{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:10;border-radius:inherit}
.fx-embers i{position:absolute;bottom:-10px;width:6px;height:6px;border-radius:50%;
  background:radial-gradient(circle,#ffd27a,#ff5a2e);opacity:0;animation:emberRise linear infinite}
@keyframes emberRise{
  0%{opacity:0;transform:translateY(0) scale(.6)}
  15%{opacity:.9}
  100%{opacity:0;transform:translateY(-320px) translateX(var(--drift,0px)) scale(.2)}}

/* ---------- middle finger ---------- */
.fx-mfinger .lock-body{position:relative}
.fx-flash{position:absolute;inset:0;display:grid;place-items:center;z-index:9;border-radius:0 0 20px 20px;
  background:repeating-linear-gradient(45deg,#ffd93d,#ffd93d 22px,#f4b41a 22px,#f4b41a 44px);
  animation:fxPop .18s var(--ease-pop)}
.fx-flash .fx-inner{display:grid;place-items:center;gap:6px;text-align:center}
.fx-flash .fx-glyph{font-size:5.2rem;line-height:1;animation:fxWobble .28s ease-in-out infinite alternate}
.fx-flash .fx-cap{font-weight:950;font-size:.9rem;letter-spacing:.12em;text-transform:uppercase;color:#3a2a02}
.fx-flash.out{animation:fxOut .28s ease forwards}
@keyframes fxOut{to{opacity:0;transform:scale(1.08)}}
@keyframes fxPop{from{transform:scale(.6);opacity:0}to{transform:scale(1);opacity:1}}
@keyframes fxWobble{from{transform:rotate(-7deg)}to{transform:rotate(7deg)}}
/* cards flipping to the finger and back */
.qcard.fx-flip{animation:cardFlip .9s ease;background:linear-gradient(160deg,#ffe36b,#f4b41a)!important;
  border-color:#caa30d!important}
@keyframes cardFlip{
  0%{transform:rotateY(0)} 18%{transform:rotateY(90deg)} 36%{transform:rotateY(0)}
  70%{transform:rotateY(0)} 85%{transform:rotateY(90deg)} 100%{transform:rotateY(0)}}
.fx-finger{font-size:1.6rem;line-height:1}

/* ---------- magic shuffle ----------
   The FLIP pass (measure → reorder → invert → play) sets --dx/--dy per card and
   the transition below carries them home. The riffle class is the wind-up:
   everything lifts, fans out and lands. */
.fx-shuffling{animation:shuffleTable .55s ease-in-out}
@keyframes shuffleTable{0%,100%{transform:none}30%{transform:rotate(-1.1deg)}65%{transform:rotate(1.1deg)}}
.qcard.fx-slide{transition:transform .52s var(--ease-out);will-change:transform;z-index:3}
.qcard.fx-riffle{animation:riffle .52s var(--ease-out)}
@keyframes riffle{
  0%{box-shadow:0 0 0 rgba(0,0,0,0)}
  40%{box-shadow:0 16px 30px rgba(0,0,0,.34);filter:brightness(1.12)}
  100%{box-shadow:0 0 0 rgba(0,0,0,0)}}
/* piano keys get a travelling wave instead — they cannot swap places on screen */
.piano.fx-shuffle-wave .pkey{animation:keyWave .6s var(--ease-out) backwards}
@keyframes keyWave{
  0%{transform:translateY(-14px) rotate(-4deg);opacity:.15;filter:blur(2px)}
  60%{transform:translateY(3px) rotate(1deg);opacity:1;filter:blur(0)}
  100%{transform:none}}
/* the shuffle caption card that flies across the surface */
.fx-shuffle-cap{position:absolute;left:50%;top:44%;transform:translate(-50%,-50%);z-index:12;
  pointer-events:none;font-weight:950;font-size:1.05rem;letter-spacing:.06em;text-transform:uppercase;
  padding:9px 16px;border-radius:99px;color:#2a1f05;
  background:linear-gradient(135deg,var(--goldBright),var(--gold));
  box-shadow:0 12px 30px rgba(0,0,0,.35);animation:shuffleCap 1.1s var(--ease-pop) forwards}
@keyframes shuffleCap{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.5) rotate(-14deg)}
  22%{opacity:1;transform:translate(-50%,-50%) scale(1.05) rotate(-3deg)}
  72%{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(-3deg)}
  100%{opacity:0;transform:translate(-50%,-90%) scale(.9) rotate(-3deg)}}

/* ---------- lock one component: the caller slams a note in ---------- */
.fx-lockin{animation:lockSlam .55s var(--ease-pop);position:relative;z-index:4}
@keyframes lockSlam{
  0%{transform:scale(1.5) translateY(-16px);filter:brightness(1.9)}
  55%{transform:scale(.95);filter:brightness(1.2)}
  100%{transform:none;filter:none}}
.fx-lockin::after{content:"";position:absolute;inset:-4px;border-radius:inherit;pointer-events:none;
  border:3px solid var(--gold);animation:ringPing .6s var(--ease-out) forwards}

/* ---------- remove one wrong: struck out, then collapsed ---------- */
.fx-struck{animation:struck .6s ease forwards;position:relative}
@keyframes struck{
  0%{transform:none;opacity:1}
  35%{transform:rotate(-3deg) scale(1.03)}
  100%{transform:scale(.9);opacity:.18}}
.fx-struck::before{content:"";position:absolute;left:6%;right:6%;top:50%;height:3px;border-radius:2px;
  background:var(--bad);transform-origin:left center;animation:strikeThrough .3s ease forwards;z-index:3}
@keyframes strikeThrough{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* ---------- show two likely ---------- */
.fx-narrow{animation:narrowPulse .9s ease-out 2}
@keyframes narrowPulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--gold) 70%,transparent)}
  100%{box-shadow:0 0 0 12px transparent}}

/* ---------- enharmonic vandalism: labels glitch as they are rewritten ---------- */
.fx-glitch{animation:glitchShift .42s steps(2,end) 3}
@keyframes glitchShift{
  0%{transform:none;filter:none}
  25%{transform:translateX(-2px) skewX(-6deg);filter:hue-rotate(70deg)}
  50%{transform:translateX(2px) skewX(5deg);filter:hue-rotate(-60deg)}
  75%{transform:translateX(-1px);filter:none}
  100%{transform:none}}

/* ---------- bureaucracy: a rubber stamp lands on the surface ---------- */
.fx-stamp{position:absolute;left:50%;top:40%;z-index:12;pointer-events:none;
  transform:translate(-50%,-50%) rotate(-14deg);
  border:4px double var(--bad);color:var(--bad);border-radius:8px;padding:8px 18px;
  font-weight:950;letter-spacing:.14em;text-transform:uppercase;font-size:1.05rem;
  background:color-mix(in srgb,var(--panel) 70%,transparent);
  animation:stampDown .9s var(--ease-pop) forwards}
@keyframes stampDown{
  0%{opacity:0;transform:translate(-50%,-50%) rotate(-14deg) scale(3)}
  35%{opacity:1;transform:translate(-50%,-50%) rotate(-14deg) scale(1)}
  75%{opacity:1}
  100%{opacity:0;transform:translate(-50%,-50%) rotate(-14deg) scale(1)}}

/* ---------- font emergency: the letters lose their nerve ---------- */
.fx-fontpanic{animation:fontPanic .5s ease-in-out 2}
@keyframes fontPanic{0%,100%{letter-spacing:normal;transform:none}
  30%{letter-spacing:.22em;transform:skewX(-8deg)}70%{letter-spacing:-.04em;transform:skewX(6deg)}}

/* ---------- bad reception: the timer breaks up ---------- */
.fx-static{animation:staticBreak 1s steps(3,end)}
@keyframes staticBreak{
  0%,100%{opacity:1;transform:none}
  12%{opacity:.25;transform:translateX(-3px)}
  28%{opacity:.85;transform:translateX(4px)}
  44%{opacity:.15}
  60%{opacity:.9;transform:translateX(-2px)}
  76%{opacity:.3}}

/* ---------- low battery: the piano flickers as it browns out ---------- */
.piano.fx-brownout{animation:brownout 1.4s ease-in-out}
@keyframes brownout{
  0%{filter:none}18%{filter:brightness(.25)}24%{filter:brightness(.9)}
  40%{filter:brightness(.2)}48%{filter:brightness(.8)}
  100%{filter:brightness(.55) saturate(.5)}}
.fx-battery{position:absolute;right:10px;top:10px;z-index:12;pointer-events:none;
  font-size:.72rem;font-weight:950;color:var(--bad);letter-spacing:.08em;
  animation:softPulse 1s ease-in-out infinite}

/* ---------- autocorrect note ---------- */
.fx-autocorrect{text-align:center;margin-top:6px;font-weight:800;color:var(--bad);
  animation:riseIn .24s var(--ease-out)}

/* ---------- extra beats: a little "+2" flies off the bar ---------- */
.fx-beatgain{position:absolute;right:0;top:-18px;font-weight:950;font-size:.9rem;color:var(--good);
  pointer-events:none;animation:beatGain .9s var(--ease-out) forwards}
@keyframes beatGain{0%{opacity:0;transform:translateY(6px) scale(.7)}
  25%{opacity:1;transform:translateY(0) scale(1.1)}
  100%{opacity:0;transform:translateY(-22px) scale(1)}}
.beatbar{position:relative}
