/* ========= 动画与特效 ========= */
@keyframes titleIn{from{opacity:0;transform:translateY(-40px) rotateX(70deg)}to{opacity:1}}
@keyframes modalIn{from{opacity:0;transform:scale(.88) translateY(14px)}to{opacity:1}}
@keyframes logIn{from{opacity:0;transform:translateX(12px)}to{opacity:1}}
@keyframes dealIn{from{opacity:0;transform:translateY(90px) rotate(14deg) scale(.7)}to{opacity:1}}
@keyframes playIn{from{opacity:0;transform:scale(.4) rotate(-12deg)}to{opacity:1}}
@keyframes chipIn{from{opacity:0;transform:translateX(16px) scale(.7)}to{opacity:1}}
@keyframes timerBar{from{transform:scaleX(1)}to{transform:scaleX(0)}}
@keyframes skGlow{0%,100%{box-shadow:0 0 0 rgba(232,200,106,0)}50%{box-shadow:0 0 10px rgba(232,200,106,.7)}}
@keyframes targetPulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.25)}}

#fxLayer{position:fixed;inset:0;pointer-events:none;z-index:600;overflow:hidden}
#bannerLayer{position:fixed;inset:0;pointer-events:none;z-index:700;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px}

/* ---------- 飞行的牌 ---------- */
.fly-card{position:fixed;z-index:610;pointer-events:none;
  transition:transform var(--t,.45s) cubic-bezier(.3,.9,.3,1), opacity var(--t,.45s)}

/* ---------- 技能横幅 ---------- */
.sk-banner{position:relative;display:flex;align-items:center;gap:10px;
  padding:9px 30px;border-radius:5px;white-space:nowrap;
  background:linear-gradient(90deg,transparent,rgba(12,9,5,.92) 14%,rgba(12,9,5,.92) 86%,transparent);
  animation:bannerLife 1.25s cubic-bezier(.16,1,.3,1) forwards}
.sk-banner .sb-name{font-size:34px;font-weight:900;letter-spacing:.24em;
  background:linear-gradient(180deg,#fff6d6,#e0b754 45%,#9c7422);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 2px 6px rgba(0,0,0,.9))}
.sk-banner .sb-who{font-size:14px;color:#c3b391;letter-spacing:.18em}
.sk-banner::before,.sk-banner::after{content:"";position:absolute;left:8%;right:8%;height:1px;
  background:linear-gradient(90deg,transparent,var(--gold),transparent)}
.sk-banner::before{top:0} .sk-banner::after{bottom:0}
@keyframes bannerLife{
  0%{opacity:0;transform:scale(.6) translateY(10px);letter-spacing:.6em}
  16%{opacity:1;transform:scale(1.06)}
  26%{transform:scale(1)}
  74%{opacity:1;transform:scale(1)}
  100%{opacity:0;transform:scale(1.14)}
}

/* ---------- 伤害 ---------- */
.dmg-num{position:fixed;z-index:620;font-size:42px;font-weight:900;color:#ff5a45;
  text-shadow:0 0 16px rgba(255,60,40,.9),0 3px 0 #5c0f08,0 6px 14px #000;pointer-events:none;
  animation:dmgPop 1.05s cubic-bezier(.2,1.3,.4,1) forwards}
.dmg-num.heal{color:#5fe07f;text-shadow:0 0 16px rgba(60,240,120,.8),0 3px 0 #0d4a1c,0 6px 14px #000}
.dmg-num.fire{color:#ff9a2e;text-shadow:0 0 20px rgba(255,140,20,.95),0 3px 0 #6b3405}
.dmg-num.thunder{color:#79d6ff;text-shadow:0 0 20px rgba(80,200,255,.95),0 3px 0 #0b3d5c}
@keyframes dmgPop{
  0%{opacity:0;transform:translate(-50%,0) scale(.3)}
  22%{opacity:1;transform:translate(-50%,-26px) scale(1.25)}
  36%{transform:translate(-50%,-22px) scale(1)}
  100%{opacity:0;transform:translate(-50%,-72px) scale(.92)}
}
.shake{animation:shake .42s cubic-bezier(.36,.07,.19,.97)}
@keyframes shake{
  10%,90%{transform:translate(-50%,-50%) scale(var(--sc,1)) translateX(-3px)}
  20%,80%{transform:translate(-50%,-50%) scale(var(--sc,1)) translateX(5px)}
  30%,50%,70%{transform:translate(-50%,-50%) scale(var(--sc,1)) translateX(-8px)}
  40%,60%{transform:translate(-50%,-50%) scale(var(--sc,1)) translateX(8px)}
}
.shake-self{animation:shakeS .42s cubic-bezier(.36,.07,.19,.97)}
@keyframes shakeS{
  10%,90%{transform:translateX(-3px)} 20%,80%{transform:translateX(5px)}
  30%,50%,70%{transform:translateX(-8px)} 40%,60%{transform:translateX(8px)}
}
.hit-flash{position:absolute;inset:0;background:radial-gradient(circle,rgba(255,60,40,.6),transparent 70%);
  animation:flashOut .45s ease-out forwards;pointer-events:none;z-index:5}
.hit-flash.heal{background:radial-gradient(circle,rgba(60,230,110,.55),transparent 70%)}
@keyframes flashOut{from{opacity:1}to{opacity:0}}

/* ---------- 杀 / 闪 特效 ---------- */
.slash{position:fixed;z-index:615;width:200px;height:200px;pointer-events:none;
  transform:translate(-50%,-50%);}
.slash i{position:absolute;left:50%;top:50%;width:230px;height:9px;border-radius:5px;
  transform-origin:50% 50%;
  background:linear-gradient(90deg,transparent,#fff 18%,#ff6a42 50%,#fff 82%,transparent);
  filter:drop-shadow(0 0 10px rgba(255,120,60,.95));
  animation:slashGo .38s cubic-bezier(.2,.9,.3,1) forwards}
@keyframes slashGo{
  0%{opacity:0;transform:translate(-50%,-50%) rotate(-42deg) scaleX(0)}
  30%{opacity:1;transform:translate(-50%,-50%) rotate(-42deg) scaleX(1.12)}
  100%{opacity:0;transform:translate(-50%,-50%) rotate(-42deg) scaleX(1.3)}
}
.dodge-ring{position:fixed;z-index:615;width:120px;height:120px;border-radius:50%;
  transform:translate(-50%,-50%);border:3px solid #7fd8ff;pointer-events:none;
  box-shadow:0 0 24px rgba(120,210,255,.9),inset 0 0 20px rgba(120,210,255,.6);
  animation:ringGo .55s cubic-bezier(.2,.9,.3,1) forwards}
@keyframes ringGo{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.2)}
  35%{opacity:1;transform:translate(-50%,-50%) scale(1.05)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(1.8)}
}

/* ---------- 光束（指向目标） ---------- */
.beam{position:fixed;z-index:605;height:4px;transform-origin:0 50%;pointer-events:none;border-radius:2px;
  background:linear-gradient(90deg,rgba(255,220,140,0),rgba(255,210,110,.95),rgba(255,140,60,0));
  filter:drop-shadow(0 0 8px rgba(255,190,90,.85));
  animation:beamGo .5s cubic-bezier(.3,.9,.3,1) forwards}
.beam.hostile{background:linear-gradient(90deg,rgba(255,110,90,0),rgba(255,90,60,.95),rgba(255,40,20,0));
  filter:drop-shadow(0 0 9px rgba(255,90,60,.9))}
.beam.friendly{background:linear-gradient(90deg,rgba(120,255,170,0),rgba(90,235,140,.95),rgba(40,200,100,0));
  filter:drop-shadow(0 0 9px rgba(90,235,140,.9))}
@keyframes beamGo{0%{opacity:0;transform:scaleX(0)}30%{opacity:1;transform:scaleX(1)}
  100%{opacity:0;transform:scaleX(1)}}

/* ---------- 判定牌 ---------- */
.judge-card{position:fixed;z-index:630;transform:translate(-50%,-50%);pointer-events:none}
.judge-card.flip{animation:judgeLife 1.5s cubic-bezier(.2,1.2,.4,1) forwards}
@keyframes judgeLife{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.3) rotateY(180deg)}
  20%{opacity:1;transform:translate(-50%,-50%) scale(1.3) rotateY(0)}
  32%{transform:translate(-50%,-50%) scale(1.15)}
  78%{opacity:1;transform:translate(-50%,-50%) scale(1.15)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(.85) translateY(-40px)}
}
/* 亮出判定牌后停在台面上等改判，结算时才淡出 */
.judge-card.pending{animation:judgeFlipIn .6s cubic-bezier(.2,1.2,.4,1) forwards}
@keyframes judgeFlipIn{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.3) rotateY(180deg)}
  70%{opacity:1;transform:translate(-50%,-50%) scale(1.3) rotateY(0)}
  100%{opacity:1;transform:translate(-50%,-50%) scale(1.15)}
}
.judge-card.settle{animation:judgeSettle 1.5s ease-out forwards}
@keyframes judgeSettle{
  0%{opacity:1;transform:translate(-50%,-50%) scale(1.15)}
  72%{opacity:1;transform:translate(-50%,-50%) scale(1.15)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(.85) translateY(-40px)}
}
/* 弹窗里已经摆了同一张判定牌，台面上那张先让位，免得两张叠在一起 */
:root body:has(#modal:not(.hidden)) .judge-card.pending{visibility:hidden}
.judge-label{position:absolute;left:50%;top:-30px;transform:translateX(-50%);white-space:nowrap;
  font-size:13px;letter-spacing:.2em;color:#f2ddab;text-shadow:0 2px 6px #000,0 0 12px #000}
.judge-card.pending .judge-label{animation:judgeWait 1.4s ease-in-out .6s infinite}
@keyframes judgeWait{0%,100%{opacity:.62}50%{opacity:1}}
.judge-slot.swap{animation:judgeReplace .42s cubic-bezier(.2,1.2,.4,1)}
@keyframes judgeReplace{
  0%{opacity:0;transform:rotateY(90deg) scale(.82)}
  100%{opacity:1;transform:none}
}
.judge-result{position:absolute;left:50%;bottom:-32px;transform:translateX(-50%);
  font-size:20px;letter-spacing:.24em;white-space:nowrap;font-weight:800;
  text-shadow:0 2px 6px #000;animation:modalIn .3s .35s backwards}
.judge-result.good{color:#5fe07f;text-shadow:0 0 14px rgba(70,230,120,.8),0 2px 6px #000}
.judge-result.bad{color:#ff6a52;text-shadow:0 0 14px rgba(255,80,60,.8),0 2px 6px #000}

/* ---------- 弃牌展示 ---------- */
.discard-flash{position:fixed;left:50%;top:38%;z-index:620;transform:translate(-50%,-50%);
  pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:9px;
  animation:discardFlashOut .38s ease-in forwards}
@keyframes discardFlashOut{
  to{opacity:0;transform:translate(-50%,calc(-50% + 38px)) scale(.84)}
}
.df-label{font-size:14px;letter-spacing:.2em;color:#ffcb92;white-space:nowrap;
  text-shadow:0 2px 6px #000,0 0 12px #000}
.df-row{display:flex;gap:10px;align-items:center;justify-content:center}
/* 跟出牌展示一样放大到能看清牌名的尺寸 */
.df-row .card.mini{width:90px;height:132px;--badge-num:13px;--badge-suit:10.5px;--badge-w:19px}
.df-row .cf-banner{font-size:17px!important}
.df-row .card{box-shadow:0 10px 26px #000a,0 0 0 1px #ffd79a40;
  animation:discardPop .32s cubic-bezier(.2,1.3,.4,1) backwards;animation-delay:calc(var(--i,0)*60ms)}
@keyframes discardPop{
  from{opacity:0;transform:translateY(-16px) scale(.7) rotate(-7deg)}
}
.df-more{font-size:15px;letter-spacing:.06em;color:#ffcb92;text-shadow:0 2px 6px #000}

/* ---------- 全屏效果 ---------- */
.screen-fx{position:absolute;inset:0;pointer-events:none}
.fx-fire{background:radial-gradient(ellipse at 50% 60%,rgba(255,130,30,.42),transparent 62%);
  animation:flashOut .9s ease-out forwards}
.fx-thunder{background:linear-gradient(0deg,rgba(120,200,255,.35),rgba(255,255,255,.55));
  animation:thunderFlash .6s ease-out forwards}
@keyframes thunderFlash{0%{opacity:0}8%{opacity:1}18%{opacity:.15}28%{opacity:.9}100%{opacity:0}}
.fx-aoe{background:radial-gradient(circle at 50% 50%,transparent 20%,rgba(255,180,80,.3) 42%,transparent 62%);
  animation:aoeWave .8s ease-out forwards}
@keyframes aoeWave{0%{opacity:0;transform:scale(.2)}40%{opacity:1}100%{opacity:0;transform:scale(1.5)}}

/* ---------- 濒死 / 死亡 ---------- */
.dying-vignette{position:fixed;inset:0;z-index:590;pointer-events:none;
  box-shadow:inset 0 0 160px 40px rgba(180,20,10,.55);
  animation:dyingPulse 1.1s ease-in-out infinite}
@keyframes dyingPulse{0%,100%{opacity:.45}50%{opacity:1}}
.death-mark{position:fixed;z-index:640;transform:translate(-50%,-50%);font-size:56px;font-weight:900;
  color:#e04b3c;letter-spacing:.2em;pointer-events:none;
  text-shadow:0 0 30px rgba(255,50,30,.9),0 4px 0 #4a0d07;
  animation:deathIn 1.5s cubic-bezier(.2,1.2,.4,1) forwards}
@keyframes deathIn{
  0%{opacity:0;transform:translate(-50%,-50%) scale(2.4) rotate(-14deg)}
  24%{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(0)}
  76%{opacity:1}
  100%{opacity:0;transform:translate(-50%,-50%) scale(1.1)}
}

/* ---------- 身份翻开 ---------- */
.reveal-id{position:fixed;z-index:645;transform:translate(-50%,-50%);padding:7px 20px;border-radius:5px;
  font-size:18px;letter-spacing:.3em;pointer-events:none;
  animation:judgeLife 1.6s cubic-bezier(.2,1.2,.4,1) forwards}

/* ---------- 摸牌 ---------- */
.draw-ghost{position:fixed;z-index:608;pointer-events:none;
  transition:transform var(--t,.42s) cubic-bezier(.3,.85,.3,1),opacity var(--t,.42s)}

/* ---------- 回合提示 ---------- */
.turn-banner{display:flex;flex-direction:column;align-items:center;gap:2px;
  animation:bannerLife 1.1s cubic-bezier(.16,1,.3,1) forwards}
.turn-banner .tb-name{font-size:30px;font-weight:900;color:#f4e2b4;letter-spacing:.3em;
  text-shadow:0 0 20px rgba(0,0,0,.95),0 3px 0 #2a1d08}
.turn-banner .tb-sub{font-size:13px;color:#9c8f74;letter-spacing:.5em}

/* ---------- 阶段提示：墨线自中心拉开，字随之浮现再散去 ----------
   两条流畅性约束：
   1. 只动 opacity 和 transform。这两个属性由合成器单独处理，
      filter/blur 之类每帧都要重新光栅化的属性一律不用。
   2. 绝对定位，不进 #bannerLayer 的 flex 列。回合横幅还没消失时
      阶段横幅就会插进来，同处一列时两者会互相挤动位置。 */
.phase-banner{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:flex;align-items:center;justify-content:center;gap:20px;
  padding:12px 0;white-space:nowrap;will-change:opacity;
  animation:phaseFade var(--life,1150ms) ease-out forwards}
/* 椭圆暗晕代替矩形底板，横幅才不会在牌桌上压出一条生硬的方块 */
.phase-banner::before{content:"";position:absolute;left:50%;top:50%;z-index:-1;
  width:min(760px,86vw);height:170px;
  transform:translate3d(-50%,-50%,0);will-change:transform,opacity;
  background:radial-gradient(ellipse at center,rgba(5,8,12,.86),rgba(5,8,12,.5) 40%,transparent 72%);
  animation:phaseGlow var(--life,1150ms) ease-out forwards}
.pb-rule{position:relative;flex:0 0 clamp(52px,11vw,168px);height:1px;
  background:linear-gradient(90deg,transparent,rgba(232,200,106,.15) 35%,rgba(240,214,154,.8));
  transform-origin:right center;will-change:transform,opacity;
  animation:phaseRule var(--life,1150ms) cubic-bezier(.2,.9,.25,1) forwards}
.pb-rule:last-child{transform-origin:left center;
  background:linear-gradient(270deg,transparent,rgba(232,200,106,.15) 35%,rgba(240,214,154,.8))}
.pb-rule::after{content:"";position:absolute;top:-2.5px;right:-4px;width:6px;height:6px;
  background:#f6e2b2;transform:rotate(45deg);box-shadow:0 0 9px rgba(240,215,154,.9)}
.pb-rule:last-child::after{right:auto;left:-4px}
.pb-text{display:flex;flex-direction:column;align-items:center;gap:6px;
  will-change:transform,opacity;
  animation:phaseText var(--life,1150ms) cubic-bezier(.22,.61,.24,1) forwards}
.phase-banner .pb-who{font-size:14px;letter-spacing:.56em;text-indent:.56em;color:#a89a7c}
.phase-banner .pb-name{font:900 35px/1.05 KaiTi,STKaiti,serif;letter-spacing:.34em;text-indent:.34em;
  background:linear-gradient(180deg,#fff8e3,#f2d295 42%,#c99c47 72%,#edd0a0);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 2px 5px rgba(0,0,0,.95)) drop-shadow(0 0 18px rgba(226,178,86,.4))}
@keyframes phaseFade{0%{opacity:0}12%{opacity:1}74%{opacity:1}100%{opacity:0}}
@keyframes phaseGlow{
  0%{opacity:0;transform:translate3d(-50%,-50%,0) scale3d(.74,.55,1)}
  26%{opacity:1;transform:translate3d(-50%,-50%,0) scale3d(1,1,1)}
  74%{opacity:1;transform:translate3d(-50%,-50%,0) scale3d(1,1,1)}
  100%{opacity:0;transform:translate3d(-50%,-50%,0) scale3d(1.1,1.06,1)}
}
@keyframes phaseRule{
  0%{opacity:1;transform:scale3d(0,1,1)}
  32%{opacity:1;transform:scale3d(1,1,1)}
  74%{opacity:1;transform:scale3d(1,1,1)}
  100%{opacity:0;transform:scale3d(1.16,1,1)}
}
/* 缩放幅度压在 ±5% 以内：层一旦按初始比例光栅化，大幅缩放会触发重新光栅 */
@keyframes phaseText{
  0%{opacity:0;transform:translate3d(0,13px,0) scale3d(.955,.955,1)}
  30%{opacity:1;transform:translate3d(0,0,0) scale3d(1,1,1)}
  74%{opacity:1;transform:translate3d(0,0,0) scale3d(1,1,1)}
  100%{opacity:0;transform:translate3d(0,-13px,0) scale3d(1.035,1.035,1)}
}
@media(prefers-reduced-motion:reduce){
  .phase-banner::before,.pb-rule,.pb-text{animation-name:phaseFade}
}

/* ---------- 弃牌飞散 ---------- */
.discard-fly{position:fixed;z-index:606;pointer-events:none;
  transition:transform .5s cubic-bezier(.3,.7,.4,1),opacity .5s}
