/* style.css — PTCG Battle（UI 对标 pmBattle：竖屏单屏 / 无底板半透菜单 / 草原场地） */

:root {
  --bg: #1a1a2e;
  --panel: #16213e;
  --panel-border: #0f3460;
  --text: #eaeaea;
  --hp-green: #4cd964;
  --hp-yellow: #ffcc00;
  --hp-red: #ff3b30;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* 顶部/底部统一留空：取两者较大值，保证刘海区与下方留空视觉对称 */
  --safe-gap: max(var(--safe-top), var(--safe-bottom));
  /* 场地草原渐变（与 pmBattle 同源） */
  --field-bg:
    radial-gradient(ellipse 34% 12% at 24% 48%, rgba(255, 253, 175, .26) 0%, transparent 72%),
    radial-gradient(ellipse 40% 14% at 76% 52%, rgba(255, 253, 175, .22) 0%, transparent 72%),
    radial-gradient(ellipse 46% 15% at 40% 62%, rgba(255, 253, 175, .30) 0%, transparent 74%),
    radial-gradient(ellipse 38% 11% at 68% 70%, rgba(126, 200, 80, .34) 0%, transparent 72%),
    radial-gradient(ellipse 44% 13% at 28% 76%, rgba(126, 200, 80, .32) 0%, transparent 72%),
    radial-gradient(ellipse 36% 10% at 56% 84%, rgba(126, 200, 80, .38) 0%, transparent 70%),
    radial-gradient(ellipse 48% 13% at 18% 92%, rgba(93, 168, 56, .40) 0%, transparent 72%),
    radial-gradient(ellipse 42% 12% at 64% 96%, rgba(93, 168, 56, .38) 0%, transparent 70%),
    radial-gradient(ellipse 40% 11% at 44% 40%, rgba(126, 200, 80, .28) 0%, transparent 72%),
    radial-gradient(ellipse 38% 10% at 10% 60%, rgba(126, 200, 80, .30) 0%, transparent 70%),
    radial-gradient(ellipse 42% 11% at 88% 62%, rgba(255, 253, 175, .20) 0%, transparent 72%),
    radial-gradient(ellipse 36% 10% at 90% 84%, rgba(93, 168, 56, .36) 0%, transparent 70%),
    linear-gradient(180deg,
      #4a90d9 0%, #87ceeb 14%,
      #9fd890 19%, #7ec850 22%,
      #6bb043 55%, #5da838 100%);
}

/* 本文件全部规则限定在 #battle-app 作用域内（原 * / html,body / body 全局规则已改造），
   以便作为 ptcg 卡牌库的嵌入式视图共存，不污染宿主布局与滚动。 */
/* 注意：必须用 :where() 把优先级降为 0 ——
   若写成 '#battle-app *'（优先级 1,0,0），会压过 .overlay-screen / .overlay-header 等
   所有类选择器上的 padding/margin，导致战斗页内边距与按钮尺寸全部失效。 */
:where(#battle-app, #battle-app *) { margin: 0; padding: 0; box-sizing: border-box; }

/* 战斗期间场地背景由 html 提供：
   html 的背景会铺满整个 canvas（含刘海区与底部横条区），
   而 #battle-app 用 fixed + 100dvh 在 iOS 上可能覆盖不到顶部安全区，
   导致刘海区露出宿主底色（黑）。class 由 main.js 在切页时添加/移除。 */
/* 战斗期间 html 只做兜底：锁定 height:100% 让它的 box 等于视口高度。
   若不加高度限制，根元素 box = 文档高度（含卡库容器），
   而根元素的渐变是按其 box 绘制的，会被拉伸（天空占比从 19% 膨胀到 ~28%）。 */
html.ptcg-battle-active {
  height: 100%;
  /* 纯色兜底：与场地上方的天空色一致。
     这里必须用纯色而不是渐变 —— 根元素的渐变按其 box 绘制，
     而 box 高度受文档影响会被拉伸（曾导致天空占比 19% → 28%）。 */
  background: #4a90d9;
}
html.ptcg-battle-active,
html.ptcg-battle-active body { overflow: hidden; }

html.ptcg-battle-active body {
  /* 覆盖刘海状态栏与底部横条区域：宿主深色底必须清掉，
     这里用与场地上方同色的天空色，保证与 #battle-app 的渐变顶部无缝衔接。 */
  background: #4a90d9;
}

#battle-app [hidden] { display: none !important; }

/* 战斗视图激活时：锁住宿主滚动，并给宿主也铺上场地背景 ——
   #battle-app 为 fixed 覆盖层，在部分 iOS 场景（动态视口/刘海区）可能露出宿主底色，此处兜底。 */
body.ptcg-battle-active {
  overflow: hidden;
  /* 背景统一由 html.ptcg-battle-active 提供，这里不再重复铺，避免渐变叠加 */
}

#battle-app {
  position: fixed;
  inset: 0;
  display: none;
  /* 不设 height：由 inset:0 拉伸到布局视口，避免 iOS 下 100dvh 覆盖不到安全区 */
  width: 100%;
  font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  color: var(--text);
  /* 主背景：box 即视口（inset:0 拉伸），渐变比例正确；
     html 的同色背景作为刘海/底边的兜底。 */
  background: var(--field-bg);
  overflow: hidden;
  /* 必须高于宿主 .search-header 的 z-index:1001，否则会被固定在顶部的搜索栏压住 */
  z-index: 1200;
}

#battle-app.active { display: block; }

#screen {
  position: relative;
  width: 100%;
  max-width: 520px;
  height: 100%;
  max-height: 960px;
  margin: 0 auto;
  /* 注意：本层子元素均为 absolute，包含块是其 padding box，
     故 safe-area 留空必须加在各元素自身（见 --safe-gap 用法）。 */
  overflow: hidden;
  background: transparent;  /* 背景由 html.ptcg-battle-active 提供 */
}

/* ================= 战斗场景（单屏铺满） ================= */
#battle-scene {
  position: absolute;
  inset: 0;
  background: transparent;
  overflow: hidden;
}

/* ================= 血条信息（无底板，pmBattle 风） ================= */
.info-box {
  position: absolute;
  width: 50%;
  z-index: 6;
  background: none;
  border: none;
  min-width: 0;
}
.info-box.opponent-info { top: calc(var(--safe-gap) + 14px); right: 6px; }
.info-box.player-info { bottom: calc(var(--safe-gap) + 14px); left: 6px; }

.info-top {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 3px;
  min-width: 0;
}
.info-name {
  font-weight: bold;
  font-size: 15px;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  text-shadow: 0 1px 2px rgba(0,0,0,.6);
}
.info-tags { display: flex; gap: 3px; flex-shrink: 0; margin-left: auto; }
.rule-tag {
  font-size: 10px;
  font-weight: bold;
  padding: 1px 5px;
  border-radius: 8px;
  background: rgba(0,0,0,.45);
  color: #ffd964;
  border: 1px solid rgba(255,217,100,.5);
  flex-shrink: 0;
}
.rule-tag.stage { color: #9fd8ff; border-color: rgba(159,216,255,.5); }

.status-tag {
  font-size: 11px;
  font-weight: bold;
  padding: 1px 6px;
  border-radius: 8px;
  background: #ff9500;
  color: #fff;
  flex-shrink: 0;
}
.status-tag.poison { background: #a040c0; }
.status-tag.burn { background: #f85030; }
.status-tag.sleep { background: #5858a8; }
.status-tag.paralysis { background: #c8a800; }
.status-tag.confusion { background: #f87818; }

/* HP 条：加厚、血量居中（保留 .hp-bar-fill 类名以兼容 main.js 渲染） */
.hp-bar {
  position: relative;
  height: 26px;
  background: rgba(0, 0, 0, .45);
  border-radius: 13px;
  overflow: hidden;
}
.hp-bar-bg { position: relative; height: 26px; background: rgba(0,0,0,.45); border-radius: 13px; overflow: hidden; }
.hp-bar-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 100%;
  background: var(--hp-green);
  transition: width .4s, background .4s;
}
.hp-bar-fill.yellow { background: var(--hp-yellow); }
.hp-bar-fill.red { background: var(--hp-red); }
.hp-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: bold;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.7);
  z-index: 1;
}
.hp-label { display: none; }
.hp-row { display: block; }

/* 对局状态行（奖赏/牌库/手牌数量） */
.info-stats {
  margin-top: 3px;
  font-size: 11px;
  color: #eaf2ff;
  text-shadow: 0 1px 2px rgba(0,0,0,.65);
  letter-spacing: .2px;
}

/* 能量 / 附着卡图标 */
.energy-icons { display: flex; gap: 3px; margin-top: 4px; flex-wrap: wrap; min-height: 12px; }
.energy {
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.55);
  display: inline-block;
  box-shadow: 0 1px 2px rgba(0,0,0,.4);
}
/* 需求：宝可梦道具用**方形**图标（带道具首字），与圆形能量视觉分组、放在能量前面 */
.tool-chip {
  width: 13px; height: 13px;
  border-radius: 2px;
  background: #ffd964;
  border: 1px solid rgba(0,0,0,.55);
  color: #4a3300;
  font-size: 9px; line-height: 11px;
  text-align: center; font-weight: 700;
  display: inline-block;
  box-shadow: 0 1px 2px rgba(0,0,0,.4);
  margin-right: 3px;
}
.energy.fire { background: #f85030; }
.energy.electric, .energy.lightning { background: #f8d030; }
.energy.water { background: #5898f8; }
.energy.grass { background: #58b858; }
.energy.psychic { background: #c860c8; }
.energy.fighting { background: #c87838; }
.energy.dark { background: #504848; }
.energy.metal { background: #a8a8b8; }
.energy.dragon { background: #7038f8; }
.energy.fairy { background: #f068a8; }
.energy.colorless { background: #e8e8e8; }
/* 旧状态图标样式保留（兼容旧 class） */
.status-icon {
  display: inline-block;
  width: 12px; height: 12px;
  font-size: 8px;
  line-height: 12px;
  text-align: center;
  border-radius: 3px;
  color: #fff;
  font-weight: bold;
}
.status-icon.poison { background: #a040c0; }
.status-icon.burn { background: #f85030; }
.status-icon.sleep { background: #5858a8; }
.status-icon.paralysis { background: #c8a800; }
.status-icon.confusion { background: #f87818; }

/* ================= 宝可梦立绘 ================= */
.sprite-slot {
  position: absolute;
  width: 50%;
  z-index: 2;
  /* 需求：固定立绘槽高度。原来槽高由立绘图片决定（min-height 96px → 有图时 128/156px），
     而脚踏台是 bottom:0 定位，导致「选宝可梦之前台子在血条上方、选完之后降下来」。
     固定高度后台子位置恒定。 */
  height: 156px;
}
.sprite-slot.opponent-sprite { height: 128px; }
.sprite-slot .sprite-img { height: 100%; }
.sprite-slot .sprite-img img { max-height: 100%; }
.sprite-slot.opponent-sprite { top: calc(var(--safe-gap) + 118px); right: 0; }
.sprite-slot.player-sprite {
  /* 需求：脚踏台会侵入下方血条的名字区域，整体再上移 30px（118 → 148）。
     立绘随之整体上移，与台面的相对关系不变（脚仍踩在台面上）。 */
  bottom: calc(var(--safe-gap) + 148px); left: 0;
}

.platform {
  position: absolute;
  bottom: 0; left: 50%;
  /* 需求：让宝可梦的脚踩在脚踏台台面上。
     平台高随宽度自适应（我方 100% / 对方 78%），
     故用相对自身高度的 translateY 下移，使台面中心略低于立绘底边，脚自然落在台面上部。
     原实现为 bottom:2px + 只做水平居中，台面中心比立绘底边高约 26px，脚会落在台子外面。 */
  transform: translateX(-50%) translateY(55%);
  width: 78%; height: auto; z-index: -1;
  image-rendering: pixelated;
  opacity: .95;
}
.sprite-slot.player-sprite .platform { width: 100%; }

.sprite-img {
  width: 100%;
  min-height: 96px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  overflow: visible;
  image-rendering: pixelated;
}
.sprite-img.large { min-height: 118px; }
.sprite-img img {
  width: 100%;
  height: auto;
  max-height: 150px;
  object-fit: contain;
  image-rendering: pixelated;
  filter: drop-shadow(3px 3px 4px rgba(0,0,0,.4));
  will-change: transform, opacity;
  /* 需求：抵消立绘 PNG 底部的透明留白。
     实测 PokeAPI 立绘底部透明留白占 8%~23%（96px 图约 8~22px，缩放到 150px 后约 12~34px），
     取 -16px 作为统一补偿，让「可见的脚」更贴近脚踏台台面。 */
  margin-bottom: -16px;
}
.sprite-slot.opponent-sprite .sprite-img img { max-height: 128px; }
.sprite-slot.player-sprite .sprite-img img { max-height: 156px; }

/* 占位（无立绘时） */
.sprite-slot .placeholder {
  font-size: 13px;
  color: rgba(255,255,255,.85);
  text-shadow: 0 1px 2px rgba(0,0,0,.6);
  padding-bottom: 18px;
}
.sprite-missing .placeholder { display: block; }

/* 旧阴影元素保留但弱化 */
.sprite-shadow { display: none; }

/* ================= 立绘动画（pmBattle keyframes） ================= */

/* 需求①：休息（呼吸）动画 —— 在战斗场闲置时缓慢起伏。
   程序化（transform + 时长/相位由 JS 按宝可梦随机，见 _syncBreathVars），不使用逐帧图片。
   放在「立绘容器」这一层，与下方的 anim-* 同层：
   .sprite-img.anim-xxx 的选择器权重更高，会自然接管 animation，动画类移除后自动恢复呼吸。 */
@keyframes idleBreath {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-5px) scale(1.038); }
}
.sprite-img {
  animation: idleBreath var(--breath-dur, 4.2s) ease-in-out infinite;
  animation-delay: var(--breath-delay, 0s);
  will-change: transform;
}
/* 需求：全局关闭动画（PTCG_ANIM='off'）时，CSS 侧的呼吸也要停掉
   —— 原来 _animEnabled() 只拦住了 JS 加的 anim-* 类，CSS 基础动画不受控。 */
body.no-anim .sprite-img,
body.no-anim .sprite-img.anim-attack,
body.no-anim .sprite-img.anim-hit,
body.no-anim .sprite-img.anim-enter,
body.no-anim .sprite-img.anim-exit { animation: none !important; }

.sprite-img.anim-attack { animation: lunge-pl .45s ease-out; }
.sprite-slot.opponent-sprite .sprite-img.anim-attack { animation: lunge-opp .45s ease-out; }
@keyframes lunge-pl {
  0% { transform: translate(0,0); }
  40% { transform: translate(28px,-24px); }
  100% { transform: translate(0,0); }
}
@keyframes lunge-opp {
  0% { transform: translate(0,0); }
  40% { transform: translate(-28px,24px); }
  100% { transform: translate(0,0); }
}
.anim-hit { animation: hitFlash .55s ease; }
@keyframes hitFlash {
  0%, 100% { opacity: 1; }
  20% { opacity: .2; }
  40% { opacity: 1; }
  60% { opacity: .3; }
  80% { opacity: 1; }
}
.anim-enter { animation: enterScale .5s ease-out; }
@keyframes enterScale {
  0% { transform: scale(0); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
.anim-exit { animation: exitScale .5s ease-in forwards; }
@keyframes exitScale {
  0% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0); opacity: 0; }
}

/* ================= 战斗日志（左上浮层，pmBattle 风） ================= */
#battle-log {
  position: absolute;
  top: calc(var(--safe-gap) + 14px); left: 6px;
  width: 44%;
  max-height: min(260px, 40vh);   /* ≈ 操作区 8 个按钮的厚度；超出可滚动回看历史动作 */
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.4) transparent;
  z-index: 4;
  pointer-events: auto;        /* 允许拖动/滚动查看历史（原为 none，无法交互） */
  cursor: grab;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
#battle-log.dragging { cursor: grabbing; user-select: none; }
#battle-log::-webkit-scrollbar { width: 6px; }
#battle-log::-webkit-scrollbar-track { background: transparent; }
#battle-log::-webkit-scrollbar-thumb { background: rgba(255,255,255,.4); border-radius: 3px; }
.log-line {
  background: rgba(0,0,0,.5);
  color: #fff;
  padding: 3px 8px;
  border-radius: 6px;
  font-size: 12px;
  line-height: 1.35;
  animation: fadeIn .25s;
  word-break: break-word;
}
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }

/* ================= 召唤 / 收回（需求②③） =================
   ② 召唤：精灵球从屏幕外旋转入场 → 到脚踏台上方 → 爆一下白光 →
           宝可梦以「缩小白化」状态从爆炸位置出现 → 放大到正常体型并恢复原色 →
           自由落体到脚踏台上 → 落地震一下
   ③ 收回：快速逐渐变白 → 全白后迅速缩小到几乎看不见
   说明：精灵球素材来自 ddp 项目（ddp/images/ball.png），已抠掉不透明绿底并 8 倍最近邻放大，
        落到 ptcg/battle/assets/ball.png（96×96 / 449B）。
   说明：所有动画都做在「立绘容器 .sprite-img」这一层，与既有 anim-* 同层，
        靠选择器权重/源码顺序自然接管，动画类移除后自动恢复呼吸动画。 */

/* 精灵球：从屏幕外旋转飞到脚踏台上方。
   --summon-rise 同时决定「球停在哪里」和「宝可梦从哪里出现/下落多少」，
   两处必须用同一个值，否则会出现「球在头顶、宝可梦从别处掉下来」的错位。 */
.sprite-slot { --summon-rise: 44px; }
.summon-ball {
  position: absolute;
  left: 50%; bottom: var(--summon-rise, 44px);
  width: 96px; height: 96px;
  margin-left: -48px;
  z-index: 3;
  image-rendering: pixelated;
  pointer-events: none;
  opacity: 0;
  /* 基态也保持缩小：否则移除 .show 后回落到 scale(1)=96px，
     在「淡出」的那一帧会闪出一个满尺寸精灵球（玩家实测看到的就是这个） */
  transform: scale(.17);
}
.summon-ball.show { animation: ballEnter .55s cubic-bezier(.22,.75,.3,1) forwards; }
@keyframes ballEnter {
  0%   { transform: translate(-190px, -64px) rotate(-540deg) scale(.17); opacity: 0; }
  20%  { opacity: 1; }
  100% { transform: translate(0, 0) rotate(0deg) scale(.17); opacity: 1; }
}
/* 白爆时球立刻隐去（否则会一直挂在宝可梦头上） */
/* 淡出必须把 transform 一起写上：只写 opacity 的话，
   动画一接管就丢掉 ballEnter 的终态 transform，球会瞬间回到原始大小 */
@keyframes ballFadeOut {
  from { opacity: 1; transform: scale(.17); }
  to   { opacity: 0; transform: scale(.17); }
}

/* 爆白光 */
.fx-burst {
  position: absolute;
  left: 50%; bottom: 44%;
  width: 140px; height: 140px;
  margin: 0 0 -70px -70px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, rgba(255,255,255,.9) 30%, rgba(255,255,255,0) 68%);
  opacity: 0;
  pointer-events: none;
  z-index: 4;
}
.fx-burst.show { animation: burstFlash .32s ease-out forwards; }
@keyframes burstFlash {
  0%   { transform: scale(.15); opacity: 0; }
  30%  { transform: scale(.85); opacity: 1; }
  100% { transform: scale(1.6); opacity: 0; }
}

/* 宝可梦出现：缩小白化 → 放大回色（起始位置＝精灵球处 = 脚踏台上方 --summon-rise） */
.sprite-img.anim-summon-appear {
  transform-origin: 50% 100%;
  animation: summonAppearGrow .36s ease-out forwards;
}
@keyframes summonAppearGrow {
  0%   { opacity: 0; transform: translateY(calc(-1 * var(--summon-rise, 44px))) scale(.12); filter: brightness(0) invert(1); }
  15%  { opacity: 1; }
  55%  { filter: brightness(0) invert(1); }
  100% { opacity: 1; transform: translateY(calc(-1 * var(--summon-rise, 44px))) scale(1); filter: none; }
}
/* 自由落体（源码顺序必须在 appear 之后：同时存在时由后者接管 animation） */
.sprite-img.anim-summon-fall {
  transform-origin: 50% 100%;
  animation: summonFall .26s cubic-bezier(.45,.02,.9,.6) forwards;
}
@keyframes summonFall {
  0%   { transform: translateY(calc(-1 * var(--summon-rise, 44px))) scale(1); }
  100% { transform: translateY(0) scale(1); }
}
/* 落地震一下（轻微压扁回弹） */
.sprite-img.anim-summon-land {
  transform-origin: 50% 100%;
  animation: summonLand .34s ease-out forwards;
}
@keyframes summonLand {
  0%   { transform: translateY(0) scale(1, 1); }
  20%  { transform: translateY(2px) scale(1.06, .93); }
  55%  { transform: translateY(0) scale(.98, 1.02); }
  100% { transform: translateY(0) scale(1, 1); }
}

/* ================= 招式表现：近战 / 远程 / 辅助（需求⑤⑥） =================
   方向变量：我方在左下、对方在右上，所以「前」= 我方(+x,-y) / 对方(-x,+y)。
   前进方向由 --fwd-x / --fwd-y 决定，攻击距离由 JS 按两个立绘的实际位置算成
   --dash-len / --beam-len / --beam-angle，避免写死像素在不同机型和横竖屏下错位。 */
.sprite-slot.player-sprite { --fwd-x: 1; --fwd-y: -0.62; }
.sprite-slot.opponent-sprite { --fwd-x: -1; --fwd-y: 0.62; }

/* ⑤ 近战·蓄力：颤抖 + 缓慢向后移动（像拉弹弓） */
.sprite-img.anim-melee-wind {
  transform-origin: 50% 100%;
  animation: meleeWind .5s ease-in-out forwards;
}
@keyframes meleeWind {
  0%   { transform: translate(0, 0); }
  12%  { transform: translate(calc(var(--fwd-x) * -2px), calc(var(--fwd-y) * -2px)); }
  24%  { transform: translate(calc(var(--fwd-x) * -5px), calc(var(--fwd-y) * -5px)); }
  36%  { transform: translate(calc(var(--fwd-x) * -3px), calc(var(--fwd-y) * -3px)); }
  48%  { transform: translate(calc(var(--fwd-x) * -8px), calc(var(--fwd-y) * -8px)); }
  60%  { transform: translate(calc(var(--fwd-x) * -5px), calc(var(--fwd-y) * -5px)); }
  72%  { transform: translate(calc(var(--fwd-x) * -11px), calc(var(--fwd-y) * -11px)); }
  86%  { transform: translate(calc(var(--fwd-x) * -8px), calc(var(--fwd-y) * -8px)); }
  100% { transform: translate(calc(var(--fwd-x) * -14px), calc(var(--fwd-y) * -14px)); }
}

/* ⑤ 近战·前冲：嗖一下冲到极限距离（约 62% 处到达极限＝命中时刻），再退回原位 */
.sprite-img.anim-melee-dash {
  transform-origin: 50% 100%;
  animation: meleeDash .38s cubic-bezier(.2,.85,.3,1) forwards;
}
@keyframes meleeDash {
  0%   { transform: translate(calc(var(--fwd-x) * -14px), calc(var(--fwd-y) * -14px)); }
  62%  { transform: translate(calc(var(--fwd-x) * var(--dash-len, 120px)), calc(var(--fwd-y) * var(--dash-len, 120px))); }
  78%  { transform: translate(calc(var(--fwd-x) * var(--dash-len, 120px) * .94), calc(var(--fwd-y) * var(--dash-len, 120px) * .94)); }
  100% { transform: translate(0, 0); }
}

/* ⑥ 远程·蓄力：边颤抖边微微向前移动 */
.sprite-img.anim-ranged-charge {
  transform-origin: 50% 100%;
  animation: rangedCharge .42s ease-out forwards;
}
@keyframes rangedCharge {
  0%   { transform: translate(0, 0); }
  18%  { transform: translate(calc(var(--fwd-x) * 3px), calc(var(--fwd-y) * 3px)); }
  34%  { transform: translate(calc(var(--fwd-x) * -2px), calc(var(--fwd-y) * -2px)); }
  52%  { transform: translate(calc(var(--fwd-x) * 4px), calc(var(--fwd-y) * 4px)); }
  70%  { transform: translate(calc(var(--fwd-x) * 1px), calc(var(--fwd-y) * 1px)); }
  100% { transform: translate(calc(var(--fwd-x) * 6px), calc(var(--fwd-y) * 6px)); }
}
/* ⑥ 远程·发射期间回到原位（光束在飞的同时往回站定） */
.sprite-img.anim-ranged-back {
  transform-origin: 50% 100%;
  animation: rangedBack .5s ease-out forwards;
}
@keyframes rangedBack {
  0%   { transform: translate(calc(var(--fwd-x) * 6px), calc(var(--fwd-y) * 6px)); }
  100% { transform: translate(0, 0); }
}

/* 辅助招式：自身发光（无伤害招式） */
.sprite-img.anim-support-glow { animation: supportGlow .6s ease-out; }
@keyframes supportGlow {
  0%, 100% { filter: none; }
  45%      { filter: brightness(1.45) drop-shadow(0 0 16px rgba(190,235,255,.95)); }
}

/* 光束 / 命中爆点：颜色取自「攻击方自身属性」，用 CSS 变量给色 */
.fx-beam {
  position: absolute;
  transform-origin: 0 50%;
  height: 14px;
  border-radius: 7px;
  background: linear-gradient(90deg, var(--fx-c1), var(--fx-c2));
  box-shadow: 0 0 14px var(--fx-c2), 0 0 4px #fff inset;
  opacity: 0;
  pointer-events: none;
  z-index: 5;
}
.fx-beam.show { animation: beamFire .55s ease-out forwards; }
@keyframes beamFire {
  0%   { opacity: 0; transform: rotate(var(--beam-angle, 0deg)) scaleX(.05); }
  18%  { opacity: 1; transform: rotate(var(--beam-angle, 0deg)) scaleX(1); }
  72%  { opacity: 1; transform: rotate(var(--beam-angle, 0deg)) scaleX(1); }
  100% { opacity: 0; transform: rotate(var(--beam-angle, 0deg)) scaleX(1); }
}
.fx-impact {
  position: absolute;
  width: 150px; height: 150px;
  margin: -75px 0 0 -75px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, var(--fx-c1) 34%, rgba(255,255,255,0) 70%);
  opacity: 0;
  pointer-events: none;
  z-index: 6;
}
.fx-impact.show { animation: impactBurst .5s ease-out forwards; }
@keyframes impactBurst {
  0%   { transform: scale(.18); opacity: 0; }
  22%  { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.55); opacity: 0; }
}

/* 属性配色（与能量圆点同色系） */
.fx-beam.grass, .fx-impact.grass       { --fx-c1: #9fe07a; --fx-c2: #45a049; }
.fx-beam.fire, .fx-impact.fire         { --fx-c1: #ffb347; --fx-c2: #f85030; }
.fx-beam.water, .fx-impact.water       { --fx-c1: #8fd0ff; --fx-c2: #2f7fe0; }
.fx-beam.electric, .fx-impact.electric,
.fx-beam.lightning, .fx-impact.lightning { --fx-c1: #fff08a; --fx-c2: #f3c410; }
.fx-beam.psychic, .fx-impact.psychic   { --fx-c1: #f0a6f0; --fx-c2: #b13fd0; }
.fx-beam.fighting, .fx-impact.fighting { --fx-c1: #f0b070; --fx-c2: #c05a20; }
.fx-beam.dark, .fx-impact.dark         { --fx-c1: #9a86b0; --fx-c2: #3a2f4a; }
.fx-beam.metal, .fx-impact.metal       { --fx-c1: #dfe3ef; --fx-c2: #8d93a6; }
.fx-beam.dragon, .fx-impact.dragon     { --fx-c1: #b08cff; --fx-c2: #6a2fe0; }
.fx-beam.fairy, .fx-impact.fairy       { --fx-c1: #ffb6d9; --fx-c2: #e8559b; }
.fx-beam.colorless, .fx-impact.colorless { --fx-c1: #f2f2f2; --fx-c2: #b9b9b9; }

body.no-anim .fx-beam.show, body.no-anim .fx-impact.show { animation: none !important; opacity: 0 !important; }
body.no-anim .sprite-img.anim-melee-wind,
body.no-anim .sprite-img.anim-melee-dash,
body.no-anim .sprite-img.anim-ranged-charge,
body.no-anim .sprite-img.anim-ranged-back,
body.no-anim .sprite-img.anim-support-glow { animation: none !important; }

/* ================= 进化 / 退化（需求④） =================
   老形象：逐渐变白 → 逐渐缩小
   新形象：以「白化的缩小版」出现 → 放大到正常尺寸 → 恢复正常颜色
   实现：进化前把当前立绘拍成覆盖层 .fx-evo-old 放在原位，
        主立绘容器里已经是进化后的新形象，于是两层天然对齐、可以做交叉过渡。 */
.fx-evo-old {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 2;
  pointer-events: none;
  transform-origin: 50% 100%;
  transition: filter .42s ease, transform .5s ease, opacity .5s ease;
}
.fx-evo-old img {
  width: 100%; height: auto; max-height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
}
.fx-evo-old.evo-white { filter: brightness(0) invert(1); }           /* 老形象变白 */
.fx-evo-old.evo-shrink { transform: scale(.06); opacity: 0; }         /* 老形象缩小消失 */

/* 击倒/换人时的「收回」：老形象逐渐变白 → 迅速缩小消失（复用覆盖层结构） */
.fx-evo-old.evo-recall { transition: filter .24s ease, transform .3s ease, opacity .3s ease; }

/* 新形象：缩小白化出现 → 放大回色 */
.sprite-img.anim-evo-new {
  transform-origin: 50% 100%;
  animation: evoNewGrow .56s ease-out forwards;
}
@keyframes evoNewGrow {
  0%   { opacity: 0; transform: scale(.3); filter: brightness(0) invert(1); }
  18%  { opacity: 1; }
  62%  { filter: brightness(0) invert(1); }
  100% { opacity: 1; transform: scale(1); filter: none; }
}
body.no-anim .sprite-img.anim-evo-new { animation: none !important; }

/* ③ 收回：快速逐渐变白 → 全白后迅速缩小到几乎看不见 */
.sprite-img.anim-recall {
  transform-origin: 50% 100%;
  animation: recallOut .42s ease-in forwards;
}
@keyframes recallOut {
  0%   { filter: none; transform: scale(1); opacity: 1; }
  55%  { filter: brightness(0) invert(1); transform: scale(1); opacity: 1; }
  100% { filter: brightness(0) invert(1); transform: scale(.03); opacity: 0; }
}

/* ================= 操作区（右下浮动，无底板；只显示按钮） ================= */
/* 提示/状态文本统一走左上信息栏，操作区仅保留按钮 */
#main-text, #fight-text, #target-text, #msg-text { display: none; }
#dialog-area {
  position: absolute;
  bottom: calc(var(--safe-gap) + 14px); right: 6px;
  width: 46%;
  z-index: 10;
  background: none;
  border: none;
  pointer-events: none;
}
.dialog-panel {
  display: none;
  flex-direction: column;
  gap: 5px;
  pointer-events: none;
}
.dialog-panel.active { display: flex; }

.dialog-text {
  pointer-events: auto;
  background: rgba(20, 25, 45, .55);
  border: 1px solid rgba(90, 108, 200, .45);
  border-radius: 8px;
  padding: 6px 8px;
  font-size: 12px;
  line-height: 1.5;
  color: #fff;
  max-height: 96px;
  overflow-y: auto;
  backdrop-filter: blur(2px);
}
.dialog-text.full { width: 100%; }

.dialog-menu {
  display: flex;
  flex-direction: column;
  gap: 5px;
  pointer-events: auto;
  /* 滚动列表（操作区固定高度内滚动，替代全屏分页界面） */
  max-height: 46vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-right: 2px;
}
/* 列表项：名称 + 元信息两行 */
.menu-item .mv-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-item .mv-meta { flex: 0 0 auto; max-width: 58%; font-size: 11px; color: #9fb4ff; margin-top: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-list { width: 100%; }
.menu-2x2 { width: 100%; }

.menu-item {
  background: rgba(20, 25, 45, .55);
  border: 1px solid rgba(90, 108, 200, .55);
  border-radius: 6px;
  padding: 5px 9px;
  cursor: pointer;
  font-size: 13px;
  color: #fff;
  transition: all .12s;
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  line-height: 1.35;
}
.menu-item:hover { background: rgba(53, 67, 122, .7); border-color: #5a6cff; }
.menu-item:active { transform: scale(.97); }
.menu-item.disabled { opacity: .4; pointer-events: none; }
.menu-item.selected { border-color: #ffd964; color: #ffd964; }
/* 需求：【战斗】页签等列表去掉装饰性符号（原来选中项会插入一个 ▶，
   既被当成 emoji，也让首条招式的排版与其它条目不一致）。
   选中态仍由上面的边框/文字颜色体现。 */
.menu-item.back-item { color: #b9c2d8; font-size: 12px; }
.menu-item .mv-name { font-weight: bold; }
.menu-item .mv-meta { flex: 0 0 auto; max-width: 58%; font-size: 11px; color: #9fb4ff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ================= 全屏覆盖（深色半透，pmBattle 风） ================= */
.overlay-screen {
  display: none;
  position: absolute;
  inset: 0;
  /* 全屏覆盖层同样要避开刘海/底部横条（其包含块是 #screen 的 padding box） */
  padding-top: var(--safe-gap);
  padding-bottom: var(--safe-gap);
  background: rgba(10, 14, 28, .92);
  backdrop-filter: blur(4px);
  flex-direction: column;
  z-index: 20;
  color: var(--text);
}
.overlay-screen.active { display: flex; }

.overlay-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 10px;
  background: rgba(22, 33, 62, .9);
  color: #fff;
  font-size: 14px;
  border-bottom: 1px solid rgba(90, 108, 200, .45);
}
.page-title { font-weight: bold; }
.page-arrow { cursor: pointer; font-size: 15px; padding: 2px 8px; border-radius: 6px; }
.page-arrow:hover { color: #ffd964; background: rgba(90,108,200,.2); }
.page-indicator { font-size: 11px; color: #9fb4ff; }

.overlay-footer {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: rgba(22, 33, 62, .9);
  border-top: 1px solid rgba(90, 108, 200, .45);
  font-size: 13px;
  color: #fff;
}
.footer-btn {
  cursor: pointer;
  padding: 6px 12px;
  border-radius: 6px;
  background: rgba(20, 25, 45, .7);
  border: 1px solid rgba(90, 108, 200, .55);
}
.footer-btn:hover { background: rgba(53, 67, 122, .8); border-color: #5a6cff; }
.footer-btn.right { margin-left: auto; }
.footer-btn.disabled { opacity: .4; pointer-events: none; }

/* ================= 卡组选择 ================= */
.deck-select-body {
  flex: 1; display: flex; flex-wrap: wrap; gap: 12px;
  padding: 12px;
  /* 卡组多时整块可滚动（移动端可拖动），避免靠后的卡组被裁掉 */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  align-content: flex-start;
}
.deck-column {
  flex: 1 1 45%; min-width: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.deck-column-title { font-size: 13px; color: #9fd8ff; text-align: center; margin-bottom: 4px; font-weight: bold; }
/* 卡组来源与异常提示（独占一行） */
.deck-source-hint {
  flex: 0 0 100%; text-align: center; color: #9aa4b2;
  font-size: 11px; line-height: 1.5; padding: 2px 4px 6px;
}
.deck-option {
  display: flex; align-items: center; gap: 10px;
  padding: 8px; 
  border: 1px solid rgba(90, 108, 200, .35);
  border-radius: 8px; cursor: pointer;
  background: rgba(20, 25, 45, .5);
}
.deck-option:hover { border-color: #5a6cff; }
.deck-option.selected { border-color: #ffd964; background: rgba(90, 61, 138, .35); }
.deck-option img { width: 40px; height: 40px; image-rendering: pixelated; object-fit: contain; }
.deck-option span { font-size: 12px; color: #fff; }

/* 卡面/立绘缺失占位 */
.sprite-missing::after { content: attr(data-name); }
