/* ==========================================================================
   布局与组件
   断点：S ≤480 / M 481-767 / L 768-1279 / XL ≥1280（移动优先，min-width 增量覆盖）
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg-0);
  color: var(--text-1);
  font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
               "Microsoft YaHei", "Source Han Sans SC", sans-serif;
  font-size: var(--fs-md);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
}

body {
  /* 深色主题下的微弱"魔法尘埃"底纹，纯 CSS，零图片 */
  background-image:
    radial-gradient(1200px 600px at 50% -10%, rgba(139, 107, 255, 0.12), transparent 60%),
    radial-gradient(900px 500px at 90% 110%, rgba(245, 178, 60, 0.08), transparent 60%);
  background-attachment: fixed;
}
:root[data-theme="light"] body { background-image: none; }

button { font: inherit; color: inherit; }
img { display: block; max-width: 100%; }

/* C3a（移动端交互）：统一关闭 300ms 点击延迟与双击缩放。
   touch-action: manipulation 仅禁用「双击缩放」并消除点击延迟，
   仍允许滚动与双指缩放（pan / pinch-zoom），对滚动容器零副作用。
   原仅 .btn-hold 单点持有，现扩展到全部交互元素，消除残留延迟。 */
button,
a[href],
input,
select,
textarea,
label,
summary,
[tabindex]:not([tabindex="-1"]),
[role="button"],
[role="tab"],
[role="option"],
[role="menuitem"],
[role="switch"],
[role="link"],
.btn, .btn-primary, .btn-arcane, .btn-sm, .btn-block, .btn-cast,
.nav-btn,
.card, .hero-card,
.chip,
.party-slot, .slot-row,
/* 内联 SVG 图标及其子节点（导航图标 / 纹章 / 建筑精灵 / 角色点等）：
   touch-action 不继承，图标作为按钮 / 卡片的子节点时，精确点到图标仍会有 300ms 延迟。
   必须显式覆盖。manipulation 仍保留滚动与双指缩放，零副作用。 */
svg, svg *,
/* 其余未进契约、但有 cursor:pointer 的真实可点控件 */
.help-q, .base-cell, .hero-name, .hero-sub, .hero-meta,
.star-row, .role-dot, .badge-lv, .badge-heal {
  touch-action: manipulation;
}

/* C3b：文本类输入框最小触控高度 44px。
   裸 <input>/<textarea> 浏览器默认仅 ~16px 高，手机上戳不准、易误触。
   复选 / 单选框（.base-toggle input）维持 16px 视觉尺寸，本规则特异性更低且位于其前，不受影响。 */
input:not([type="checkbox"]):not([type="radio"]),
textarea,
select { min-height: var(--touch-min); }

/* ---------------------------------------------------------------- 骨架布局 */

#app {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  padding-bottom: env(safe-area-inset-bottom);
}

#hud {
  flex: 0 0 auto;
  position: sticky;
  top: 0;
  z-index: 30;
  padding-top: env(safe-area-inset-top);
  background: linear-gradient(180deg, var(--bg-1), color-mix(in srgb, var(--bg-1) 88%, transparent));
  border-bottom: 1px solid var(--line);
  backdrop-filter: blur(8px);
}

#view {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Req3：抬到 #page-bg（z-index:0 的 fixed 背景层）之上，卡片内容不被背景盖住 */
  position: relative;
  z-index: 1;
}

/* Req3 功能菜单背景：每个功能菜单一张场景图（assets/bg/<id>.webp），
   上面压一层暗化渐变保证文字可读。fixed 层不随内容滚动；
   图片由 main.js 的 applyPageBg 按当前页 / 子页切换（网络优先 + sw 运行时缓存）。 */
#page-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
  pointer-events: none;
}

#nav {
  flex: 0 0 auto;
  position: sticky;
  bottom: 0;
  z-index: 30;
  border-top: 1px solid var(--line);
  background: var(--bg-1);
}

/* ---------------------------------------------------------------- HUD */

.hud-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;          /* 需求 7：窄屏时货币网格换到标题下一行，不横向溢出 */
  gap: 6px;
  padding: 6px 8px;
  min-height: var(--hud-h);
}

.hud-title {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--gold-1);
  letter-spacing: 0.08em;
  white-space: nowrap;
  margin-right: auto;
}

.res {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 0;
}

.res-icon {
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  border-radius: 50%;
}
.res-gold .res-icon { background: radial-gradient(circle at 35% 30%, #FFE9B0, var(--res-gold)); }
.res-crystal .res-icon { background: radial-gradient(circle at 35% 30%, #D9CCFF, var(--res-crystal)); }
.res-xp .res-icon { background: radial-gradient(circle at 35% 30%, #BEF0FF, var(--res-xp)); }
.res-build .res-icon { background: radial-gradient(circle at 35% 30%, #FFD2C7, var(--res-build)); }
.res-pet .res-icon { background: radial-gradient(circle at 35% 30%, #D3F7CF, var(--res-pet)); }
.res-skin .res-icon { background: radial-gradient(circle at 35% 30%, #FBD4F3, var(--res-skin)); }
/* 需求 7：中文名直接可见（不只是 title），用短名；数字优先保证可读 */
.res-name { color: var(--text-3); flex: 0 0 auto; }
.res-val { overflow: hidden; text-overflow: ellipsis; margin-left: auto; font-weight: 600; }

/* 需求 7：6 项货币带中文名，一行塞不下 —— 改成 3 列 × 2 行的整齐网格。
   窄屏（标题 + 网格放不下一行）时 flex-wrap 让网格独占一整行，宽度足以容纳 3 列；
   宽屏则与标题同行。绝不允许横向溢出，也不允许换行错乱（网格列宽严格相等）。 */
.hud-res {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  margin-left: auto;
  flex: 1 1 300px;
  min-width: 0;
}

/* ③-2 HUD 上的高性能模式开关（与「更多」页的同名开关共用 settings.highPerf）。
   默认画质下**不占位**（display:none）——这是状态指示 + 一键退出入口，不是常驻按钮：
   常驻会把整个 HUD 行顶高一截，移动端视觉回归基线（mobile-golden 14 个组合）会全部飘红。
   只在高性能模式出现，既是"当前开着省电模式"的提示，也是顺手关掉它的入口。 */
/* 选择器必须带元素名：.btn 定义在文件更后面（266 行），同特异性下它后写会赢，
   display:none 会被 .btn 的 inline-flex 顶掉 —— 写成 button.hud-perf（0,1,1）才压得住。 */
button.hud-perf {
  display: none;
  flex: 0 0 auto;
  min-height: 26px;
  padding: 0 8px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-2);
  color: var(--text-3);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}
.high-perf button.hud-perf { display: inline-flex; align-items: center; }
.hud-perf[aria-pressed="true"] {
  border-color: var(--gold-3);
  color: var(--gold-1);
}

/* 限时增益条（05 §1.10）：只在有增益时出现，不占位、不挤压货币行 */
.hud-buffs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}
.buff-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  color: var(--gold-1);
  background: rgba(255, 199, 61, 0.12);
  border: 1px solid rgba(255, 199, 61, 0.38);
}
.buff-chip b { font-variant-numeric: tabular-nums; font-weight: 700; }

/* ---------------------------------------------------------------- 导航 */

/* 6 个主标签（新增「装备」页，05 §1.3）：列数写死会漏掉新页签，
   这里用 auto-fit + 1fr，加页签不再需要同步改 CSS。 */
.nav-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  padding-bottom: env(safe-area-inset-bottom);
}

.nav-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--nav-h);
  padding: 6px 2px;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-3);
  font-size: var(--fs-xs);
  transition: color var(--fx-dur) var(--fx-ease);
  position: relative;
  min-width: 0;
}
.nav-btn[aria-current="page"] { color: var(--gold-1); }
.nav-btn[aria-current="page"]::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 26px;
  height: 2px;
  background: var(--gold-2);
  border-radius: 0 0 3px 3px;
}
/* 内联 SVG 图标的统一笔触。本项目的图标（纹章 / 导航 / 关闭）全部是**描边式线稿**，
   而 SVG 里 <path>/<line>/<circle> 的默认值是 `fill: black` + `stroke: none` ——
   深色主题下就渲染成纯黑剪影，更糟的是 <line> 干脆不画（英雄详情右上角的 × 就是这么丢的）。
   这里按设计意图补回笔触；颜色一律用 currentColor，随按钮的激活/未激活态自动变化
   （.nav-btn 本身 color 在 text-3 与 gold-1 之间切，图标跟着走）。
   注意 .crest 自带白色笔触，其声明在本规则**之后**，纹章不受影响。 */
.nav-ico {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.nav-lbl { white-space: nowrap; }

.nav-dot {
  position: absolute;
  top: 6px;
  right: 50%;
  margin-right: -18px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger);
}
/* 背包「快满」的橙色预警点（7 项候选④）：与真溢出（红点）同一位置不同色，
   一瞟就知道是"快没地方了"还是"刚丢东西了"。 */
.nav-dot.nav-dot-warn {
  background: var(--warning);
}

/* ---------------------------------------------------------------- 通用面板 */

.panel {
  padding: 12px;
  padding-bottom: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 100%;
}

.panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.panel-head h2 {
  margin: 0;
  font-size: var(--fs-lg);
  color: var(--text-1);
  white-space: nowrap;
}
.panel-head .spacer { margin-left: auto; }

.card {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px;
  box-shadow: var(--shadow-card);
  min-width: 0;
}

/* v0.9.101 #6：轻段（同卡内分区）。无独立边框/阴影，靠顶部细线与外卡 padding 分隔，
   专门用来收口「卡片套卡片」的层层框问题（装备页把 7 张卡压成 1 张外卡 + 若干 .seg）。 */
.seg {
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.seg:first-child { border-top: none; padding-top: 0; }
.seg:last-child { padding-bottom: 0; }
/* 背包内容容器：不自带边框/阴影（内容本身已是单张 .card 或分段），去掉「外层又套一层框」。 */
.bag-content { min-width: 0; }

.muted { color: var(--text-2); }
.dim { color: var(--text-3); }
.tiny { font-size: var(--fs-xs); }
.small { font-size: var(--fs-sm); }
.num { font-variant-numeric: tabular-nums; }
.center { text-align: center; }
.row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.row > .grow { flex: 1 1 auto; min-width: 0; }
.wrap { flex-wrap: wrap; }

/* ---------------------------------------------------------------- 玩法帮助（v0.9.22）
 * 折叠条：默认收起，手机上不会把「更多」页撑成一整面墙。
 * summary 用 grid 两列（标题 + 一句话引言），点开时标题变主色当作"已展开"的反馈。
 */

.help-page {
  border-top: 1px solid var(--line);
  padding: 6px 0;
}
.help-page:first-of-type { border-top: 0; }

.help-sum {
  display: grid;
  grid-template-columns: minmax(5.5em, auto) 1fr;
  align-items: baseline;
  gap: 8px;
  cursor: pointer;
  padding: 4px 2px;
  font-weight: 600;
  min-height: var(--touch-min);
}
.help-sum::-webkit-details-marker { display: none; }
.help-sum::before {
  content: '▸';
  color: var(--text-3);
  font-weight: 400;
  transition: transform .15s ease;
}
.help-page[open] > .help-sum::before { transform: rotate(90deg); }
.help-page[open] > .help-sum { color: var(--accent, var(--text-1)); }
.help-sum > .tiny { font-weight: 400; }

.help-body {
  padding: 2px 2px 8px 1.2em;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.help-item { min-width: 0; }
.help-a { line-height: 1.6; }

/* ---------------------------------------------------------------- 按钮 */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: var(--touch-min);
  padding: 0 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
  color: var(--text-1);
  font-size: var(--fs-md);
  font-weight: 600;
  cursor: pointer;
  transition: transform var(--fx-dur) var(--fx-ease), filter var(--fx-dur) var(--fx-ease);
  white-space: nowrap;
}
.btn:active { transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--gold-1); outline-offset: 2px; }

.btn-primary {
  background: linear-gradient(180deg, var(--gold-1), var(--gold-2) 55%, var(--gold-3));
  border-color: var(--gold-3);
  color: var(--on-gold);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

.btn-arcane {
  background: linear-gradient(180deg, var(--arcane-1), var(--arcane-2) 55%, var(--arcane-3));
  border-color: var(--arcane-3);
  color: #FFF;
}

/* v0.9.64（B4）：统一最小触控目标 44px —— .btn-sm 之前是 34px，与 .btn 的 --touch-min 不一致，
   在手机上偏小戳不准。直接复用 --touch-min（44px），不动配色 / 间距口径。 */
.btn-sm { min-height: var(--touch-min); padding: 0 10px; font-size: var(--fs-sm); }
/* v0.9.64：章节跳关的数字输入框 —— 复用 --touch-min 保证最小触控目标 44px（与 B4 统一口径）。 */
.stage-jump-input {
  min-height: var(--touch-min);
  padding: 0 8px;
  font-size: var(--fs-sm);
  color: var(--text-1);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-sizing: border-box;
}
.stage-jump-input:focus-visible { outline: 2px solid var(--gold-1, #f4c95d); outline-offset: 2px; }
.btn-block { width: 100%; }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  filter: grayscale(0.5);
  /* 禁用态原来是「金按钮 + 灰掉」= 中间调灰底配深/浅字，只有 2.2~2.8:1 一团糊。
     两主题各取自己的主文字色：深底浅字 / 浅底深字，靠 opacity 表达"不可用"。 */
  color: var(--text-1);
}

/* ---------------------------------------------------------------- 战场 */

.battlefield {
  position: relative;
  /* 战场内部改**纵向 flex**：棋盘要从"写死 112px 的定高"改成"跟着大框架长高"。
     上下沿与右侧日志框齐平是 .bf-field-wrap 的 align-items:stretch 给的（两栏同属
     一条 flex 线，吃的是同一个行高）；这里再让棋盘把多出来的高度全吃掉，
     立绘才可能跟着变大（立绘是正方形，边长既受格宽、也受行高约束，
     只放一边会被另一边顶回来）。 */
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(139, 107, 255, 0.14), transparent 62%),
    linear-gradient(180deg, var(--bg-2), var(--bg-1));
  padding: 7px 8px 6px;          /* 瘦身：收紧内边距（原 10px） */
  overflow: hidden;
  min-width: 0;
}

/* ---------------------------------------------------------------- 棋盘（5 列 × 2 行）
   单位位置由 col/row 写成 grid-column / grid-row（1-based），sim 与 UI 共用
   sim/board.js 的 assignBoard，不存在"两套摆法"。 */

.bf-grid {
  display: grid;
  grid-template-columns: repeat(var(--bf-cols, 5), 1fr);
  /* minmax(0,1fr)：纯 1fr 会被内容的 min-content 顶破（立绘把行撑到 80px+，
     战场总高失控）。行高必须死死锁在 定高/行数 上。 */
  grid-template-rows: repeat(var(--bf-rows, 2), minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  gap: 3px;
  /* 不再写死 112px：战场被 stretch 到与日志框等高后，这块高度全归棋盘，
     行高随之变大、立绘跟着变大；窄屏没有对齐对象时 min-height 兜底、值仍是旧值。
     ⚠ 行高与单位宽度是**耦合**的：立绘永远是正方形（边长 = 单位宽 = 格宽），
     一旦单位宽超过行高就会往上溢进上一排，所以下面的 max-width 必须从行高推算，
     不能另写一个独立数字 —— 见 .bf-field-wrap 里那一段与 @media (min-width:768px)。 */
  flex: 1 1 auto;
  height: auto;
  min-height: var(--bf-grid-min);
  /* ↑ 保底高度走变量：窄屏 139px（改动前写死 112px），宽屏取「日志高 − 其余占用」=177px。
     注意这里**不能写死一个比 --bf-log-h 还大的值**：那会让战场内容高过日志框，
     align-items:stretch 的整条 flex 线被顶高，左右上下沿又错了。 */
  position: relative;
  min-width: 0;
  --lunge: -6px;                    /* 推进时的位移量，方向随 <root> 的 dir-* 翻转 */
}
.dir-left .bf-grid { --lunge: 6px; }

.bf-cell {
  min-height: 0;                    /* 去掉 aspect-ratio:1/1，行高改由网格轨道决定 */
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.028);
  border: 1px dashed rgba(255, 255, 255, 0.06);
  min-width: 0;
}

/* 推进反馈：220ms 的一次"顶进"。grid-column 是不可动画的离散属性，
   所以位移是瞬时的，这里用一次整体轻推把"我迈了一格"读出来。 */
.bf-grid.advance { animation: bfAdvance 220ms var(--fx-ease); }
@keyframes bfAdvance {
  0%   { transform: translateX(0); }
  45%  { transform: translateX(var(--lunge)); }
  100% { transform: translateX(0); }
}

.unit {
  position: relative;
  width: 58px;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
/* 棋盘内的单位跟格子同宽，天然自适应 360px（58px 立绘在 5 列下会挤爆网格） */
.bf-grid .unit {
  width: 100%;
  max-width: var(--bf-unit-cap);   /* 上限 = 行高算出来的值（见 .bf-field-wrap 那一段），窄屏沿用 52px */
  height: 100%;                    /* 占满所在格高，立绘才能「立」起来 */
  align-self: center;
  justify-self: center;
  z-index: 2;
}
/* 名字与血条改为叠在格底：把整格高度都让给立绘（纵向站立），
   二者压在立绘与宠物之上，保证可读。
   二者是上下两条独立的带：名字带贴底（行盒收紧到 1.15），
   血条带整体抬到名字带上方，中间留净空，互不侵入。 */
.bf-grid .unit-name {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  text-align: center;
  z-index: 3;
  line-height: 1.15;               /* 收紧行盒：默认行高会让 9px 字顶到 13.5px，顶进血条带 */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}
.bf-grid .unit-hp {
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 13px;                    /* 血条底边抬到名字行盒（约 10.4px）之上，留 ~2.6px 净空 */
  z-index: 3;
}

.unit-art {
  width: 100%;
  flex: 1 1 auto;                   /* 占满可用高度：英雄纵向站立、贴格底 */
  min-height: 0;
  object-fit: contain;             /* 竖构图，不裁切 */
  object-position: bottom;         /* 贴格底 */
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.55));
  transition: transform var(--fx-dur) var(--fx-ease);
  /* 翻转与放大都走变量，避免和下面的 .acting 抢 transform */
  --mirror: 1;
  --art-scale: 1;
  transform: scaleX(var(--mirror)) scale(var(--art-scale));
  position: relative;              /* 宠物节点 .unit-pet 要把 z-index 压在立绘之下 */
  z-index: 2;
}
/* 敌方立绘水平翻转。只翻 .unit-art：翻整个 .unit 会把单位名也照成镜像，读不了。
   方向由 <battlefield> 的 dir-* 类决定，CSS 里不出现任何写死的方向。
   源图统一面朝左：dir-right（我方在左往右打）时敌方本来就朝左，不翻；
   dir-left（我方在右往左打）时敌方在左应朝右，才需要镜像。 */
.dir-left  .unit[data-side="enemy"] .unit-art { --mirror: -1; }
/* 与敌方翻转对称：dir-left（我方在右往左打）时我方在右应朝左迎敌，需镜像；
   dir-right（默认）我方在左本就朝右，不翻。 */
.dir-left  .unit[data-side="ally"] .unit-art { --mirror: -1; }
.unit[data-boss="1"] .unit-art { --art-scale: 1.25; }
/* 立绘缺失时的占位：用阵营纹章顶上，避免深色背景上出现一片空白 */
.unit-art-none {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius);
  background: var(--bg-2);
  border: 1px dashed var(--line-strong);
}
.unit-art-none svg { opacity: 0.7; }

/* 出战宠物：站在目标英雄**前面**（朝敌侧），与英雄**并排**而不是叠在它身上。
   z-index 低于英雄立绘，不拦截点击。方向随 <root> 的 dir-* 翻转。

   【2026-10-03 实机修复：宠物与英雄糊成一坨】
   旧版这里写着 width: 46%，但下面 `.sprite.unit-pet { width: 100% }`（当初为"与英雄立绘
   同一档降采样"加的）把它顶成了 100% —— 宠物实际和英雄立绘一样宽、一样方，整只压在英雄的
   腿和武器上，玩家截图里就是"英雄和携带的宠物堆叠到一起"。
   那条"同档降采样"的理由**早已失效**：宠物改走 256 cell 的 pets.webp 之后，38% × 52px ≈ 20px
   的承载盒对应 12.8× 降采样，比主图集口径下的同尺寸更细，没有继续放大的理由。
   现在：英雄立绘收窄到 70% 靠**背敌侧**，宠物 38% 贴**朝敌侧**，中间只擦过约 4% 的边
   （刻意留一点压边，读起来才有"站在前面"的纵深，而不是两张并排贴纸）。 */
.unit-pet {
  position: absolute;
  right: -5%;
  bottom: 16px;                    /* 抬到名字/血条叠层之上，与英雄并肩贴底 */
  width: 40%;
  aspect-ratio: 1 / 1;             /* 正方：与英雄立绘同一套 contain 口径，加载前后不跳变 */
  object-fit: contain;
  object-position: bottom;
  z-index: 1;                       /* 低于 .unit-art（z-index:2），重叠时英雄压住宠物 */
  pointer-events: none;            /* 纯表现，不挡单位点击 */
  opacity: 0.98;
  /* 旧版这里有一圈半透明黑底 + 描边 —— 那是"宠物是英雄身上的一枚徽章"的视觉语言，
     也正是堆叠感的来源。改成纯立绘 + 一点投影，读起来才是"站在旁边的同伴"，
     也顺带让立绘边缘不再被那圈底框吃掉。 */
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.55));
}
/* dir-left 下我方朝左，宠物改贴左侧（朝敌侧），不写死方向 */
.dir-left .unit-pet { right: auto; left: -5%; }
/* 带宠物的英雄立绘让出前方空间：收窄到 68%，并靠背敌侧（dir-right 靠左、dir-left 靠右）。
   格子只有 ~52px 宽，两张立绘要并排就必须各让一步；不让的结果就是必然重叠。
   数值口径：英雄占 0~68%、宠物占 66~102%（探出格子 2%），水平只擦过约 2% 的边
   —— 留这一点压边是为了纵深，压得太多就又回到"糊在一起"。 */
.unit.has-pet .unit-art { width: 68%; align-self: flex-start; }
.dir-left .unit.has-pet .unit-art { align-self: flex-end; }

/* 出手抬手：带上 mirror 与 art-scale，否则会盖掉上面的翻转/放大 */
.unit.acting .unit-art,
.dir-right .unit[data-side="enemy"].acting .unit-art,
.dir-left  .unit[data-side="enemy"].acting .unit-art {
  transform: translateY(-5px) scale(var(--art-scale)) scaleX(var(--mirror));
}
.unit.dead .unit-art { opacity: 0.25; filter: grayscale(1); }
/* 战斗可读性再提升 · 出手提示：当前出手单位名高亮 + 头顶「出手」标签 */
.unit.acting .unit-name { color: #ffe08a; }
.actor-tag {
  position: absolute;
  transform: translateX(-50%);
  font-size: 10px;
  font-weight: 800;
  color: #fff;
  background: var(--gold-1);
  border-radius: 6px;
  padding: 1px 5px;
  z-index: 6;
  pointer-events: none;
  white-space: nowrap;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
}
/* 纹理图集精灵（#6）：承载元素用 background 子矩形显出本格。
   源图 512 正方，元素强制正方以复刻 <img> 的 object-fit:contain。 */
.sprite { background-repeat: no-repeat;
  /* spriteEl 设了 background-clip: content-box，但 background-origin 的初始值是 padding-box。
     两者不一致时：背景按 padding-box 定位、却只有 content-box 那块能看见 ——
     .hero-art 的 padding: 4px 于是让立绘每边被内裁 4·512/S 个图集像素
     （S=94 的英雄卡是每边 21.8 图集像素 = 格宽 4.25%，S=84 是 4.76%），
     贴边的武器尖/披风下摆会被切，且内裁量随承载盒尺寸漂移。
     把 origin 拉到 content-box，定位区与裁剪区重合，内裁归零。
     **这条不许删**：删掉就退回"立绘莫名被切四边 + 随尺寸漂移"的老毛病
     （改法只有两条 —— 这里用 origin，另一条是删掉 .hero-art 的 padding，
     但它在 img 形态/皮肤详情卡里还在起作用，波及面更大，故不选）。 */
  background-origin: content-box; }
/* 战斗立绘：原本是 <img> 占满格高 + object-fit:contain(bottom)。
   改为正方承载元素、贴格底，与 object-fit:contain 视觉等价。 */
.sprite.unit-art { flex: 0 0 auto; align-self: flex-end; width: 100%; aspect-ratio: 1 / 1; }
/* 出战宠物：尺寸口径**只留在 `.unit-pet` 一处**（width: 38% + aspect-ratio: 1/1），
   这里不再重复声明 —— 见上方那段修复注释：旧版正是在这里写 width:100% 把宠物的
   46% 意图顶掉，才让宠物和英雄一样宽、叠在一起（两条规则互相打架的典型）。
   `.sprite` 基类负责图集子矩形定位与 background-origin，尺寸一概不碰。 */
.unit-name {
  font-size: 9px;                   /* 瘦身：单位名可降到 9px，但不能压没 */
  color: var(--text-2);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* 名字压在图集精灵下缘（立绘不是纯色底，两侧 alpha 不一）。
     给字行加一条"反向"遮罩带：深底用黑带托亮字、浅底用白带压暗字，
     按 var(--unit-scrim) 主题反色，立绘本体一点不动。
     20% 处到满 alpha：字身（内容盒从 5px 起，占带高 ~31%）整段吃满遮罩；
     40% 会让字顶那一行只剩 0.58 黑，压不住浅灰立绘底(#B2B2B3)，实测只有 4.1:1。 */
  padding: 5px 4px 1px;
  background: linear-gradient(180deg, transparent, var(--unit-scrim) 20%);
}
.unit-hp {
  width: 100%;
  height: 4px;                     /* 瘦身：血条略收窄，仍可读 */
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.5);
  overflow: hidden;
  border: 1px solid rgba(0, 0, 0, 0.4);
}
.unit-hp > i {
  display: block;
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--success), #7FE0AC);
  transition: width var(--fx-dur) linear;
}
.unit-hp.enemy > i { background: linear-gradient(90deg, var(--danger), #FF8A8E); }
/* 战斗可读性再提升 · 低血预警配色：血条按血量分级（≥60% 绿 / 30–60% 黄 / <30% 红） */
.unit-hp > i.mid { background: linear-gradient(90deg, #F0A93B, #FFD27A); }
.unit-hp > i.low { background: linear-gradient(90deg, #E0392B, #FF7A7A); }
.unit-hp.enemy > i.low { background: linear-gradient(90deg, #FF2B2B, #FF6B6B); }
.unit.low-hp .unit-hp-num { color: #ffd2d2; border-color: rgba(255, 120, 120, 0.85); }
/* O7 可读性：HP% 角标（绝对定位、不占布局；贴在右上角，避开左上角的阵营纹章）。
   深底浅字 + 绿描边，儿童一眼能读出「谁快没血了」。pointer-events:none 不挡点击。 */
.unit-hp-num {
  position: absolute;
  top: 1px;
  right: 1px;
  z-index: 4;
  font-size: 9px;
  font-weight: 700;
  line-height: 1.2;
  padding: 1px 3px;
  border-radius: 6px;
  color: #eafff2;
  background: rgba(8, 14, 24, 0.72);
  border: 1px solid rgba(63, 191, 127, 0.55);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  white-space: nowrap;
}
/* Boss 角标换金色调，与放大立绘 + 分段血条区分度叠加，进一步强化「这是大怪」的读感 */
.unit[data-boss="1"] .unit-hp-num {
  color: #ffe9b0;
  background: rgba(40, 26, 4, 0.78);
  border-color: rgba(255, 196, 64, 0.75);
}
/* Boss 王冠标记：纯 CSS（复用已有 [data-boss="1"] 钩子），零布局位移、零 JS、零节点池改动 */
.unit[data-boss="1"] .unit-name::before {
  content: "\1F451";
  margin-right: 2px;
}
/* v0.9.71（R4）：血条本体强化——加内描边 + 低血外发光，儿童更易读出血量变化（纯表现，零数值） */
.unit-hp { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .35), 0 1px 2px rgba(0, 0, 0, .4); }
.unit-hp > i { box-shadow: 0 0 4px rgba(127, 224, 172, .5); }
.unit-hp.enemy > i { box-shadow: 0 0 4px rgba(255, 138, 142, .5); }
.unit-hp > i.low { box-shadow: 0 0 7px rgba(224, 57, 43, .85); }
.unit-hp.enemy > i.low { box-shadow: 0 0 7px rgba(255, 43, 43, .85); }
/* v0.9.71（R4）：受击脉冲——单位挨打时立绘短促缩放 + 提亮，强化"被打到了"的读感（纯表现，零数值）。
   只动 transform/filter，不挤动立绘布局（与 .acting / 偏移 transform 互不冲突）。 */
.unit.hurt .unit-art { animation: unitHurt 160ms ease-out; }
@keyframes unitHurt {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.07); filter: brightness(1.25); }
  100% { transform: scale(1); }
}
.unit-shield {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(76, 154, 255, 0.5) inset;
  pointer-events: none;
}

/* ---------------------------------------------------------------- ⑤ Boss 分段血条 + 阶段切换演出
   纯表现层，零数值风险。分隔刻度固定在 enrageAt 比例处（=50%），与填充缩水无关，
   提前预告「还有一段」；sim 在 Boss 血量过半推 { type:'enrage' } 时由 JS 挂 .enraged
   类 + 屏闪 + 「狂暴！」横幅。刻度不动 transform，故不抢 .acting / 同格偏移的 transform。 */
.unit-hp-seg {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;          /* 以 left 为中线 */
  background: rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  z-index: 2;
  transition: background var(--fx-dur), box-shadow var(--fx-dur);
}
/* 进入狂暴后：刻度转红并加发光，强化「这一阶段更凶」的读感 */
.unit.enraged .unit-hp-seg {
  background: var(--danger);
  box-shadow: 0 0 4px var(--danger);
}
/* 狂暴态立绘：红光 + 一次性脉冲（只用 filter，不碰 transform，避免和 .acting / 偏移打架） */
.unit.enraged .unit-art {
  animation: unitEnrage 480ms var(--fx-ease);
  filter: drop-shadow(0 0 7px var(--danger)) drop-shadow(0 3px 6px rgba(0, 0, 0, 0.55));
}
@keyframes unitEnrage {
  0%   { filter: drop-shadow(0 0 0 var(--danger)) brightness(1); }
  35%  { filter: drop-shadow(0 0 14px var(--danger)) brightness(1.5) saturate(1.4); }
  100% { filter: drop-shadow(0 0 7px var(--danger)) brightness(1); }
}
/* 屏闪：覆盖整个战场，红闪后淡出 */
.enrage-flash {
  position: absolute;
  inset: 0;
  z-index: 20;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 45%, rgba(255, 64, 64, 0.42), rgba(255, 64, 64, 0.12) 60%, transparent 100%);
  animation: enrageFlash 540ms ease-out forwards;
}
@keyframes enrageFlash {
  0%   { opacity: 0; }
  18%  { opacity: 1; }
  100% { opacity: 0; }
}
/* 「狂暴！」横幅：战场居中，上浮淡出 */
.enrage-banner {
  position: absolute;
  left: 50%;
  top: 38%;
  transform: translate(-50%, -50%);
  z-index: 21;
  pointer-events: none;
  font-size: 34px;
  font-weight: 900;
  letter-spacing: 4px;
  color: #fff;
  text-shadow: 0 0 10px var(--danger), 0 2px 6px rgba(0, 0, 0, 0.8);
  animation: enrageBanner 1500ms var(--fx-ease) forwards;
}
@keyframes enrageBanner {
  0%   { opacity: 0; transform: translate(-50%, -30%) scale(0.7); }
  16%  { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
  30%  { transform: translate(-50%, -50%) scale(1); }
  78%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -72%) scale(1); }
}
/* 高性能模式：关掉所有 enrage 动画与覆盖层，避免低端机掉帧 */
.high-perf .enrage-flash,
.high-perf .enrage-banner { display: none !important; }
/* 战斗可读性再提升 · 胜负结算横幅（复用 enrage 的屏闪 + 上浮淡出模式） */
.result-flash {
  position: absolute; inset: 0; z-index: 22; pointer-events: none;
  animation: resultFlash 720ms ease-out forwards;
}
.result-flash.win { background: radial-gradient(120% 90% at 50% 45%, rgba(120,220,150,.32), rgba(63,191,127,.10) 60%, transparent 100%); }
.result-flash.lose { background: radial-gradient(120% 90% at 50% 45%, rgba(150,160,180,.30), rgba(90,100,120,.10) 60%, transparent 100%); }
.result-banner {
  position: absolute; left: 50%; top: 42%; transform: translate(-50%, -50%);
  z-index: 23; pointer-events: none; text-align: center;
  font-size: 40px; font-weight: 900; letter-spacing: 4px;
  animation: resultBanner 1500ms var(--fx-ease) forwards;
}
.result-banner.win { color: #fff; text-shadow: 0 0 14px var(--success), 0 2px 8px rgba(0,0,0,.8); }
.result-banner.lose { color: #fff; text-shadow: 0 0 14px var(--text-2), 0 2px 8px rgba(0,0,0,.8); }
@keyframes resultFlash { 0% { opacity: 0; } 18% { opacity: 1; } 100% { opacity: 0; } }
@keyframes resultBanner {
  0% { opacity: 0; transform: translate(-50%, -30%) scale(.7); }
  16% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
  30% { transform: translate(-50%, -50%) scale(1); }
  78% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -72%) scale(1); }
}
.high-perf .result-flash, .high-perf .result-banner { display: none !important; }
/* #181 VFX 深化：低端机（.high-perf）下整层演出覆盖关掉，与 .enrage-flash 同纪律（照建不报错） */
.high-perf .vfx { display: none !important; }
.high-perf .unit.enraged .unit-art { animation: none !important; }

/* ⑥ 连击 Combo HUD（纯表现层，不改结算）：战场右上角大字，命中跳动、满 10 放大 */
.combo-hud {
  position: absolute;
  right: 10px;
  top: 10px;
  z-index: 19;
  pointer-events: none;
  font-size: 22px;
  font-weight: 900;
  letter-spacing: 1px;
  color: #ffd86b;
  text-shadow: 0 0 10px rgba(255, 180, 40, .9), 0 2px 6px rgba(0, 0, 0, .8);
  transform-origin: right top;
}
.combo-hud.combo-pop { animation: comboPop 220ms ease-out; }
.combo-hud.combo-milestone { color: #fff; font-size: 30px; text-shadow: 0 0 16px var(--danger), 0 2px 8px rgba(0, 0, 0, .85); }
@keyframes comboPop {
  0%   { transform: scale(0.8); opacity: 0.6; }
  40%  { transform: scale(1.18); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
/* ⑦ Boss 暴走全屏预警：覆盖战场的大红字 + 脉冲（纯表现层，复用 enrage 事件） */
.enrage-warn {
  position: absolute;
  left: 0; right: 0; top: 28%;
  z-index: 24;
  pointer-events: none;
  text-align: center;
  font-size: 30px;
  font-weight: 900;
  letter-spacing: 6px;
  color: #ff5a5a;
  text-shadow: 0 0 16px rgba(255, 40, 40, .9), 0 2px 8px rgba(0, 0, 0, .85);
  background: linear-gradient(180deg, rgba(120, 0, 0, .34), rgba(120, 0, 0, .12));
  padding: 10px 0;
  border-top: 2px solid rgba(255, 80, 80, .8);
  border-bottom: 2px solid rgba(255, 80, 80, .8);
  animation: enrageWarn 1600ms ease-out forwards;
}
@keyframes enrageWarn {
  0%   { opacity: 0; transform: scale(1.2); }
  12%  { opacity: 1; transform: scale(1); }
  22%  { transform: scale(1.05); }
  32%  { transform: scale(1); }
  70%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.06); }
}
.high-perf .enrage-warn { display: none !important; }

/* ⑩ 战斗间隙「小知识」科普条（纯表现层，零立绘）：战场底部居中小贴士，自动淡出 */
.battle-funfact {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  z-index: 18;
  pointer-events: none;
  max-width: 92%;
  padding: 5px 14px;
  font-size: 13px;
  font-weight: 600;
  color: #eaf0ff;
  background: rgba(18, 22, 38, .82);
  border: 1px solid rgba(139, 107, 255, .5);
  border-radius: 999px;
  box-shadow: 0 2px 10px rgba(139, 107, 255, .28);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.battle-funfact.fact-pop { animation: factPop 260ms ease-out; }
@keyframes factPop {
  0%   { opacity: 0; transform: translateX(-50%) translateY(6px); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* P1 留存反馈：成就达成全屏庆祝（金色，复刻 enrage 的 flash + banner 模式，但全局、任意页面可见） */
.milestone-flash {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 45%, rgba(255, 212, 121, 0.40), rgba(245, 178, 60, 0.14) 60%, transparent 100%);
  animation: milestoneFlash 600ms ease-out forwards;
}
@keyframes milestoneFlash {
  0%   { opacity: 0; }
  18%  { opacity: 1; }
  100% { opacity: 0; }
}
/* 「成就达成」横幅：视口居中，上浮淡出（标题 + 成就名两行） */
.milestone-banner {
  position: fixed;
  left: 50%;
  top: 40%;
  transform: translate(-50%, -50%);
  z-index: 91;
  pointer-events: none;
  text-align: center;
  animation: milestoneBanner 1800ms var(--fx-ease) forwards;
}
.milestone-banner-title {
  font-size: 30px;
  font-weight: 900;
  letter-spacing: 3px;
  color: #fff;
  text-shadow: 0 0 12px var(--gold-1), 0 2px 8px rgba(0, 0, 0, 0.8);
}
.milestone-banner-name {
  margin-top: 6px;
  font-size: 20px;
  font-weight: 700;
  color: var(--gold-1);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.8);
}
@keyframes milestoneBanner {
  0%   { opacity: 0; transform: translate(-50%, -30%) scale(0.7); }
  14%  { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
  28%  { transform: translate(-50%, -50%) scale(1); }
  76%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -72%) scale(1); }
}
/* 高性能模式：关掉庆祝动画与覆盖层，避免低端机掉帧 */
.high-perf .milestone-flash,
.high-perf .milestone-banner { display: none !important; }

/* v0.9.61 成就里程碑可视化 ①：首页（远征页）成就概览卡 —— 环形进度 */
.ach-ring {
  position: relative;
  width: 64px; height: 64px;
  border-radius: 50%;
  flex: 0 0 auto;
}
.ach-ring-inner {
  position: absolute; inset: 7px;
  border-radius: 50%;
  background: var(--bg-1);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  line-height: 1.15;
}
.ach-ring-num { font-size: 18px; font-weight: 800; color: var(--gold-1); }
.ach-next > i { background: linear-gradient(90deg, var(--gold-1), var(--gold-2, #d9982a)); }

/* v0.9.61 ②：成就墙分组（每组一条进度条） */
.ms-group { margin-top: 10px; }
.ms-group-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.ms-group-label { font-weight: 700; }
.ms-group-stat { margin-left: auto; }
.ms-group-bar { height: 6px; }
.ms-group-body { margin-top: 4px; }

/* v0.9.61 ③：阶段徽章（铜 / 银 / 金）+ 百分比 chip */
.ms-metal {
  flex: 0 0 auto;
  font-size: var(--fs-xs); font-weight: 800; line-height: 1;
  padding: 2px 6px; border-radius: 999px; color: #1a1205;
}
.ms-metal-bronze { background: linear-gradient(160deg, #d9a066, #b9763c); }
.ms-metal-silver { background: linear-gradient(160deg, #dfe6ee, #aeb9c6); color: #2a2f36; }
.ms-metal-gold   { background: linear-gradient(160deg, var(--gold-1), var(--gold-2, #d9982a)); }
.ms-pct {
  font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text-1);
  min-width: 38px; text-align: right;
}
.ms-slot { display: inline-flex; align-items: center; gap: 6px; }

/* v0.9.61 ④：庆祝演出 —— 奖励预览 + 星星 / 彩带（.high-perf 下整层关掉，避免低端机掉帧） */
.milestone-banner-reward {
  margin-top: 8px; font-size: 15px; font-weight: 600; color: #fff;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.8);
}
.milestone-confetti-layer {
  position: fixed; inset: 0; z-index: 92; pointer-events: none; overflow: hidden;
}
.ms-confetti {
  position: absolute; top: -14px; width: 8px; height: 12px; border-radius: 2px;
  opacity: 0; animation: msConfetti 1700ms var(--fx-ease) forwards;
}
.ms-star {
  position: absolute; top: 8%; font-size: 20px; color: var(--gold-1);
  opacity: 0; text-shadow: 0 0 8px var(--gold-1);
  animation: msStar 1700ms var(--fx-ease) forwards;
}
@keyframes msConfetti {
  0%   { opacity: 0; transform: translateY(0) rotate(0deg); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(78vh) rotate(540deg); }
}
@keyframes msStar {
  0%   { opacity: 0; transform: scale(0.4) translateY(0); }
  20%  { opacity: 1; transform: scale(1.1) translateY(-6px); }
  70%  { opacity: 1; transform: scale(1) translateY(-2px); }
  100% { opacity: 0; transform: scale(0.8) translateY(20px); }
}
.high-perf .milestone-confetti-layer { display: none !important; }

/* 高对比度：刻度纯白更醒目 */
.high-contrast .unit-hp-seg { background: #fff; box-shadow: none; }

.unit-faction {
  position: absolute;
  top: 0;
  left: 0;
  width: 12px;
  height: 12px;
  opacity: 0.9;
}

/* 飘字 */
/* v0.9.65：z-index 抬到 .battle-item-layer(3) 之上，避免道具图标遮挡技能提示飘字（飘字 pointer-events:none，不挡图标点击） */
.float-layer { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 4; }
/* v0.9.53 战斗内道具图层：覆盖在棋盘之上，只让图标本身可点，其余穿透到战场 */
.battle-item-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  overflow: hidden;
}
.battle-item {
  position: absolute;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  border: none;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  background: rgba(18, 22, 38, 0.86);
  color: #fff;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  animation: itemBreath 1.6s ease-in-out infinite;
}
.battle-item:active { transform: scale(0.9); }
.battle-item.bi-common { box-shadow: 0 4px 14px rgba(0,0,0,0.5), inset 0 0 0 2px #9fb2d8; }
.battle-item.bi-rare   { box-shadow: 0 4px 16px rgba(80,160,255,0.5), inset 0 0 0 2px #5aa0ff; color: #d6ecff; }
.battle-item.bi-epic   { box-shadow: 0 4px 18px rgba(180,90,255,0.6), inset 0 0 0 2px #c08bff; color: #f0ddff; }
@keyframes itemBreath {
  0%, 100% { box-shadow: 0 4px 14px rgba(0,0,0,0.5), inset 0 0 0 2px rgba(255,255,255,0.25); }
  50% { box-shadow: 0 4px 22px rgba(255,255,255,0.4), inset 0 0 0 2px rgba(255,255,255,0.6); }
}
/* 低端机降级：关掉呼吸动画，降低重绘（.high-perf 由 perfWatch 在低端机加上） */
.high-perf .battle-item { animation: none; }

.float {
  position: absolute;
  font-weight: 700;
  font-size: var(--fs-sm);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  animation: floatUp 900ms var(--fx-ease) forwards;
  white-space: nowrap;
}
.float.dmg { color: var(--float-dmg); }
.float.crit { color: var(--gold-1); font-size: var(--fs-lg); }
.float.strong { color: var(--success); font-size: var(--fs-md); }
.float.weak { color: var(--text-2); }
.float.heal { color: var(--float-heal); }
/* ⑨ 施法演出：技能名飘字（横扫 / 连射 / 雷击 / 奥术连弹…），青色描边与伤害飘字区分 */
.float.cast {
  color: var(--skill-cast, #8ad7ff);
  font-size: var(--fs-md);
  letter-spacing: 0.5px;
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.95), 0 0 2px var(--skill-cast, #8ad7ff);
}

@keyframes floatUp {
  0% { opacity: 0; transform: translateY(6px) scale(0.9); }
  18% { opacity: 1; transform: translateY(0) scale(1.05); }
  100% { opacity: 0; transform: translateY(-34px) scale(1); }
}
/* P2 暴击演出增强：暴击飘字加一次性放大脉冲（打击感），只覆盖 .crit，不碰其它飘字/布局 */
.float.crit { animation: critPop 900ms var(--fx-ease) forwards; }
@keyframes critPop {
  0%   { opacity: 0; transform: translateY(6px) scale(0.6); }
  14%  { opacity: 1; transform: translateY(-2px) scale(1.4); }
  30%  { transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-34px) scale(1); }
}
/* #181 VFX 深化：命中 / 暴击 / 技能施放 / 治疗 / 护盾 / 死亡 的瞬时演出覆盖层。
   覆盖层挂在 .float-layer（pointer-events:none、overflow:hidden），由 JS setTimeout 自清。
   纯表现层，不影响任何结算；.high-perf 下整层 display:none（见上方 .high-perf .vfx 规则）。
   径向渐变从中心亮到边缘透明，叠在立绘上即一圈打击光晕，不依赖 mix-blend，深浅主题都可读。 */
.vfx { position: absolute; pointer-events: none; border-radius: 14px; }
.vfx.hit-flash  { background: radial-gradient(circle at 50% 55%, rgba(255,255,255,0.55), rgba(255,210,120,0.18) 60%, transparent 72%); animation: vfxHit 160ms var(--fx-ease) forwards; }
.vfx.crit-flash { background: radial-gradient(circle at 50% 55%, rgba(255,240,180,0.85), rgba(245,178,60,0.35) 55%, transparent 70%); animation: vfxCrit 240ms var(--fx-ease) forwards; }
.vfx.cast-burst { background: radial-gradient(circle at 50% 55%, rgba(138,215,255,0.0), rgba(138,215,255,0.5) 40%, transparent 68%); animation: vfxCast 280ms var(--fx-ease) forwards; }
.vfx.heal-glow  { background: radial-gradient(circle at 50% 55%, rgba(110,231,168,0.0), rgba(110,231,168,0.45) 45%, transparent 70%); animation: vfxHeal 320ms var(--fx-ease) forwards; }
.vfx.shield-shimmer { background: radial-gradient(circle at 50% 55%, rgba(120,200,255,0.0), rgba(120,200,255,0.4) 45%, transparent 70%); animation: vfxShield 320ms var(--fx-ease) forwards; }
.vfx.death-flash { background: radial-gradient(circle at 50% 55%, rgba(20,22,34,0.0), rgba(20,22,34,0.55) 50%, transparent 72%); animation: vfxDeath 300ms var(--fx-ease) forwards; }

@keyframes vfxHit   { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }
@keyframes vfxCrit  { 0% { opacity: 0; transform: scale(0.7); } 20% { opacity: 1; transform: scale(1.1); } 100% { opacity: 0; transform: scale(1); } }
@keyframes vfxCast  { 0% { opacity: 0; transform: scale(0.6); } 30% { opacity: 1; transform: scale(1.15); } 100% { opacity: 0; transform: scale(1); } }
@keyframes vfxHeal  { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }
@keyframes vfxShield{ 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }
@keyframes vfxDeath { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }
/* 分享卡预览图（P2 传播）：首屏内联显示，限制宽高不溢出窄屏 */
.share-canvas { width: 100%; max-width: 320px; height: auto; display: block; margin: 0 auto; box-shadow: 0 6px 20px rgba(0,0,0,0.4); }

/* 战斗控制条（05 §1.9）：暂停 / 快进。始终一行，窄屏才换行 */
.bf-ctrl {
  z-index: 2;                      /* 压在飘字层之上，否则飘字会挡住按钮 */
  margin-top: 4px;                 /* 收紧控制条与战场间的间距 */
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}
/* 战场内的按钮用紧凑尺寸（只作用于战场，不影响全局 .btn-sm） */
.bf-ctrl .btn-sm { min-height: 26px; padding: 3px 10px; }

/* 战斗明细日志（第9项 · 方案 B）：战场左、日志右栏常驻；窄屏回退成收起开关 */
.bf-field-wrap {
  display: flex;
  gap: 10px;
  align-items: stretch;
  width: 100%;
  /* ── 战场棋盘/立绘尺寸的「一套数」───────────────────────────────────────
     ① 立绘是正方形（.sprite.unit-art: width:100% + aspect-ratio:1/1），边长 = 单位宽，
        所以「单位最大宽度」必须 ≤ **行高**，否则正方形直接从格子里往上溢、压到上一排。
     ② 行高 = (棋盘高 − (行数-1) 条横 gap) / 行数；棋盘高只有一个输入 --bf-grid-min。
     ③ 宽屏（≥768px）的 --bf-grid-min 反过来由**日志定高反推**（177px → 行高 87、立绘上限 85）：
        align-items:stretch 给的上下沿对齐，**反面**才是要守的 —— 战场这栏一旦比日志框高，
        整条 flex 线就被顶高、上下沿又错开，所以这里不能另写一个"差不多"的定高。
     ④ 窄屏（≤767px，日志收起、没有对齐对象）这张表的输入就是棋盘自己的保底高，
        从 112px 抬到 139px（行高 56→68、立绘 52→66），战场整块只比改动前高 27px。 */
  /* 窄屏这套：没有日志框可对齐，行高由棋盘自己的保底高 --bf-grid-min 决定。
     旧值 112px（行高 56、立绘 52）；手机正是玩家真机在看的那一屏，只放大宽屏等于这条
     白做，所以抬到 139px（行高 68、立绘上限 66）—— 战场整块只比改动前高 27px。
     ⚠ --bf-unit-cap 是**从 --bf-row-h 算出来的**，窄屏这里也用同一条算式（不是另写一个数）；
     行高与立绘上限始终锁在一处，谁改高度另一边跟着动。 */
  --bf-gap: 3px;                       /* 棋盘横 gap，与 .bf-grid 的 gap 同一个数 */
  --bf-rows: 2;                        /* 棋盘行数，与 grid-template-rows 同一个数 */
  --bf-grid-min: 139px;                /* 窄屏棋盘保底高（改动前 112px） */
  --bf-row-h: calc((var(--bf-grid-min) - var(--bf-gap) * (var(--bf-rows) - 1)) / var(--bf-rows));
  --bf-unit-cap: min(100%, calc(var(--bf-row-h) - 2px));
  /* 宽屏那套：--bf-grid-min 会被 @media (min-width:768px) 换成从日志定高反推的值，
     --bf-row-h / --bf-unit-cap 自动跟着重算，这里不用再写一遍。 */
}
@media (min-width: 768px) {
  .bf-field-wrap {
    --bf-log-h: 220px;                 /* = .bf-log.right 的 height 定高 */
    --bf-pad-v: 13px;                  /* .battlefield 上下内边距 7 + 6 */
    --bf-ctrl-h: 30px;                 /* .bf-ctrl 一条：按钮 min-height 26 + margin-top 4 */
    /* 从日志定高**反推**棋盘高度：对齐靠 stretch，但战场这栏一旦比日志框高，
       整条 flex 线就被顶高、左右上下沿又错开，所以这里不能另写一个"差不多"的定高。 */
    --bf-grid-min: calc(var(--bf-log-h) - var(--bf-pad-v) - var(--bf-ctrl-h));
  }
}
.bf-field-wrap .battlefield {
  flex: 1 1 auto;
  min-width: 0;                 /* 防止战场内容把双栏撑破 */
  position: relative;           /* 飘字层绝对定位的参照（保持） */
}
.bf-log.right {
  flex: 0 0 270px;
  width: 270px;
  height: 220px;               /* 展开态固定高度：跨场恒定、不被日志行数撑变（修复③）。父子均靠它对齐，战场随之等高 */
  max-height: 430px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  margin-top: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  overflow: hidden;
}
/* 收起：只隐藏明细列表，标题栏与按钮保留 —— 否则按钮随面板一起消失，玩家收起后无处再开 */
.bf-log.right:not(.is-open) {
  flex: 0 0 40px;
  width: 40px;
  max-height: none;
}
.bf-log.right:not(.is-open) .bf-log-list { display: none; }
.bf-log.right:not(.is-open) .bf-log-title { display: none; }
.bf-log.right:not(.is-open) .bf-log-head { justify-content: center; padding: 6px 2px; }
.bf-log.right:not(.is-open) .bf-log-toggle {
  writing-mode: vertical-rl;
  letter-spacing: 2px;
  min-height: 0;
  padding: 8px 3px;
}
.bf-log-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 4px 8px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-2);
  flex: 0 0 auto;
}
.bf-log-title { font-size: var(--fs-sm); font-weight: 700; color: var(--text-1); }
.bf-log-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 24px;
  padding: 2px 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  color: var(--text-1);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.bf-log-toggle:hover { background: var(--bg-3); border-color: var(--gold-2); }
.bf-log-toggle:focus-visible { outline: 2px solid var(--gold-1); outline-offset: 2px; }

.bf-log-list {
  display: block;
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px 8px;
  font-size: var(--fs-xs);
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
}
.bf-log-line { white-space: nowrap; }
.lg-dmg { color: var(--text-2); }
.lg-crit { color: var(--gold-1); font-weight: 700; }
.lg-strong { color: var(--success); }
.lg-weak { color: var(--text-3); }
.lg-heal { color: var(--success); }
.lg-buff { color: var(--arcane-1); }
.lg-death { color: var(--danger); }
.lg-win { color: var(--success); font-weight: 700; }
.lg-lose { color: var(--danger); font-weight: 700; }
/* ⑤ Boss 阶段切换（狂暴）日志行：醒目红 */
.lg-enrage { color: var(--danger); font-weight: 700; }

/* 窄屏（手机）：双栏放不下，回退为上下布局 + 收起开关（默认收起不挤占加点面板） */
@media (max-width: 767px) {
  .bf-field-wrap { flex-direction: column; align-items: stretch; }
  /* 展开时给固定高度：日志行数变化不再改变面板尺寸 */
  .bf-log.right { flex: 0 0 auto; width: 100%; height: 190px; max-height: none; }
  .bf-log.right:not(.is-open) { height: auto; }
  .bf-log.right:not(.is-open) .bf-log-list { display: none; }
  .bf-log.right:not(.is-open) .bf-log-head { justify-content: space-between; padding: 4px 8px; }
  .bf-log.right:not(.is-open) .bf-log-title { display: block; }
  .bf-log.right:not(.is-open) .bf-log-toggle { writing-mode: horizontal-tb; letter-spacing: 0; min-height: 24px; padding: 2px 8px; }
}

/* 手动技能条（实时演算模式）：一个英雄一组按钮，冷却中置灰并显示剩余秒 */
.bf-cast {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 4px;
  min-width: 0;
}
.bf-cast-group {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 4px 5px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  min-width: 0;
}
.bf-cast-hero {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.btn-cast {
  padding: 4px 6px;
  font-size: var(--fs-xs);
  line-height: 1.25;
  text-align: center;
}
.btn-cast.is-cooling {
  opacity: 0.45;
}
.btn-cast:disabled {
  cursor: default;
}

/* 战斗状态 + 控制：**一行**（战斗中 · 第 N 关 / 自动闯关 / 自动技能 / 快进 / 暂停） */
.bf-status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;                 /* 窄屏（手机）允许折行，不横向溢出 */
  row-gap: 4px;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  font-size: var(--fs-sm);
  min-width: 0;
}
/* 状态条里的控制条不再单独占行：抹掉外边距，直接并进这一行 */
.bf-status .bf-ctrl {
  margin-top: 0;
  gap: 4px;
}
/* 这一行塞了 5 个东西，全程按最紧的弹药用：手机上也要压成一行，不横向溢出。
   关掉时（自动闯关/自动技能 变灰）宽度不变，避免一行在开关之间来回跳。 */
.bf-status { padding: 5px 8px; gap: 4px; }
.bf-status > .btn-sm,
.bf-status .bf-ctrl > .btn-sm { padding: 3px 6px; }
.bf-status .tag { padding: 1px 6px; }
/* 这一行里的按钮统一用战场内的紧凑尺寸：否则「自动闯关/自动技能」比旁边的
   「快进/暂停」高出 8px，一行里四种高度，看着像拼错的。 */
.bf-status .btn-sm {
  min-height: 26px;
  padding: 3px 8px;
}
.bf-status .tag {
  padding: 1px 7px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  border: 1px solid var(--line-strong);
  color: var(--text-2);
}
.bf-status.win { border-color: var(--success); }
.bf-status.lose { border-color: var(--danger); }
/* 需求 3：状态条只承载战斗结果。没结果时它是空节点，收起不占一行（避免凭空多出一条灰框）。 */
.bf-status:empty { display: none; }

/* ---------------------------------------------------------------- 英雄卡 */

.hero-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px;
  min-width: 0;
}

.hero-card {
  position: relative;
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background:
    linear-gradient(160deg, var(--bg-3), var(--bg-1) 62%);
  overflow: hidden;
  cursor: pointer;
  padding: 0;
  text-align: left;
  min-width: 0;
  transition: transform var(--fx-dur) var(--fx-ease);
}
.hero-card:active { transform: scale(0.97); }
.hero-card::before {
  /* 顶部冷高光：制造卡纸厚度感，成本最低的一招 */
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: rgba(255, 255, 255, 0.18);
}
.hero-card::after {
  /* 底部压暗：藏拙，让文字压得住 */
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 46%;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.62));
  pointer-events: none;
}

/* v0.9.101 #7：稀有度边框加粗（1px→2px）+ SSR 加辉光，配合下方显式文字 pill，
   让英雄品质从外观即可辨别（过去只有细边框 + 星级，儿童玩家分不清）。 */
.hero-card[data-rarity="r"]   { border-color: var(--rarity-r, #3FBF7F); border-width: 2px; }
.hero-card[data-rarity="sr"]  { border-color: var(--rarity-sr, #4C9AFF); border-width: 2px; }
.hero-card[data-rarity="ssr"] { border-color: var(--rarity-ssr, #A855F7); border-width: 2px; box-shadow: 0 0 0 1px rgba(168, 85, 247, 0.45), var(--shadow-card); }
.hero-card[data-rarity="ssr"][data-fx-on] .hero-art { filter: drop-shadow(0 0 10px rgba(168, 85, 247, 0.55)); }

/* 英雄卡稀有度文字 pill（R/SR/SSR 各自着色，白字 + 描边，压在立绘/暗带上清晰可读）。 */
.rarity-pill {
  font-size: var(--fs-xs);
  font-weight: 700;
  padding: 0 5px;
  border-radius: 999px;
  line-height: 1.5;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.55);
}
.hero-card[data-rarity="r"]   .rarity-pill { background: var(--rarity-r, #3FBF7F); }
.hero-card[data-rarity="sr"]  .rarity-pill { background: var(--rarity-sr, #4C9AFF); }
.hero-card[data-rarity="ssr"] .rarity-pill { background: var(--rarity-ssr, #A855F7); }

.hero-art {
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  padding: 4px;
}

.hero-meta {
  position: absolute;
  inset: auto 0 0 0;
  padding: 4px 5px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* 文字专用压暗带（只压这一条，不碰上面 54% 的立绘）。
     立绘两侧 alpha 不一（有的英雄浅、有的深），压 0.62 的底罩在字行上
     只剩 ~0.35，白字 / 金星压在浅色立绘本体上只有 2.9~3.5:1。
     这里再叠一条 0.62→0.88 的带子，字行落点必然 ≥0.8 黑，稳定过 4.5:1。 */
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0.88));
}
.hero-name {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--on-dark-1);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hero-sub {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  color: var(--on-dark-2);
}

.crest { width: 11px; height: 11px; flex: 0 0 auto; }
.crest path, .crest circle, .crest line, .crest ellipse { fill: none; stroke: #FFF; stroke-width: 1.6; }

.role-dot {
  width: 7px;
  height: 7px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.role-guard { background: var(--role-guard); }
.role-warrior { background: var(--role-warrior); }
.role-ranger { background: var(--role-ranger); }
.role-arcane { background: var(--role-arcane); }

.star-row {
  display: flex;
  gap: 1px;
  font-size: 9px;
  color: var(--gold-on-dark);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}

.badge-lv {
  position: absolute;
  top: 4px;
  left: 4px;
  z-index: 2;
  padding: 1px 5px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.66);
  border: 1px solid var(--line-strong);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--on-dark-1);
}
.badge-heal {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 2;
  width: 16px;
  height: 16px;
  color: var(--success);
}

/* 英雄卡「可加点」角标（2026-10-02 用户实测新增）：
   金币够 + 该英雄加点未到上限时才挂，帮玩家一眼看出谁还能提属性。
   左上 .badge-lv 是等级、战斗态 .badge-heal 占右上，
   所以这个角标压右下，不抢位置。 */
/* 需求 ④：已拥有但还没穿的皮肤。与「穿着中」的星芒角标互斥、共用右下角同一格；
   刻意的紫色方牌 + 「肤」字：形状与星芒完全不同，去色灰度下也分得开。 */
.hero-skin-own {
  position: absolute;
  right: 4px;
  bottom: 4px;
  z-index: 2;
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid var(--arcane-2);
  color: var(--arcane-1);
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
}

.hero-up-badge {
  position: absolute;
  right: 4px;
  bottom: 4px;
  z-index: 2;
  padding: 1px 5px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid var(--warning);
  font-size: 9px;
  font-weight: 600;
  color: var(--warning);
}

/* ---------------------------------------------------------------- 羁绊条 */

.bond-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius);
  background: var(--bg-2);
  border: 1px solid var(--line);
  font-size: var(--fs-sm);
  min-width: 0;
  flex-wrap: wrap;
}
/* 需求 3：战斗前置控件（自动闯关 / 自动技能 / 快进 / 暂停）从状态条搬到 bond-bar 最前，
   与羁绊 chip 同行。bond-ctrl 自身是一行可折行的小控件组，窄屏（手机）允许折行不横向溢出。 */
.bond-ctrl {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  min-width: 0;
}
/* 沿用战场内紧凑按钮尺寸，与旁边 chips 一行等高（否则「自动闯关/自动技能」比「快进/暂停」高出一截） */
.bond-ctrl .btn-sm { min-height: 26px; padding: 3px 6px; }

/* 战力来源拆解（v0.9.21）：每行 = 标签 + 一根占比条 + "去掉它会少多少"的数值 + 归一化占比。
   占比条刻意不用 width 过渡 —— 面板每秒不重绘，只在展开/收起时换一次 DOM，动画没有意义。 */
.pw-detail { display: flex; flex-direction: column; gap: 3px; margin-top: 6px; }
.pw-row { align-items: center; gap: 6px; }
.pw-label { flex: 0 0 72px; font-size: var(--fs-sm, 13px); }
.pw-bar {
  flex: 1 1 auto; min-width: 40px; height: 6px; border-radius: 3px;
  background: rgba(127, 127, 127, 0.28); overflow: hidden;
}
.pw-bar-fill { display: block; height: 100%; background: var(--arcane-1, #7aa2ff); }
.pw-pct { flex: 0 0 34px; text-align: right; }
/* 把「快进/暂停」内部那条 .bf-ctrl 的 4px 顶距抹掉，否则在 bond-bar 里会下沉一行 */
.bond-ctrl .bf-ctrl { margin-top: 0; gap: 4px; }
/* bond-info 只作语义容器，让内部 chips 直接成为 bond-bar 的 flex 子项，保证与控件同行 */
.bond-info { display: contents; }

/* 需求 4：按住持续加点的按钮。触摸设备上必须关掉长按的默认行为
   （系统文本选中 / 长按弹出菜单 / 双击缩放），否则按住加点会被打断。 */
.btn-hold {
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* 需求 6：合并页子标签上的未读红点（如「背包」有新装备时） */
.sub-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 4px;
  border-radius: 50%;
  background: var(--danger);
  vertical-align: middle;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--bg-1);
  font-size: var(--fs-xs);
  white-space: nowrap;
}
.chip.on { border-color: var(--success); color: var(--success); }
.chip.counter-strong { border-color: var(--success); color: var(--success); }
.chip.counter-weak { border-color: var(--danger); color: var(--danger); }

/* 自动加点的属性选择：chip 当按钮使（胶囊外观沿用 .chip，只补交互态） */
button.chip {
  min-height: 28px;
  cursor: pointer;
  transition: transform var(--fx-dur) var(--fx-ease), filter var(--fx-dur) var(--fx-ease);
}
button.chip:active { transform: scale(0.97); }
button.chip:focus-visible { outline: 2px solid var(--gold-1); outline-offset: 2px; }
button.chip:disabled { opacity: 0.45; cursor: not-allowed; filter: grayscale(0.5); }

/* ---------------------------------------------------------------- 进度与数值 */

.bar {
  height: 8px;
  border-radius: 5px;
  background: var(--bg-3);
  overflow: hidden;
  border: 1px solid var(--line);
}
.bar > i {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--arcane-2), var(--arcane-1));
}
.bar.hp > i { background: linear-gradient(90deg, var(--success), #7FE0AC); }

.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 10px;
  font-size: var(--fs-sm);
}
.kv dt { color: var(--text-2); }
.kv dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

.up { color: var(--success); }
.down { color: var(--danger); }

/* ---------------------------------------------------------------- 弹窗 */

.modal-scrim {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg-scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  animation: fadeIn 160ms var(--fx-ease);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.modal {
  width: 100%;
  max-width: 560px;
  max-height: 88dvh;
  overflow-y: auto;
  background: var(--bg-1);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-pop);
  padding: 14px;
  padding-bottom: calc(14px + env(safe-area-inset-bottom));
  animation: slideUp 220ms var(--fx-ease);
  min-width: 0;
}
@keyframes slideUp { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }

.modal-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.modal-head h3 { margin: 0; font-size: var(--fs-lg); }
.modal-close {
  margin-left: auto;
  width: var(--touch-min);
  height: var(--touch-min);
  min-height: var(--touch-min);
  padding: 0;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--bg-2);
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  flex: 0 0 auto;
}
/* 关闭图标是两条 <line>：SVG 里 <line> 默认 stroke:none，不给笔触就整条不渲染，
   按钮只剩一个空方块（v0.7.5 修）。笔宽 1.8 比导航图标略粗，保证 44×44 触摸目标里看得清。 */
.modal-close svg {
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
}

/* ---------------------------------------------------------------- Toast */

#toast-root {
  position: fixed;
  left: 50%;
  bottom: calc(var(--nav-h) + 16px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  pointer-events: none;
  width: max-content;
  max-width: 92vw;
}
.toast {
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--bg-3);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-pop);
  font-size: var(--fs-sm);
  animation: toastIn 200ms var(--fx-ease);
  text-align: center;
}
.toast.ok { border-color: var(--success); }
.toast.warn { border-color: var(--warning); }
.toast.err { border-color: var(--danger); }
@keyframes toastIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ---------------------------------------------------------------- 离线结算弹窗 */

.offline-big {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px;
  border-radius: var(--radius);
  background: linear-gradient(160deg, var(--bg-3), var(--bg-2));
  border: 1px solid var(--line-strong);
  text-align: center;
}
.offline-big .v { font-size: var(--fs-xl); font-weight: 700; color: var(--gold-1); }

/* ---------------------------------------------------------------- 招募 */

.pull-result {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
}
.pull-item {
  position: relative;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  overflow: hidden;
  background: var(--bg-2);
  animation: popIn 260ms var(--fx-ease) backwards;
}
@keyframes popIn { from { opacity: 0; transform: scale(0.82); } to { opacity: 1; transform: none; } }
.pull-item[data-rarity="SR"] { border-color: var(--rarity-sr); }
.pull-item[data-rarity="SSR"] { border-color: var(--rarity-ssr); box-shadow: 0 0 10px rgba(168, 85, 247, 0.4); }

/* A9 抽卡/招募演出强化：十连闪光 + 英雄登场。 */
/* 十连闪光：结果卡上方一层径向爆发，每次重抽都会随卡片重建重新播放。 */
.pull-card { position: relative; overflow: hidden; }
.pull-flash {
  position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background: radial-gradient(circle at 50% 42%, rgba(255,255,255,0.92), rgba(168,85,247,0.35) 42%, transparent 72%);
  opacity: 0; animation: pullFlash 720ms var(--fx-ease) forwards;
}
@keyframes pullFlash {
  0%   { opacity: 0; transform: scale(0.55); }
  28%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.25); }
}
/* 新英雄登场：金边高亮 + 扫光，强调"这是第一次拿到"。 */
.pull-item.debut { border-color: var(--gold-1); box-shadow: 0 0 14px rgba(255, 215, 120, 0.55); animation: debutPop 440ms var(--fx-ease) backwards; }
.pull-item.debut::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 215, 120, 0.32), transparent 58%);
  animation: debutSweep 900ms var(--fx-ease) forwards;
}
@keyframes debutPop { from { opacity: 0; transform: scale(0.58) translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes debutSweep { from { transform: translateY(-110%); } to { transform: translateY(110%); } }

/* ---------------------------------------------------------------- 详情抽屉 */

.detail-hero {
  display: flex;
  gap: 12px;
  min-width: 0;
}
.detail-hero > img {
  width: 96px;
  height: 96px;
  object-fit: contain;
  flex: 0 0 auto;
  border-radius: var(--radius);
  background: radial-gradient(60% 60% at 50% 40%, rgba(139, 107, 255, 0.2), transparent);
}

/* ⑨ 角色一句话简介卡（纯展示，帮助儿童快速认识英雄 / 宠物） */
.hero-bio { border-left: 3px solid var(--accent, #8b6bff); }
.hero-bio-text { font-size: var(--fs-md, 15px); line-height: 1.5; color: var(--text-1); }
.pet-bio { margin-top: 4px; line-height: 1.5; }

.slot-list { display: flex; flex-direction: column; gap: 6px; }
/* 任务 / 成就行：比 slot-row 高一点，因为下面还要挂一条进度条 */
.quest-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
/* 成就"刚达成未领"：金边高亮 + 入场脉冲，提示孩子有奖励可领（成就墙反馈增强）。
   只靠颜色不够（灰阶模式也认得出），所以外加描边 + 一枚"新达成"胶囊徽标。 */
.milestone-fresh {
  border-color: var(--gold-1);
  box-shadow: 0 0 0 1px var(--gold-1), 0 0 12px rgba(245, 178, 60, 0.35);
  animation: milestonePulse 1.6s var(--fx-ease) 2;
}
@keyframes milestonePulse {
  0%   { box-shadow: 0 0 0 1px var(--gold-1), 0 0 4px rgba(245, 178, 60, 0.2); }
  50%  { box-shadow: 0 0 0 1px var(--gold-1), 0 0 16px rgba(245, 178, 60, 0.5); }
  100% { box-shadow: 0 0 0 1px var(--gold-1), 0 0 4px rgba(245, 178, 60, 0.2); }
}
.milestone-badge {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 1;
  padding: 3px 7px;
  border-radius: 999px;
  color: #1a1205;
  background: linear-gradient(160deg, var(--gold-1), var(--gold-2, #d9982a));
  box-shadow: var(--shadow-pop);
}
/* 商店售价高亮：价格是「买不买」的唯一判据，必须比旁边那行 dim 的商品描述更抢眼。
   做成描边金币胶囊，跟正文小字是**形状**差别 —— 去掉颜色（灰阶/高对比）也认得出哪儿是价。
   每个币种一枚胶囊：金币 / 水晶 / 皮肤碎片一眼分开，不用在同一行里数颜色。 */
.shop-cost {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--gold-3);
  background: rgba(245, 178, 60, 0.16);
  color: var(--gold-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  white-space: nowrap;
}
.shop-cost-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-3);
  white-space: nowrap;
}
/* 皮肤系列徽标（v0.9.26：黄金 / 动物 / 冰火）。
   色调只有**一处**：行内 style 的 color 取 SKIN_SERIES[].tone（data 层唯一来源，
   UI 不写第二份色值）。边框 / 底色 / 圆点全部用 currentColor 派生，
   因为 dom.js 的 h() 走 Object.assign(el.style, …) 塞不进 CSS 自定义属性，
   而行内写死 rgb 又等于把色值抄第二遍。
   纯展示件：不参与任何加成计算，也没有 hover 态（读不到就不该动）。 */
.skin-series {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  /* 与前面的名称 / 状态角标拉开一点；商店那行前面是纯文字，这点缩进看不出来 */
  margin-left: 6px;
  padding: 0 6px;
  border-radius: var(--radius-sm);
  color: var(--text-3);                                        /* 兜底：tone 没注入也还读得出 */
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
  background: color-mix(in srgb, currentColor 12%, transparent);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}
.skin-series::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
/* 宠物立绘缩略图（宠物面板每行左侧）。
   44px 时降采样 512/44 = 11.6×（第二烂），抬到 60px → 8.53×，进 spec §2 的 8~9× 档；
   行高从 44 涨到 60，.row 是纯 flex（无定高），不会撑破宠物面板。 */
.pet-thumb {
  width: 60px;
  height: 60px;
  flex: 0 0 60px;
  object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--bg-3);
  border: 1px solid var(--line);
}

.slot-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
  min-width: 0;
  /* C3b：列表行是最常点的目标之一，给足 44px 最小触控高度（原仅 ~35px）。 */
  min-height: var(--touch-min);
}
.slot-row .grow { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* v0.9.101 #5：皮肤管理页里「已拥有」的皮肤整行高亮（绿左边条 + 浅底），
   一眼区别于未解锁行。owned 由行内 dataset.owned 标记（保持 skinRowView 的
   `class: 'slot-row', style: { flexWrap: 'wrap' }` 字面不变，免触发防溢出静态守卫）。 */
.slot-row[data-owned="1"] {
  border-left: 3px solid var(--success, #3FBF7F);
  background: color-mix(in srgb, var(--success, #3FBF7F) 12%, var(--bg-2, #16161e));
}

/* 装备孔位格子（v0.9.33 把镶嵌真正摆到台面上）。
   三个状态必须一眼可分：空孔是虚线缺口、生效孔是实心底、镶错地方的孔压暗 ——
   后两种长得一样的话，玩家会以为"镶上了但没反应"，那批坑就是这么来的。 */
.rune-slot {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; line-height: 1;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--bg-2);
  color: var(--fg-2);
  cursor: pointer;
  padding: 0;
}
.rune-slot.empty {
  border-style: dashed;
  color: var(--fg-3, var(--fg-2));
}
.rune-slot.filled {
  background: var(--accent-soft, var(--bg-2));
  border-color: var(--accent, var(--line));
}
/* 镶嵌宝石（v0.9.35 #50）：径向渐变 + 高光，底色 = JS 写入的 --gem（调性色）。
   空孔 / 虚线壳不画宝石，只留「＋」占位。 */
.rune-gem {
  width: 18px; height: 18px; border-radius: 50%;
  background: radial-gradient(circle at 32% 28%,
    rgba(255,255,255,.85) 0, rgba(255,255,255,.16) 34%, var(--gem) 64%);
  border: 1px solid rgba(0,0,0,.28);
  box-shadow: inset 0 0 4px rgba(255,255,255,.35);
}
/* 废孔（镶错部位）：宝石压一层红斜纹 + 红边，壳也染淡红，一眼能认出"这颗白镶了"（#53） */
.rune-gem.inert {
  border-color: #e53935;
  background-image:
    repeating-linear-gradient(45deg, rgba(229,57,53,.30) 0 3px, rgba(229,57,53,0) 3px 6px),
    radial-gradient(circle at 32% 28%, rgba(255,255,255,.85) 0, rgba(255,255,255,.16) 34%, var(--gem) 64%);
}
.rune-slot.inert {
  border-color: #e53935;
  background: rgba(229,57,53,.10);
}
.rune-slot:active { transform: translateY(1px); }

/* 原生 <select> 弹层可读性（v0.9.36 hotfix）。
   根因：<option> 由浏览器单独绘制，不套 .btn 的深色渐变底，却又继承 body 的浅色字（--text-1），
   深色主题下点开就是「白底 + 浅字」≈ 一片空白（截图即此）。显式给弹层底/字色。
   只动 option 层，不碰 .btn 闭合态的 padding/border → 盒尺寸不变，移动端基线不受影响。 */
select option {
  background-color: var(--bg-1);
  color: var(--text-1);
}
select option:checked {
  background-color: var(--accent, var(--bg-3));
  color: var(--text-1);
}

/* 装备行右侧的按钮组（v0.9.24 手机适配）。
   这行最多 5 颗按钮（强化 / 打孔+洗练 / 装备 / 分解 / 锁定），横排固有宽约 359px，
   而 360 窄屏下容器只剩 312px —— 实测「锁定」被顶到 383px（视口外，玩家点不到，
   而且整行把 panel 撑到 383px，页面横向溢出）。
   允许换行后窄屏自动折成两行，430 起仍是一行、视觉与步进都不变。
   min-width:0 是让它在 .slot-row 里真的可收缩（flex 项默认 min-width:auto 会拒绝收缩）。 */
.eq-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: flex-end;
  align-items: center;
  min-width: 0;
}

/* 装备品质：条目左侧色条 + 品质名着色。
   色值全部来自 tokens.css 的 --eq-*（六阶），JS 只挂 data-q=档位，
   同档装备一眼可辨，且浅色 / 高对比主题各自有可读值。
   注意：这里只动条目容器与文字，装备立绘 / 图片一个字节都不碰。 */
.eq-row { position: relative; padding-left: 10px; }
.eq-row::before {
  content: '';
  position: absolute;
  left: 0; top: 5px; bottom: 5px;
  width: 3px;
  border-radius: 2px;
  background: var(--eq, var(--line));
}
.eq-row[data-q="common"]    { --eq: var(--eq-common); }
.eq-row[data-q="uncommon"]  { --eq: var(--eq-uncommon); }
.eq-row[data-q="rare"]      { --eq: var(--eq-rare); }
.eq-row[data-q="epic"]      { --eq: var(--eq-epic); }
.eq-row[data-q="legendary"] { --eq: var(--eq-legendary); }
.eq-row[data-q="mythic"]    { --eq: var(--eq-mythic); }
.eq-qname { color: var(--eq, var(--text-2)); font-weight: 700; }

/* 品质角标：颜色 + 文字**双编码**。
   旧版只有一行着色文字（.eq-qname），浅色主题下色弱玩家分不出「史诗 / 传说」。
   这里把档位做成一枚实心小牌：描边用 --eq 实色、底用 --eq 的半透明、字也用 --eq ——
   三处同色，扫一眼就知道档位；但**文字通道仍在 DOM 里**（不是背景图 / 不是纯色块），
   所以把颜色整个拿掉也读得出来。
   归形（padding / radius / font-size）只在这一个选择器里写一次，别再撒第二处
   —— 不然就会重演「.sprite.unit-pet 把 46% 顶成 100%」那种两条规则互相打架的老毛病。 */
.eq-badge {
  display: inline-flex;
  align-items: center;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid var(--eq, var(--line-strong));
  background: color-mix(in srgb, var(--eq, var(--line)) 18%, transparent);
  color: var(--eq, var(--text-1));
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 1.6;
  white-space: nowrap;
}
/* 已锁定：一行文字角标就把"这块千万别拆"说清楚。
   锁定判定 / 分解产出全在 sim 层（一行都不碰），这里只负责**看得见**。 */
.eq-lock-tag {
  border: 1px solid var(--warning);
  background: color-mix(in srgb, var(--warning) 16%, transparent);
  color: var(--warning);
  margin-left: 6px;
}
/* v0.9.88 #301 装备归属标记（「（XX 身上）」）高亮：原来与参考信息同用 tiny dim，
   扫列表时分不清谁穿着谁没穿。已装备 = 主色金（--gold-1 两主题各自可读）+ 加粗
   （跟品质角标 / .up 数值行同调性），未装备保持 dim 弱化，一眼可分。 */
.eq-holder-tag {
  color: var(--gold-1, var(--text-1));
  font-weight: 700;
}

/* ---------------------------------------------------------------- 王国基地 */

/* 12×12 = 144 格领土网格（07 §3 / §11）。
   【为什么不用 1fr】360px 屏上 12 列 1fr 每格只有 25px，手指点不准、图形也放不下。
   改成固定单格尺寸 + 容器滑动：整块 12×38+11×3 ≈ 489px 宽，横向滑着看，
   纵向不再退化成 1815px 的长条，也不必为窄屏改列数（改列数就不是"一块地"了）。 */
.base-scroll {
  overflow: auto;
  max-height: min(62vh, 540px);
  margin: 8px 0 6px;
  padding: 2px;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.base-grid {
  /* 需求①-①「方框尽量填满」：过去是固定 12×38px（合计 489px）——
     在 390px 手机上必然要左右滑，在宽屏上又只居中一小块，两头都不"满"。
     改成 12 等分列 + 格子保持正方形（aspect-ratio:1，不用 grid-auto-rows 定高）：
     窄屏自动缩到放得下。
     2026-10-04 v0.9.16：max-width 那个 489px 卡口**去掉**了。
     .panel 是 720px（L 断点）/ 960px（XL），489px 的棋盘在宽屏上左右各空 100~230px，
     看着就是"方框没铺满"（用户截图第 5 张）。这里改成跟随卡片宽度：
     格子按 1fr + aspect-ratio 自动放大，宽屏单格约 55~70px，
     顺带格子大了，角标那个「行,列」小字也改由选中/hover 才显示（见 .base-cell .cell-coord）。
     窄屏（≤420px）行为完全不变，依旧是放得下就不滑。 */
  --base-gap: 3px;
  /* v0.9.100 方案 A（16×16）+ 方案 B（外域 8×8）：列数不再写死 12，由 UI 按当前区域设置。 */
  --base-cols: 16;
  /* v0.9.100 方案 C：缩放。棋盘按 zoom 倍数变宽，外层 .base-scroll 负责滚动 / 拖动平移；
     列宽仍是 1fr，所以格子跟着一起放大（不用在 JS 里反推格宽，窄屏也不会溢出）。 */
  --base-zoom: 1;
  /* 单格最小宽：16 列在 390px 手机上按 1fr 摊只有 ~21px，图形看不清、手指也点不准
     （C3b 那条 44px 触控纪律在棋盘上做不到，但至少别掉到 20px）。
     给每列一个 30px 下限：放不下就让棋盘整体变宽、由 .base-scroll 左右拖 ——
     这正是方案 C「缩放平移」存在的理由，总比缩成一团看不清强。
     宽屏（面板 720px+）单格本来就有 42px+，下限不生效，行为与以前一致。 */
  --base-cell-min: 30px;
  display: grid;
  grid-template-columns: repeat(var(--base-cols), minmax(var(--base-cell-min), 1fr));
  gap: var(--base-gap);
  width: calc(100% * var(--base-zoom));
  /* 下限撑出来的宽度要落在**棋盘盒子自己身上**：敌部队浮层是 inset:0 盖在棋盘上的，
     盒子不跟着变宽的话，浮层只有一屏宽，第 15 列的敌军就画到盒子外面去了。 */
  min-width: calc(var(--base-cols) * (var(--base-cell-min) + var(--base-gap)));
  max-width: none;
  margin: 0 auto;
  position: relative;
}
.base-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0;
  /* 列宽是 1fr（见 .base-grid），高度靠 aspect-ratio 跟着走 —— 格子永远是正方形，
     同时不再需要 grid-auto-rows 那个固定值（它正是"填不满"的一半原因）。 */
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  border: 1px dashed var(--line);
  background: var(--bg-2);
  cursor: pointer;
  overflow: hidden;
  min-width: 0;
  transition: transform var(--fx-dur) var(--fx-ease);
}
.base-cell:active { transform: scale(0.96); }

/* Req2 游荡敌对部队浮层：盖在棋盘上，自身不挡格子点击（pointer-events 由子节点各自开） */
.base-enemy-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}
.base-enemy {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 2px solid #ff5a5a;
  border-radius: 50%;
  background: rgba(40, 0, 0, 0.82);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 0 6px rgba(255, 90, 90, 0.7);
  line-height: 1;
  z-index: 5;
}
.base-enemy .enemy-sword { font-size: 12px; }
.base-enemy .enemy-name {
  font-size: 8px;
  transform: scale(0.92);
  white-space: nowrap;
  margin-top: 1px;
  max-width: 30px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.base-enemy.dead {
  border-color: #8a8a8a;
  background: rgba(30, 30, 30, 0.8);
  box-shadow: none;
  cursor: default;
  opacity: 0.85;
}
.base-enemy .enemy-skull { font-size: 12px; }
.base-enemy .enemy-cd {
  font-size: 7px;
  transform: scale(0.9);
  margin-top: 1px;
}
.base-enemy:not(.dead):active { transform: translate(-50%, -50%) scale(0.9); }
.base-cell .base-glyph { display: block; line-height: 0; }
/* 图形跟着格子缩放（不再钉 26px）：窄屏格子缩到 ~27px 时图标也不会溢出被裁，
   宽屏封顶 26px，与改动前的观感一致。 */
.base-grid .base-glyph { width: 84%; }
.base-grid .base-glyph svg { width: 100%; height: auto; max-width: 26px; max-height: 26px; }

/* v0.9.25 建筑 AI 立绘（图集版）。
   立绘铺满**同一个** glyph 方框（宽度 84% 格宽、封顶 26px），所以立绘与它替掉的
   矢量图形占位逐像素一致 —— 换图不会让格子里的东西忽大忽小。
   徽章浮在右下角，用一个铺满百分比方框的 SVG 承载（见 baseView.js 的 artLevelBadge
   头注释：用 px 定字号会在窄屏溢出，只有 viewBox 才能跟着格子一起缩）。
   注意 .bld-badge svg 必须写在 .base-grid .base-glyph svg 之后并把选择器加长一层：
   前者是 (0,2,1)、后者加长后 (0,3,1)，否则 height:auto 会盖掉徽章要的 height:100%。 */
.base-grid .base-glyph-art { position: relative; }
.base-grid .base-glyph-art .bld-sprite {
  width: 100%;
  max-width: 26px;
  max-height: 26px;
  margin: 0 auto;
  aspect-ratio: 1 / 1;
}
.base-grid .base-glyph-art .bld-badge {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 52%;
  height: 38%;
  line-height: 0;
  pointer-events: none;
}
.base-grid .base-glyph-art .bld-badge svg { width: 100%; height: 100%; max-width: none; max-height: none; }

/* 需求①-②：格内 X/Y 坐标刻度（1 基，「行,列」）。144 格下格内放不下正文，
   这行只当刻度用，绝对定位到左上角、不参与布局。 */
.base-coord {
  position: absolute;
  left: 1px;
  top: 0;
  z-index: 1;
  font-size: 7px;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
}

/* 需求①-③：敌袭横幅。挂在王国页最顶上（invasionBanner），紧贴下面的卡片 */
.base-alert { margin-bottom: 8px; }

/* —— 格子状态（07 §11）：144 格下格内没有文字，状态全靠边框 / 描边 / 角标 —— */
/* 部队所在格：正中的小圆点（在途时跟着当前步走） */
.base-cell.army::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--arcane-1);
  box-shadow: 0 0 0 2px var(--bg-1);
  pointer-events: none;
}
/* 当前正在走的那一格 */
.base-cell.step { outline: 2px solid var(--arcane-1); outline-offset: -2px; }
/* 终点格 */
.base-cell.target { outline: 2px dashed var(--danger); outline-offset: -3px; }
/* 玩家选中、正在下面看详情的那一格 */
.base-cell.sel { outline: 2px solid var(--success); outline-offset: -2px; }

/* 行动中区（07 §11）：进度条 + 倒计时，每秒只改数字不改结构 */
.base-action { display: flex; flex-direction: column; gap: 6px; }
.base-bar { min-height: 8px; }
/* 并行行动：5 槽各占一行，窄屏也能一行放下（按钮最后一个折行） */
.base-missions { display: flex; flex-direction: column; gap: 6px; }
.base-mission {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.base-mission .bar { flex: 1 1 80px; min-width: 60px; }
.base-mission .num { min-width: 54px; text-align: right; font-variant-numeric: tabular-nums; }
.base-mission .btn { flex: 0 0 auto; }
.base-toggle { align-items: center; cursor: pointer; }
.base-toggle input { width: 16px; height: 16px; margin: 0; }
/* 目标面板：点格后显示"能干什么 + 要多久" */
.base-target {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 6px 0 8px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  border: 1px solid var(--line);
}
/* 格子提示行（v0.9.19）：地图正下方那一行"这格是什么 + 打开操作"。
   点格之后操作会直接弹出来，所以这里只留入口 —— 弹窗开着时下面那整块
   .base-target 已被搬进弹窗当正文，卡片里就只剩这一行。 */
.base-cell-hint {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 8px 0 0;
  padding: 6px 2px;
}

/* 汇总条（v0.9.20「一眼看出该干什么」）：网格正上方那一行件数。
   只报数、不放按钮 —— 按钮放上来就变成第二套操作入口，会和下面卡片里的同名按钮分家。 */
.base-summary {
  margin: 0 0 8px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  border: 1px solid var(--line);
}

/* 建造 / 升级进度环（v0.9.20）：144 格下格内放不下文字，
   用一圈 conic-gradient 表示"这格在动、动到哪了"。
   每秒只改 --ring 一个 CSS 变量（见 baseView 的 paintBuildRings），不重建节点 ——
   重建 144 格会整片重排，手机上肉眼可见地掉帧。 */
.base-ring {
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(
    var(--arcane-1) calc(var(--ring, 0) * 1turn),
    rgba(127, 127, 127, 0.28) 0
  );
  /* 挖空中间，只留一圈边 —— 否则整格被盖住，建筑图标看不见 */
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2.5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2.5px));
}

/* 144 格下格内已不放文字（07 §11），详情走 title 与点开后的目标面板 */
/* 未解锁：锁死感。斜纹比纯灰更能传达"还没到手" */
.base-cell.locked {
  border-style: dotted;
  border-color: var(--line);
  background:
    repeating-linear-gradient(45deg, transparent 0 5px, rgba(255, 255, 255, 0.03) 5px 10px),
    var(--bg-1);
  cursor: not-allowed;
}
.base-cell.empty { border-style: dashed; border-color: var(--line-strong); }

/* 地块（方向 A）——只在「还没建东西」的格子上着色，那正是做决策的格子。
   建好之后格子已经有自己的配色（建筑 / 据点 / 施工中各一套），再往里叠地块底色
   只会糊成一团；锁格更要保住它的斜纹「未到手」信号，所以也不参与着色。
   六种地块各配一种底纹（不只靠颜色区分），配色不敏感的用户也能认出该往哪放。 */
.base-cell.terr-plains.empty {
  background-color: rgba(126, 217, 120, 0.10);
  background-image: repeating-linear-gradient(0deg, transparent 0 6px, rgba(126, 217, 120, 0.14) 6px 7px);
}
.base-cell.terr-ore.empty {
  background-color: rgba(150, 120, 90, 0.12);
  background-image: repeating-linear-gradient(45deg, transparent 0 4px, rgba(150, 120, 90, 0.20) 4px 5px);
}
.base-cell.terr-forest.empty {
  background-color: rgba(90, 170, 130, 0.12);
  background-image: radial-gradient(rgba(90, 170, 130, 0.30) 1px, transparent 1.6px);
  background-size: 7px 7px;
}
.base-cell.terr-marsh.empty {
  background-color: rgba(120, 155, 165, 0.12);
  background-image: repeating-linear-gradient(90deg, transparent 0 5px, rgba(120, 155, 165, 0.26) 5px 6px);
}
.base-cell.terr-highland.empty {
  background-color: rgba(160, 140, 220, 0.11);
  background-image: repeating-linear-gradient(90deg, transparent 0 3px, rgba(160, 140, 220, 0.16) 3px 4px);
}
.base-cell.terr-wasteland.empty {
  background-color: rgba(190, 160, 110, 0.12);
  background-image:
    repeating-linear-gradient(45deg, transparent 0 5px, rgba(190, 160, 110, 0.18) 5px 6px),
    repeating-linear-gradient(-45deg, transparent 0 5px, rgba(190, 160, 110, 0.18) 5px 6px);
}
/* 地块高亮（7 项候选③）：点「按地块看」里的某一地块，全图所有同地块格描一圈同色内边。
   为什么用 inset box-shadow 而不是 outline —— 选中 / 目标 / 行军那三圈都是 outline，
   再叠一层 outline 会互相盖掉（后渲染的那个赢），box-shadow 走的是另一层，两者能同框。
   六种各配一色，与上面 .terr-* 的底纹同源：高亮不要求玩家再认一套颜色。
   排在 .event / .occupied 之后 —— 高亮要盖在它们的底色上，不能反过来被压掉。 */
.base-cell.terr-hl.terr-plains { box-shadow: inset 0 0 0 3px rgba(126, 217, 120, 0.85); }
.base-cell.terr-hl.terr-ore { box-shadow: inset 0 0 0 3px rgba(150, 120, 90, 0.85); }
.base-cell.terr-hl.terr-forest { box-shadow: inset 0 0 0 3px rgba(90, 170, 130, 0.85); }
.base-cell.terr-hl.terr-marsh { box-shadow: inset 0 0 0 3px rgba(120, 155, 165, 0.9); }
.base-cell.terr-hl.terr-highland { box-shadow: inset 0 0 0 3px rgba(160, 140, 220, 0.85); }
.base-cell.terr-hl.terr-wasteland { box-shadow: inset 0 0 0 3px rgba(190, 160, 110, 0.85); }
/* 未解锁的锁格也参与高亮（整片盐沼要一眼连起来，锁着的那几块也得算在内），
   但斜纹"还没到手"的信号不能丢，所以只描边、不压底色。 */

/* 边陲事件格（方向 C）：告警色实框 + 一抹暖底，跟据点的盾牌、
   空地的虚线框三者剪影与配色都分得开 —— 一眼看出"这格有事"。
   必须排在 .empty / .terr-* 之后：事件格也是空地，底色要盖得住地块纹理。 */
.base-cell.event {
  border-style: solid;
  border-color: var(--info, var(--arcane-1));
  background: linear-gradient(160deg, rgba(139, 107, 255, 0.20), var(--bg-2) 70%);
}
/* 探索点（#364，地下 / 外域空地）：琥珀色边框 + 淡琥珀底色，与边陲事件（蓝）区分。
   今天已探过的点降饱和、降透明，提示「今天没油水了」。 */
.base-cell.explore {
  border-style: solid;
  border-color: #e6a23c;
  background: linear-gradient(160deg, rgba(230, 162, 60, 0.22), var(--bg-2) 70%);
}
.base-cell.explore.done {
  border-color: var(--line-strong);
  background: linear-gradient(160deg, rgba(230, 162, 60, 0.07), var(--bg-2) 70%);
}
/* 手上拿着建筑、而这格**跟它不投缘**时描一道告警色边（mismatched 由 baseView 加）。
   「这一格能吃满」与「这格要折四成」在落子前就分得清，不用等建完去读 title。 */
.base-cell.mismatched {
  border-color: var(--warning);
  box-shadow: inset 0 0 0 1px rgba(255, 183, 77, 0.5);
}
.base-cell.occupied {
  border-style: solid;
  border-color: var(--line-strong);
  background: linear-gradient(160deg, var(--bg-3), var(--bg-2) 70%);
}
/* 按建筑类型配色（#111）：13 种各一色相，格子边框+淡底色就区分种类，
   不再只靠 26px 小 glyph。写在 .occupied 之后（同特异性，后者覆盖边框/底色）。
   颜色直接写死 13 个不同色相，跨明暗主题都认得出。 */
/* 13 种建筑各配一色，全部统一 0.42 浓度（不再有 0.20 的淡到看不见的几类），
   并把最接近的几对色相拉开：暖色 mine/outpost/workshop、冷色 warehouse/post/harbor。 */
.base-cell.bld-castle   { border-color: #a06bff; background: linear-gradient(160deg, rgba(160,107,255,0.42), var(--bg-2) 55%); }
.base-cell.bld-farm     { border-color: #3fbf5f; background: linear-gradient(160deg, rgba(63,191,95,0.42),  var(--bg-2) 55%); }
.base-cell.bld-mine     { border-color: #d9a23a; background: linear-gradient(160deg, rgba(217,162,58,0.42),  var(--bg-2) 55%); }
.base-cell.bld-market   { border-color: #f2d23a; background: linear-gradient(160deg, rgba(242,210,58,0.42),  var(--bg-2) 55%); }
.base-cell.bld-outpost  { border-color: #ff7a33; background: linear-gradient(160deg, rgba(255,122,51,0.42),  var(--bg-2) 55%); }
.base-cell.bld-warehouse{ border-color: #1fc4d6; background: linear-gradient(160deg, rgba(31,196,214,0.42), var(--bg-2) 55%); }
.base-cell.bld-college  { border-color: #4f8bff; background: linear-gradient(160deg, rgba(79,139,255,0.42),  var(--bg-2) 55%); }
.base-cell.bld-barracks { border-color: #ff4d4d; background: linear-gradient(160deg, rgba(255,77,77,0.42),   var(--bg-2) 55%); }
.base-cell.bld-wall     { border-color: #8a7bff; background: linear-gradient(160deg, rgba(138,123,255,0.42), var(--bg-2) 55%); }
.base-cell.bld-post     { border-color: #2f8fe8; background: linear-gradient(160deg, rgba(47,143,232,0.42),  var(--bg-2) 55%); }
.base-cell.bld-workshop { border-color: #8a5a2a; background: linear-gradient(160deg, rgba(138,90,42,0.42),  var(--bg-2) 55%); }
.base-cell.bld-preserve { border-color: #ff5fa8; background: linear-gradient(160deg, rgba(255,95,168,0.42), var(--bg-2) 55%); }
.base-cell.bld-harbor   { border-color: #1fb87f; background: linear-gradient(160deg, rgba(31,184,127,0.42),  var(--bg-2) 55%); }
/* v0.9.100 新区建筑（外域 / 地下）：同样各配一色，颜色与主图 13 种不重 */
.base-cell.bld-ranch      { border-color: #c8d64a; background: linear-gradient(160deg, rgba(200,214,74,0.42),  var(--bg-2) 55%); }
.base-cell.bld-quarry     { border-color: #a0763c; background: linear-gradient(160deg, rgba(160,118,60,0.42),  var(--bg-2) 55%); }
.base-cell.bld-under_farm { border-color: #6fd36f; background: linear-gradient(160deg, rgba(111,211,111,0.42), var(--bg-2) 55%); }
.base-cell.bld-under_mine { border-color: #7f6bff; background: linear-gradient(160deg, rgba(127,107,255,0.42), var(--bg-2) 55%); }
.base-cell.bld-under_forge{ border-color: #ff8a3d; background: linear-gradient(160deg, rgba(255,138,61,0.42),  var(--bg-2) 55%); }
/* 加粗建筑类型边框（v0.9.46 加强 #111 辨识度）：2px 同色边，手机小格也一眼分种类 */
.base-cell.bld-castle, .base-cell.bld-farm, .base-cell.bld-mine, .base-cell.bld-market,
.base-cell.bld-outpost, .base-cell.bld-warehouse, .base-cell.bld-college, .base-cell.bld-barracks,
.base-cell.bld-wall, .base-cell.bld-post, .base-cell.bld-workshop, .base-cell.bld-preserve, .base-cell.bld-harbor,
.base-cell.bld-ranch, .base-cell.bld-quarry, .base-cell.bld-under_farm, .base-cell.bld-under_mine, .base-cell.bld-under_forge {
  border-width: 2px;
}
/* 手上拿着建筑时，空格子亮起来，明确"现在点的是建造" */
.base-cell.pickable {
  border-color: var(--arcane-2);
  box-shadow: inset 0 0 0 1px rgba(139, 107, 255, 0.25);
}
/* NPC 据点：告警色实线，与自家建筑一眼分开。
   必须排在 .locked 之后 —— 据点大多还没解锁，否则灰斜纹会把告警色盖掉。 */
.base-cell.npc {
  border-style: solid;
  border-color: var(--warning);
  background: linear-gradient(160deg, rgba(255, 183, 77, 0.16), var(--bg-2) 70%);
}
/* 建造 / 升级中（受理即扣费，到点才落成） */
.base-cell.building {
  border-style: dashed;
  border-color: var(--arcane-2);
  background: linear-gradient(160deg, rgba(139, 107, 255, 0.14), var(--bg-2) 70%);
}
.base-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  min-width: 62px;
}
.base-palette {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.base-pick {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
.base-pick.on { border-color: var(--arcane-2); background: var(--bg-3); }
/* #377 Req3：选中地块时，给「能在这格满产」的建筑打标 */
.base-pick.aff-bonus { border-color: var(--success); box-shadow: inset 0 0 0 2px rgba(126,217,120,0.35); background: var(--bg-3); }
.aff-bonus-tag { color: var(--success); font-weight: 700; }
/* 用户反馈：点空地弹窗里「此地建造可加成」的行动行 —— 与建筑面板 aff-bonus 同一语义。
   v0.9.107：改成不依赖 .base-actions 容器的裸选择器，并补上边框。
   容器只是恰好叫这个名字，把样式钉在容器名上，容器哪天换一层整条高亮就静默失效；
   同时把绿底加深 + 加一圈绿边 —— 原来只有 10% 底色的浅背景，混在十几个按钮里根本挑不出来。 */
.cell-aff-hit {
  background: rgba(126,217,120,0.12);
  border: 1px solid rgba(126,217,120,0.45);
  border-radius: 8px;
  padding: 4px 8px;
}
.aff-hit-btn { border-color: var(--success); color: var(--success); }
.aff-hit-tag { color: var(--success); font-weight: 700; }
/* 弹窗顶部那行结论：这一格是 XX，建什么满产 */
.aff-line { color: var(--success); font-weight: 600; }
/* #377 Req2：网格正上方居中的「下一波资源刷新」倒计时 */
.base-wave-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 6px 0 8px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
.base-wave-bar .wave-label { font-weight: 700; color: var(--text-1); }
.base-wave-bar .wave-clock {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: 1.1em;
  color: var(--accent, #8b6bff);
  letter-spacing: 0.5px;
}

/* 边陲事件面板（方向 C）：沿用 .base-pick 的卡片口径，
   让玩家不用重新学一块控件长什么样 —— 差别只在它是"问你拿主意"，所以标题行加粗。 */
.base-event {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.base-ev-choice {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
.base-ev-choice.off {
  /* 买不起 / 交不起：压暗但**不隐藏**，让玩家知道这个选项"存在但暂时不能选" */
  opacity: 0.55;
}
.base-raids { display: flex; flex-direction: column; gap: 6px; }
.base-raid {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
/* 已占领（P0 战役化）：整卡转成成功色描边，与"打了一半"一眼分开。
   两种状态必须能在去色 / 高对比下也分得开，所以兼用右边框粗细 + chip 文案。 */
.base-raid.done {
  border-color: var(--success);
  border-left-width: 3px;
  background: linear-gradient(160deg, rgba(61, 214, 152, 0.12), var(--bg-2) 70%);
}
.chip.ok-chip { border-color: var(--success); color: var(--success); }
/* 战役进度条：波数进度比"打赢没打赢"更需要一眼看到，给它固定高度的细条 */
.base-waves { display: flex; flex-direction: column; gap: 3px; }
/* 资源去向（P1-B）：三笔账 + 还能撑几天，行内换行，窄屏也不塌成竖条 */
.base-uses { display: flex; flex-direction: column; gap: 3px; }
/* 王国科技（P2-C）三态：已解锁 / 可解锁 / 买不起 必须一眼分得开，
   去色（灰阶）主题下靠左边框与底色也能分，所以不只靠颜色。 */
.base-techs { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.base-tech {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--text-3);
}
.base-tech.ready { border-left-color: var(--success); }
.base-tech.poor { border-left-color: var(--warning); opacity: 0.9; }
.base-tech.blocked { border-left-color: var(--line); background: var(--bg-1); opacity: 0.75; }
.base-tech.on { border-left-color: var(--success); background: linear-gradient(160deg, rgba(61, 214, 152, 0.12), var(--bg-2) 70%); }
/* 预警类文字：告警色，去色主题下靠字重也读得出（不与其它 dim 文字同色） */
.warn-text { color: var(--warning); font-weight: 600; }

/* ---------------------------------------------------------------- 提示块 */

.notice {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--info);
  background: var(--bg-2);
  font-size: var(--fs-sm);
}
.notice.warn { border-left-color: var(--warning); }
.notice.danger { border-left-color: var(--danger); }
.notice.ok { border-left-color: var(--success); }

/* ---------------------------------------------------------------- 响应式 */

/* M 及以下：战场单位更紧凑。
   注意：`.unit` 的宽度不再在这里调——棋盘内由 `.bf-grid .unit { width:100% }`
   跟着格子走，5 列在 360px 下每格约 62px，正好放得下 58px 立绘。
   这里只调非棋盘语境下的单位宽度（当前无此类语境，留着防回归）。 */
@media (max-width: 480px) {
  .panel { padding: 10px; }
  .hero-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
  .pull-result { grid-template-columns: repeat(5, 1fr); }
  /* 12×12 填满屏宽后每格约 27px：图形跟着格子缩，坐标刻度再降 1px 免得糊成一团 */
  .base-grid .base-glyph { width: 80%; }
  .base-grid .base-glyph svg { max-width: 22px; max-height: 22px; }
  .base-coord { font-size: 6px; }
  .hud-title { display: none; }
}

/* L：单列加宽居中，不提前切三栏（战场是核心视图，宁可留白也不挤） */
@media (min-width: 768px) {
  .panel { max-width: 720px; margin: 0 auto; width: 100%; }
  .modal-scrim { align-items: center; }
  .modal { border-radius: var(--radius-lg); }
}

/* XL：三段式（左导航 + 中央舞台 + 右详情）。1280-1439 右面板抽屉化、舞台保底 560 */
@media (min-width: 1280px) {
  .panel { max-width: 960px; }
}

/* 320px 宽度硬要求：无页面级横向滚动。
   最常见的溢出源是 flex 子项没有 min-width:0（中文长词会顶破容器）。 */
@media (max-width: 360px) {
  .bf-grid { gap: 3px; }
  .bf-grid .unit { max-width: 56px; }
  .unit-name { display: none; }
  .res { padding: 3px 6px; font-size: var(--fs-xs); }
  .hud-row { gap: 4px; }
}

/* 英雄加点：上阵英雄的 2 列网格（窄屏退化为单列），用于远征页下半部 */
.hg-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
@media (max-width: 420px) { .hg-grid { grid-template-columns: 1fr; } }

/* 减少动效偏好 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* 减少动态效果（#179，手动开关；与系统 prefers-reduced-motion 同效）。
   纯 CSS 根类驱动，零数值改动；只在用户手动开启时生效，移动端视觉回归基线不受影响。 */
.reduce-motion *, .reduce-motion *::before, .reduce-motion *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

/* 桌面滚动条 */
#view::-webkit-scrollbar { width: 8px; }
#view::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 4px; }
#view::-webkit-scrollbar-track { background: transparent; }

/* 背包分标签页：标签行与内容间距 */
.bag-tabs { margin: 8px 0; }
.bag-tabs .btn { min-width: 64px; }

/* ================================ A4 每日签到轮盘 ================================ */
.signin-streak { font-size: 0.82rem; color: var(--fg-dim, #9aa); margin-bottom: 8px; }
.signin-wheel {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
  margin-bottom: 10px;
}
.signin-slot {
  border: 1px solid var(--bg-3, #2a2f45);
  border-radius: 8px;
  padding: 6px 4px;
  text-align: center;
  background: var(--bg-1, #0b0e1a);
  position: relative;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.signin-slot .signin-day { font-size: 0.7rem; font-weight: 700; color: var(--fg-dim, #9aa); }
.signin-slot .signin-reward { font-size: 0.62rem; line-height: 1.15; margin-top: 2px; color: var(--fg, #e8eaf2); }
.signin-slot .signin-mark {
  position: absolute; top: 3px; right: 4px;
  font-size: 0.62rem; font-weight: 700; color: var(--gold-1, #f5b23c);
}
/* 今日：金边脉冲提示 */
.signin-slot.today { border-color: var(--gold-1, #f5b23c); box-shadow: 0 0 0 1px var(--gold-1, #f5b23c) inset; }
/* 已签：暗化 + 勾 */
.signin-slot.claimed { opacity: 0.55; background: var(--bg-2, #141826); }
.signin-slot.claimed .signin-mark { color: var(--up, #6ee7a8); }

/* ================================================================ 体验/性能模式（③-2 / ③-5 / ③-6）
   所有效果都由 <html> 上的根类驱动（applySettings 统一打/摘），纯 CSS、零数值改动。 */

/* ③-2 高性能模式：关阴影、减粒子、降动画、降 DPR 合成开销。
   注意所有规则都挂在 .high-perf 下，默认画质（开关关着）一点不变，
   移动端视觉回归基线（mobile-golden）也就不会被自己的降级开关打红。 */
.high-perf * {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
}
.high-perf .hero-card[data-fx-on] .hero-art { filter: none; }
/* HUD 的毛玻璃 + 渐变底换成纯色：blur 是逐帧重绘，纯色只画一次。 */
.high-perf #hud { backdrop-filter: none; background: var(--bg-1) !important; }
.high-perf .bf-grid.advance { animation: none !important; }
/* 减少粒子：飘字几乎瞬时消失，不再长时间悬浮占用合成层。 */
.high-perf .float { animation-duration: 80ms !important; }
/* 阴影 / 渐变是最贵的两类合成装饰，换成无阴影 + 纯色底（可读性不变，只差"浮起来"的观感）。 */
.high-perf .card,
.high-perf .btn,
.high-perf .btn-primary,
.high-perf .panel,
.high-perf .modal,
.high-perf .hero-card { box-shadow: none !important; background-image: none !important; }
/* blur 类装饰（毛玻璃 / 发光）是逐帧重绘的大开销，直接摘掉。
   只有纯装饰（fx-glow 发光）才摘 filter；hero-card::after 是英雄名的压暗层，
   属功能性底衬不能摘，改成同色实底 —— 渐变换纯色，开销从逐帧降到一次。 */
.high-perf .fx-glow,
.high-perf .float,
.high-perf .bf-status { filter: none !important; backdrop-filter: none !important; }
.high-perf .hero-card::after { background: rgba(0, 0, 0, 0.62) !important; }

/* ③-6 大字号模式：整体放大 1 档（覆盖设计令牌，全站继承）。 */
:root.big-font {
  --fs-xs: 0.8125rem;
  --fs-sm: 0.9375rem;
  --fs-md: 1.0625rem;
  --fs-lg: 1.3125rem;
  --fs-xl: 1.75rem;
}
/* 大字号叠在极窄屏（≤360px，含 320 极限档）上会顶破 HUD 货币网格与设置页分段按钮行，
   这条把放大幅度退回半档：字仍比默认大（--fs-md 1rem > 0.9375rem），但一行塞得下。
   规则写在上面那条之后，同特异性下后写的赢，无需 extra 选择器。 */
@media (max-width: 360px) {
  :root.big-font {
    --fs-xs: 0.75rem;
    --fs-sm: 0.875rem;
    --fs-md: 1rem;
    --fs-lg: 1.1875rem;
    --fs-xl: 1.4375rem;
  }
}

/* ③-6 高对比模式：提亮文本、加重描边（关键文字/边框加描边）。 */
:root.high-contrast {
  --text-1: #FFFFFF;
  --text-2: #F2F5FF;
  --text-3: #D2DAF2;
  --line: #5B68A0;
  --line-strong: #9AA8DC;
  --bg-1: #0E1326;
  --bg-2: #161D38;
}
/* 浅色主题下的高对比：上一条的深底浅色令牌在浅底上会把字变成白底白字（最坏 1.1:1）。
   本工程 app.css 排在 tokens.css 之后，`:root.high-contrast` 会盖掉 `[data-theme=light]`
   的同名变量，所以浅色必须自己给一套"更深的前景 + 更实的边框"，否则一开高对比就瞎。 */
:root[data-theme="light"] .shop-cost {
  /* 浅底上的金币胶囊：把高亮底压成淡金，别用深色的 rgba 直接压白纸 */
  background: rgba(201, 134, 31, 0.14);
}
:root[data-theme="light"].high-contrast {
  --text-1: #000000;
  --text-2: #22263A;
  --text-3: #3B4258;
  --line: #7C86A8;
  --line-strong: #1A2036;
  --bg-1: #FFFFFF;
  --bg-2: #E6EAF6;
  --gold-1: #2E1F00;
  --gold-2: #4A3405;
  --gold-3: #6B4A07;
  --gold-on-dark: #2E1F00;
  --on-gold: #FFFFFF;
}
.high-contrast .card,
.high-contrast .btn,
.high-contrast .panel,
.high-contrast .nav-btn,
.high-contrast .hero-card,
.high-contrast .modal,
.high-contrast .bf-grid .unit {
  border: 1px solid var(--line-strong);
}
.high-contrast .num { text-shadow: 0 0 1px rgba(0, 0, 0, 0.9); }
/* 高对比模式：售价再加重一档描边，别让胶囊和背景糊在一起 */
.high-contrast .shop-cost { border-width: 2px; }
.high-contrast .res { text-shadow: 0 0 2px #000; }

/* 色盲辅助（#179）：稀有度配色换成色盲安全的 Okabe-Ito 三色（橙 / 天蓝 / 蓝绿），
   并对 sr / SSR 档加虚线 / 双线边框，使档位不仅靠色相区分。纯 CSS 根类驱动，零数值改动。 */
.color-blind {
  --rarity-r: #E69F00;
  --rarity-sr: #56B4E9;
  --rarity-ssr: #009E73;
}
.color-blind .hero-card[data-rarity="sr"],
.color-blind .pull-item[data-rarity="SR"] { border-style: dashed; }
.color-blind .hero-card[data-rarity="ssr"],
.color-blind .pull-item[data-rarity="SSR"] { border-style: double; border-width: 3px; }

/* #286 E 无障碍增强（v0.9.82）：红绿语义除颜色外再补形状/符号线索，色盲玩家不靠色相分辨。
   纯 CSS 根类驱动，仅在 .color-blind 生效；默认渲染与移动端视觉基线零变化。 */
.color-blind .lg-win,
.color-blind .bf-status.win,
.color-blind .toast.ok,
.color-blind .chip.on,
.color-blind .chip.ok-chip,
.color-blind .notice.ok { box-shadow: inset 0 0 0 2px currentColor; }
.color-blind .lg-win::after,
.color-blind .bf-status.win::after { content: ' \2713'; }
.color-blind .lg-lose,
.color-blind .bf-status.lose,
.color-blind .toast.err,
.color-blind .chip.counter-weak,
.color-blind .notice.danger { box-shadow: inset 0 0 0 2px currentColor; }
.color-blind .notice.warn { box-shadow: inset 0 0 0 2px currentColor; }
.color-blind .lg-lose::after,
.color-blind .bf-status.lose::after { content: ' \2717'; }
.color-blind .up::before { content: '\25B2 '; }
.color-blind .down::before { content: '\25BC '; }
/* 血条：色盲下叠斜纹，方向（友方/敌方）不靠色相 */
.color-blind .unit-hp > i { background-image: repeating-linear-gradient(45deg, rgba(0,0,0,.18) 0 6px, transparent 6px 12px); }

/* #286 E 系统级：偏好更高对比度时，自动加粗描边 + 文字描边提对比（仅 OS 开启时生效） */
@media (prefers-contrast: more) {
  :root { --hc-stroke: rgba(0, 0, 0, 0.6); }
  .modal, .card, .panel, .toast, .chip, .btn, button {
    border-width: 2px;
    text-shadow: 0 0 1px var(--hc-stroke);
  }
  .lg-win, .lg-lose, .win-text, .lose-text { text-shadow: 0 0 2px var(--hc-stroke); }
}

/* #286 E 系统级：Windows 高对比（forced-colors）下保住边框与控件可见性 */
@media (forced-colors: active) {
  .btn, button, .chip, .modal, .card, .panel, .toast {
    border: 2px solid;
    forced-color-adjust: auto;
  }
  .bar.hp > i, .unit-hp > i { border: 1px solid; }
}

/* ---------------------------------------------------------------- 需求 ① 物资页填白
   英雄 / 背包 / 抽奖三个子页在手机视口下底部留白 290~520px（实测见
   tools/verify/_tmp_ui_polish.mjs）。页脚由 renderSupply 挂载、只挂这三页。
   样式复用既有 token，不新增颜色。 */
.supply-foot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}

.party-strip {
  /* auto-fit + minmax：360px 下 4 个槽位一排，宽屏自动摊开，不会撑出横向滚动 */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(62px, 1fr));
  gap: 6px;
  min-width: 0;
}

.party-slot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 6px 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-2);
  color: var(--text-1);
  cursor: pointer;
  min-width: 0;
  overflow: hidden;
}
/* 队伍 / 图鉴的立绘位（sprite-spec §1.3）：承载盒**必须正方**，这是硬前提。
   spriteEl 的 backgroundSize 写死 700% / 400%（= ATLAS_W/512、ATLAS_H/512），
   这两套系数只在「承载元素正方」时让图集格恰好铺满定位区；
   一旦盒子被拉成横条，同一组 700%/400% 就把立绘横向拉宽
   （360px 手机 +18.5%、390px +32.4%、≥768px 桌面 +183%）——
   这正是真机反馈「队伍 / 英雄图鉴图片失真」的直接成因。
   所以：① 宽度用 min(100%, 64px) 钳住，别被 grid 的 1fr 摊成 153px；
        ② 高度**交给 aspect-ratio: 1/1 从宽度推导**，绝不写死 height ——
           写了死高度就等于把「高」和「宽」脱钩，正方形前提当场失效。
   定在 64px 是因为 512/64 = 8.0× 降采样，是这套 512 图集在零新资源前提下的最优值。 */
.party-slot .hero-art {
  width: min(100%, 64px);
  height: auto;                /* ← 保持正方形的前提，见上方注释，改回写死高度就是回归 */
  aspect-ratio: 1 / 1;
  object-fit: contain;
  padding: 0;
}
.party-slot-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
}
.party-slot.is-empty {
  justify-content: center;
  /* 立绘改回正方形（64×64）后，满位槽高 ≈ 64 立绘 + 2×11px 文字行 + 2×6 padding
     + 2×1 border + 2×1 gap ≈ 106px。虚线空位必须跟满位一样高，否则一排槽会高低错落。 */
  min-height: 106px;
  border-style: dashed;
  background: transparent;
  cursor: default;
}
/* 图鉴里的未招募英雄：只留剪影，名字照给（收集类游戏靠"还差谁"驱动）。
   剪影的**对比度**而不是"亮度"才是锁定态该表达的东西 —— 原来的
   grayscale(1) brightness(0.45) + opacity: 0.45~0.85 把本就偏暗的立绘压进卡底：
   按 atlas 实测 10 个英雄格算，深底（--bg-2 #1C2440）下最差主体的 WCAG 对比度只有
   **1.0:1**（= 完全看不见），中灰英雄也只有 1.2~1.7:1；浅色英雄靠运气还能看见。
   症结是**压暗在深底上等于"往背景里沉"**，方向反了。
   改成「只去色 + 提亮 + 一圈亮描边」，按实测把最暗的三个（berserker / rogue / shaman）
   主体对比度抬到 ≥3:1（spec §5.3 的 Standard 门槛），暗色英雄才认得出是谁；
   opacity 回到 1（再压半透只会把刚才抬起来的对比度又吃掉），
   "未解锁"的语义交给虚线框 + 名字 + 卡片说明，不再靠把图压黑。 */
.party-slot.is-locked .hero-art {
  filter: grayscale(1) brightness(1.9) drop-shadow(0 0 1px rgba(150, 165, 255, 0.55));
  opacity: 1;
}
/* 浅色主题下方向相反：卡底（--bg-2 #EAEEF9）比立绘亮，压暗才是提对比度，
   所以这一档必须保留「压暗」，不能照抄深底的亮剪影公式。
   实测：br=0.45/op=.85 时浅底下 10 个英雄主体对比度 4.8~26:1（最差 @priest），全部达标。 */
:root[data-theme="light"] .party-slot.is-locked .hero-art {
  filter: grayscale(1) brightness(0.45) drop-shadow(0 0 1px rgba(40, 50, 90, 0.35));
}
.party-slot.is-locked { border-style: dashed; }

/* C3c：编队拖拽排序（pointer events）。以下视觉只在「正在拖拽」时出现，
   静止态零布局变更，不会撼动 mobile-golden 像素基线。
   .base-kp-slot / .btn 拖拽表面用 touch-action:none 关闭滚动手势，保证拖动顺滑。 */
.base-kp-slot { touch-action: none; }
.slot-drag-ghost {
  position: fixed; z-index: 9999; pointer-events: none;
  transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 6px 8px; border-radius: 8px;
  background: var(--bg-2); border: 1px solid var(--accent, #8b6bff);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); opacity: 0.92; max-width: 132px;
}
.slot-drag-ghost .hero-art { width: 44px; height: auto; aspect-ratio: 1 / 1; object-fit: contain; }
.slot-drag-ghost .tiny { white-space: nowrap; }
/* 落点候选槽（虚线提示可放） */
.slot-drop-target { outline: 1px dashed var(--line); }
/* 当前指针悬停的落点槽（实线高亮） */
.slot-drop-over {
  outline: 2px solid var(--accent, #8b6bff);
  background: color-mix(in srgb, var(--accent, #8b6bff) 14%, var(--bg-2));
}

/* ③-5 小屏兜底（≤320px，含折叠屏外屏）：无页面级横向滚动、导航可点、战斗区不压垮。
   360px 档已在上面覆盖，这里只补 320 极限宽的几处余量。 */
@media (max-width: 320px) {
  .nav-lbl { font-size: 10px; }
  .nav-btn { padding: 4px 1px; gap: 1px; }
  .nav-ico { width: 18px; height: 18px; }
  .hud-row { gap: 2px; }
  .res { padding: 2px 4px; gap: 2px; }
  .res-val { font-size: 10px; }
  /* ③-2 的 HUD 一键开关在 320px 下压到和货币 chip 同一档字号，才不会被挤到 hud-row 的第三行 */
  .hud-perf { padding: 0 5px; font-size: 10px; }
  .panel { padding: 8px; }
  .bf-grid { gap: 2px; }
  .bf-grid .unit { max-width: 46px; }
  .modal { width: 96vw; }
  .hero-grid { grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); }
  /* ① 页脚：320px 下 4 个槽位仍要一排放下（62px×4 + gap 已接近极限，收到 56px） */
  .party-strip { grid-template-columns: repeat(auto-fit, minmax(56px, 1fr)); gap: 4px; }
  /* 320px 档**不要**再给 .hero-art 写死 height：槽宽已压到 ~57px，
     上面 min(100%, 64px) 自然算出 57×57（> 彩色立绘的 48 下限），
     写死高度会让正方形前提失效、立绘在窄屏重新被横拉。 */
}
