/* 妖怪图鉴:香港篇 —— 雨夜霓虹主题
 *
 * 版式原则:整屏不滚动,战场居中最大。所有尺寸跟视口高度联动(clamp + vh),
 * 窄屏是等比缩小而不是换行,这样卡牌之间的空间关系永远读得懂。
 */

:root {
  --bg:      #0b0e14;
  --bg-2:    #10141d;
  --panel:   rgba(19, 25, 37, .74);
  --panel-2: rgba(28, 36, 52, .84);

  --atk:  #ff3d5a;   /* 朱红霓虹 = 攻 */
  --def:  #22d3ee;   /* 青碧霓虹 = 防 */
  --gold: #f5c542;   /* 暖金 = 资源 */
  --ink:  #e8edf7;
  --muted:#7f8da8;
  --line: rgba(125, 155, 195, .16);
  --ok:   #4ade80;

  /* 顶栏与常驻条(降神 / 海克斯)之外还剩多少 —— topstrip.js 把那个高度
   * 实时写进 --panel-top。**为什么需要它**:--ch-lg 原来只跟 vh 挂钩,vh 是
   * 算不到常驻条的;两条常驻条一露脸,战场需要的高度就超过它实际分到的,
   * 而 #arena 是 overflow:hidden —— 它不会缩,它会把守擂读数和累计攻击
   * **齐齐切掉**(实测 1440×900 差 66px、375×812 差 107px:真机上战况一个数
   * 都读不到)。有了这个量,卡牌会在挤不下时先缩,读数永远不被切。 */
  --stage-h: calc(100dvh - var(--panel-top, 46px));

  /* 卡牌基准尺寸(5:7)。以**高度**为主导跟视口联动 —— 垂直空间才是这个
   * 版式的瓶颈,按高度算才能保证三段永远塞得下且卡牌尽可能大。
   * --ch-lg 的第三项就是上面那张安全网:两侧(HUD + 结界区 + 间距)吃完
   * 之后,战场那一格还能给两张牌各分多少。系数由实测标定,见 --stage-h。 */
  --ch: clamp(88px, 12.8vh, 166px);
  --cw: calc(var(--ch) / 1.4);
  --ch-lg: clamp(140px, min(22.5vh, (var(--stage-h) - 2 * var(--ch) - 240px) / 2), 278px);
  --cw-lg: calc(var(--ch-lg) / 1.4);
  --ch-sm: clamp(56px, 7vh, 94px);         /* 垫牌 */
  --cw-sm: calc(var(--ch-sm) / 1.4);

  --drawer-h: clamp(196px, 27vh, 274px);
  --r: 9px;
  --ease: cubic-bezier(.22, .68, .36, 1);

  /* ── 共用度量衡(docs/design/视觉基调.md §3 §4)────────────────────
   * 三个 UX 层共用这一套,名字不许改。老板 2026-08-26:「战斗阶段,血量和
   * 攻击力的数字要大一点」—— 病根不是某个数太小,是全站基本只有一档 14px、
   * 没有主次。所以定四档,并且**每一屏只有一组东西能用 --t-hero**。
   * 窄屏与抽屉展开时这两档会被重定义(见对应的规则块),名字保持不变。 */
  --t-hero:  clamp(30px, 6.4vh, 52px);   /* 一屏唯一的主角读数 */
  --t-major: clamp(18px, 3.2vh, 26px);   /* 次要但要能瞟到的数值 */
  --t-body:  14px;                       /* 正文 / 卡名 / 按钮 */
  --t-label: 12px;                       /* 说明小字 / 标签 / 状态 */

  --fast:  120ms;   /* 点击、悬停的即时反馈 */
  --beat:  320ms;   /* 一次值得注意的变化 */
  --stage: 640ms;   /* 换场级的大事 */
}

/* 决策抽屉占掉下面 1/4 屏时,棋盘等比缩小而不是被裁掉 —— 局面必须全程可见 */
body.drawer-open {
  --stage-h: calc(100dvh - var(--panel-top, 46px) - var(--drawer-h));
  --ch: clamp(66px, 9.2vh, 120px);
  --ch-lg: clamp(88px, min(15.5vh, (var(--stage-h) - 2 * var(--ch) - 240px) / 2), 196px);
  --ch-sm: clamp(44px, 5.4vh, 72px);
  --t-hero:  clamp(24px, 4.6vh, 40px);
  --t-major: clamp(15px, 2.5vh, 21px);
}

* { box-sizing: border-box; }
/* 作者样式里的 display 会盖过 hidden 属性,必须显式压回去 */
[hidden] { display: none !important; }

html, body { height: 100%; }
body {
  margin: 0;
  height: 100dvh;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.5 "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
}

/* ─────────────────────────── 雨夜底景 ─────────────────────────── */

.scene { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.scene-glow {
  position: absolute; inset: 0;
  background:
    radial-gradient(58vw 42vh at 18% -6%, rgba(255, 61, 90, .17), transparent 64%),
    radial-gradient(52vw 38vh at 84% -2%, rgba(34, 211, 238, .15), transparent 64%),
    radial-gradient(46vw 34vh at 50% 106%, rgba(245, 197, 66, .08), transparent 68%),
    linear-gradient(180deg, var(--bg-2), var(--bg) 55%);
}
/* 湿柏油路的横向反光 */
.scene-wet {
  position: absolute; left: 0; right: 0; bottom: 0; height: 46%;
  background: repeating-linear-gradient(
    180deg, transparent 0 5px, rgba(130, 185, 225, .030) 5px 6px);
  -webkit-mask-image: linear-gradient(transparent, #000 85%);
          mask-image: linear-gradient(transparent, #000 85%);
}

/* ─────────────────────────── 顶栏 ─────────────────────────── */

#topbar {
  position: relative; z-index: 20;
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 18px;
  padding: 7px 16px;
  background: linear-gradient(180deg, rgba(11, 14, 20, .92), rgba(11, 14, 20, .55));
  border-bottom: 1px solid var(--line);
  backdrop-filter: blur(8px);
}
#topbar h1 {
  margin: 0; font-size: 17px; letter-spacing: 3px; font-weight: 700;
  color: var(--ink);
  text-shadow: 0 0 10px rgba(255, 61, 90, .55), 0 0 26px rgba(255, 61, 90, .25);
}
#topbar h1 em {
  font-style: normal; font-size: 11px; letter-spacing: 5px;
  color: var(--def); margin-left: 9px;
  text-shadow: 0 0 8px rgba(34, 211, 238, .7);
}
.tools { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.tools .sep { width: 1px; height: 20px; background: var(--line); }
.fld { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 12px; }

/* ── 指导视角常驻条(老板 2026-09-07) ──
   贴顶栏下沿,暖金细条 —— 老板开着角斗场也得看得见自己在看谁;图鉴是他
   主动打开的,层级不能压过它。夹在竞猜面板(66)与图鉴(68)之间:66 < 67 < 68。
   高度要登记进 topstrip.js 的 STRIPS,否则下面那些 fixed 面板会被它压住。 */
#coach-bar {
  /* top 只贴顶栏自己的高度(--topbar-h,topstrip.js 写),不能用 --panel-top ——
     那个数已经把 coach-bar 自己算进去了,会把它摞到自己脚底下 */
  position: fixed; left: 0; right: 0; top: var(--topbar-h, 46px);
  z-index: 67;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 5px 16px; font-size: 12.5px; color: var(--ink);
  background: linear-gradient(90deg, rgba(245, 197, 66, .16), rgba(245, 197, 66, .03) 80%);
  border-bottom: 1px solid rgba(245, 197, 66, .35);
}
#coach-who { color: var(--gold); }
#coach-pick {
  padding: 3px 8px; border-radius: 6px; font-size: 12px; font-family: inherit;
  background: rgba(8, 11, 17, .8); border: 1px solid var(--line); color: var(--ink);
}
#coach-off { padding: 3px 10px; min-height: 0; }

/* 指导视角下整屏禁手(老板 2026-09-07):服务端本来就拒 pid<0 的写请求,
   这道闸的价值是**别让教练白点** —— 看得见按钮却点不动,比点了没反应好得多。
   常驻条自己(#coach-bar)、切视角、顶栏、图鉴、详情浮层不禁 —— 那几样
   要么是只读的资料面板,要么就是教练本人切视角/退出跟看要用的控件。 */
body.coach #prep button, body.coach #prep input, body.coach #prep select,
body.coach #drawer button, body.coach #drawer input,
body.coach .yg-panel button,
body.coach #flip-cta,
body.coach #betting-pane button,
body.coach #roundend button,
body.coach #lobby-seated .primary,
body.coach [data-act] {
  pointer-events: none; opacity: .55;
}

/* 次要工具的折叠容器(牌库/种子/自动播放/图源)。
 * 宽屏(>700px)下强制展开、隐藏 ⚙ 开关 —— 视觉上和折叠前完全一样的一排;
 * ≤700px 时才由 @media 里的规则改成真正可点开/收起的面板。用 !important
 * 是因为要盖过浏览器对 <details> 未展开时子内容的原生隐藏规则。 */
/* display:contents 让 details 的盒子在宽屏上彻底消失,四个控件直接成为
 * .tools 行的 flex 项目 —— 否则 details 作为 flex item 宽度塌成 0,
 * 子控件会溢出盒子横躺到视口外(桌面回归实测抓到的)。 */
.more-tools { display: contents; }
.more-tools > summary { display: none; list-style: none; cursor: pointer; }
.more-tools > summary::-webkit-details-marker { display: none; }
.more-tools > summary::marker { content: ""; }
.more-tools > .more-tools-body {
  display: flex !important; align-items: center; gap: 12px;
}

select, input[type=number] {
  background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 7px;
  padding: 5px 8px; font-size: 12px; font-family: inherit;
}
input[type=number] { width: 5.5em; }
select:focus, input:focus { outline: 1px solid var(--def); outline-offset: 0; }

button {
  background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 7px;
  padding: 6px 13px; cursor: pointer; font-size: 13px; font-family: inherit;
  transition: border-color .16s, box-shadow .16s, transform .1s;
}
button:hover { border-color: rgba(34, 211, 238, .55); box-shadow: 0 0 14px rgba(34, 211, 238, .2); }
button:active { transform: translateY(1px); }
button:disabled { opacity: .38; cursor: not-allowed; box-shadow: none; border-color: var(--line); }
button.primary {
  background: linear-gradient(180deg, #ff5c74, #e02748);
  border-color: #ff7f92; color: #fff; font-weight: 600;
  box-shadow: 0 0 18px rgba(255, 61, 90, .38);
}
button.primary:hover { box-shadow: 0 0 26px rgba(255, 61, 90, .6); }
button.icon { padding: 5px 9px; font-size: 15px; line-height: 1; }
button.icon.off { opacity: .38; }

/* 自动播放开关 */
.switch { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 12px; cursor: pointer; }
.switch input { position: absolute; opacity: 0; pointer-events: none; }
.switch .track {
  width: 34px; height: 18px; border-radius: 10px; position: relative;
  background: rgba(120, 150, 190, .22); border: 1px solid var(--line);
  transition: background .18s;
}
.switch .track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 12px; height: 12px; border-radius: 50%; background: var(--muted);
  transition: transform .18s var(--ease), background .18s;
}
.switch input:checked + .track { background: rgba(34, 211, 238, .3); border-color: var(--def); }
.switch input:checked + .track::after {
  transform: translateX(16px); background: var(--def);
  box-shadow: 0 0 10px var(--def);
}

/* ─────────────────────────── 棋盘 ─────────────────────────── */

#board {
  position: relative; z-index: 10;
  flex: 1 1 auto; min-height: 0;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 4px;
  padding: 8px 14px 10px;
  transition: padding-bottom .28s var(--ease);
}
body.drawer-open #board { padding-bottom: var(--drawer-h); }
/* 桌面原本没有任何让位规则:1280×800 下棋盘 754px 减掉 46vh 抽屉只剩
   386px,两排结界区加两条 HUD 就把战场吃光。下面三条对所有宽度生效——
   窄屏块(见下方媒体查询)里同名规则值相同,层叠上后者覆盖前者不冲突。 */
body.drawer-open #arena { justify-content: flex-start; }
body.drawer-open .barrier-wrap { --ch: clamp(42px, 5.6vh, 58px); }
body.drawer-open { --ch-lg: clamp(84px, min(15.5vh, (var(--stage-h) - 2 * var(--ch) - var(--ch-sm) - 170px) / 2), 196px); }

.side { display: flex; flex-direction: column; gap: 5px; min-height: 0; }
.side.mine { justify-content: flex-end; }

/* ── HUD ── */
.hud {
  display: flex; align-items: center; gap: 14px;
  padding: 5px 12px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
}
.hud .who {
  font-weight: 700; font-size: 14px; letter-spacing: 1px;
  display: flex; align-items: center; gap: 8px;
}
.hud .role {
  font-size: 10px; letter-spacing: 2px; padding: 2px 8px; border-radius: 20px;
  border: 1px solid var(--line); color: var(--muted); white-space: nowrap;
}
.hud .role.atk {
  color: #fff; border-color: var(--atk); background: rgba(255, 61, 90, .18);
  box-shadow: 0 0 14px rgba(255, 61, 90, .4);
}
.hud .role.def {
  color: #fff; border-color: var(--def); background: rgba(34, 211, 238, .16);
  box-shadow: 0 0 14px rgba(34, 211, 238, .35);
}
.hud .stats { display: flex; align-items: center; gap: 13px; margin-left: auto; }
.stat { display: flex; align-items: baseline; gap: 5px; font-size: 11px; color: var(--muted); white-space: nowrap; }
.stat b { font-size: 14px; font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 700; }
/* 金币/愿力**不发光**:颜色已经说明了"这是可交易之物",辉光要留给
   "现在看这里"。备战阶段金币是主角,靠的是尺寸(见 .prep-purse)不是光。 */
.stat.gold b { color: var(--gold); }
/* 愿力也是暖金(2026-08-29 Lead 裁):紫是三色之外的**第四种主色**,不在 :root
   任何 token 里;而基调 §1 写着「凡是能换东西的都是金」—— 愿力终局能折算,
   正是可交易之物。跟结算屏那枚 chip 改成同一个色是**故意的**:同一个量在两块屏上
   一金一紫,自相矛盾比统一地错更难看。金币/愿力靠**标签文字**区分,不靠颜色。 */
.stat.wish b { color: var(--gold); }
/* HUD 里的「边角」一档:退场张数 / 金币 / 愿力在一场对战里根本不会变,
   它们不该跟判负量表抢注意力(量表满了是直接判负的)。 */
.hud .stat.minor { font-size: 10px; opacity: .72; }
.hud .stat.minor b { font-size: 12px; }

/* 判负进度条(幽冥 / 席位) */
.gauge { display: flex; flex-direction: column; gap: 3px; min-width: 62px; }
.gauge .g-lab { font-size: 10px; color: var(--muted); display: flex; justify-content: space-between; gap: 6px; }
.gauge .g-lab b { font-size: 12px; color: var(--ink); font-variant-numeric: tabular-nums; }
/* 逼近上限 = 快输了,数字本身也得跟着报警,不能只靠 3px 高的进度条 */
.gauge.warn .g-lab b { color: var(--gold); }
.gauge.crit .g-lab b { color: var(--atk); text-shadow: 0 0 10px rgba(255, 61, 90, .55); }
.gauge .g-bar { height: 3px; border-radius: 2px; background: rgba(120, 150, 190, .2); overflow: hidden; }
.gauge .g-bar i { display: block; height: 100%; background: var(--def); transition: width .35s var(--ease); }
.gauge.warn .g-bar i { background: var(--gold); }
.gauge.crit .g-bar i { background: var(--atk); box-shadow: 0 0 10px var(--atk); }

/* 牌库堆:轮到自己时会呼吸发光 */
.deckpile {
  position: relative; width: 30px; height: 40px; border-radius: 5px; flex: 0 0 auto;
  background: linear-gradient(150deg, #23304a, #131a28);
  border: 1px solid var(--line);
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.deckpile::before, .deckpile::after {
  content: ""; position: absolute; inset: 0; border-radius: 5px;
  border: 1px solid var(--line); background: inherit; z-index: -1;
}
.deckpile::before { transform: translate(2px, 2px); }
.deckpile::after  { transform: translate(4px, 4px); }
.deckpile.live {
  color: var(--gold); border-color: var(--gold); cursor: pointer;
  animation: breathe 1.7s ease-in-out infinite;
}

/* ── 结界区:固定席位槽 ── */
.barrier-wrap { display: flex; justify-content: center; align-items: center; gap: 10px; min-height: 0; }

/* 退场区 / 手牌区:结界区两侧的小卡堆 */
.side-pile {
  display: flex; align-items: center;
  padding: 5px 8px; border-radius: 10px;
  border: 1px dashed var(--line); background: rgba(16, 21, 32, .45);
  min-width: calc(var(--cw-sm) + 46px); min-height: calc(var(--ch-sm) + 10px);
  position: relative;
}
.side-pile .card { margin-left: calc(var(--cw-sm) * -0.6); }
.side-pile .card:first-child { margin-left: 0; }
/* 装备窗口开着的时候手牌要**摊开**。平时叠着是对的(一摞牌,省地方,只要
   知道有几张),但那一刻每一张都是可以单独拖起来的东西 —— 46px 宽的卡按
   -0.6 叠完只剩 18px 的抓取步距,手机上根本捏不准想要的那张,而"把法宝拖到
   卡上"正是这一版要做的交互。`.yg-eq-on` 由 equipdrag 在开窗时挂上。
   (这条是两个 UX agent 之间的接缝,谁都没认领,Lead 补。) */
.side-pile.hand.yg-eq-on .card { margin-left: 4px; }
.side-pile.hand.yg-eq-on .card:first-child { margin-left: 0; }
.side-pile.discard { cursor: pointer; }
.side-pile.discard:hover { border-color: rgba(255, 61, 90, .45); }
.side-pile.empty { opacity: .5; }
.pile-tag {
  position: absolute; bottom: -2px; left: 8px;
  font-size: 9.5px; color: var(--muted); letter-spacing: 1px; white-space: nowrap;
}
.pile-tag b { color: var(--ink); font-variant-numeric: tabular-nums; }
.pile-tag .ym {
  font-style: normal; margin-left: 6px; color: var(--atk);
  text-shadow: 0 0 8px rgba(255, 61, 90, .5);
}
/* 手牌区的法宝:轮到你时可点 */
.card.treasure-card { box-shadow: 0 0 0 1px rgba(245, 197, 66, .45), 0 4px 12px rgba(0,0,0,.5); }
.card.treasure-card.usable { cursor: pointer; }
.card.treasure-card.usable:hover {
  box-shadow: 0 0 0 2px var(--gold), 0 0 22px rgba(245, 197, 66, .55);
  transform: translateY(-6px) scale(1.06);
}

/* 区域展开(点退场区看全部)*/
#zoneview {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: flex-start; justify-content: center; overflow-y: auto;
  background: rgba(5, 7, 11, .86); backdrop-filter: blur(5px);
}
.zone-inner {
  margin-block: auto;
  max-width: min(1000px, 92vw); max-height: 84dvh;
  padding: 16px 20px; border-radius: 14px;
  background: rgba(14, 19, 29, .97); border: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 12px;
}
.zone-head {
  display: flex; align-items: center;
  font-size: 13px; letter-spacing: 3px; color: var(--gold);
}
.zone-head button { margin-left: auto; }
.zone-body {
  display: flex; flex-wrap: wrap; gap: 10px;
  overflow-y: auto; align-content: flex-start;
}
.barrier {
  display: flex; gap: 6px; align-items: flex-start;
  padding: 5px 8px; border-radius: 10px;
  background: rgba(16, 21, 32, .5); border: 1px solid var(--line);
  transition: border-color var(--fast) linear;
}
/* 满席预告(战斗演出计划 P0-3):席位摆满 = 下一张不同名的卡进来就当场判负。
   玩家不看 HUD 的席位量表 —— 他盯着卡位,所以结界区整块自己说话。
   **一条朱红实线,不加辉光、不加动画**:对战中屏上已经有守擂/攻擂两处在亮,
   基调铁律二不许再点第三盏灯。它是"这里已经满了"的状态,不是一段戏,
   所以只用 --fast 做状态切换,关掉动效也不影响它成立。 */
.barrier.full { border-color: var(--atk); }
.slot {
  width: var(--cw); height: var(--ch); border-radius: var(--r);
  border: 1px dashed rgba(125, 155, 195, .26);
  background: rgba(255, 255, 255, .015);
  position: relative; flex: 0 0 auto;
  display: grid; place-items: center;
}
.slot.empty::after {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: rgba(125, 155, 195, .22);
}
.slot.wide { width: calc(var(--cw) * 2 + 6px); }   /* 红孩儿横置占两席 */
.slot.filled { border-style: solid; border-color: rgba(34, 211, 238, .3); }
.slot .stackn {
  position: absolute; top: -6px; right: -6px; z-index: 3;
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: 10px;
  background: var(--gold); color: #1a1206;
  font-size: 11px; font-weight: 800; line-height: 19px; text-align: center;
  box-shadow: 0 0 12px rgba(245, 197, 66, .55);
}
.barrier .cap {
  align-self: center; font-size: 11px; color: var(--muted);
  padding-left: 8px; margin-left: 2px; border-left: 1px solid var(--line);
  white-space: nowrap;
}
.barrier .cap b { color: var(--ink); font-variant-numeric: tabular-nums; }
.barrier .cap.crit b { color: var(--atk); text-shadow: 0 0 10px rgba(255, 61, 90, .5); }

/* ─────────────────────────── 战场 ─────────────────────────── */

#arena {
  position: relative; min-height: 0;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: clamp(4px, 1.2vh, 14px);
  border-radius: 14px;
  background:
    radial-gradient(70% 90% at 50% 50%, rgba(34, 211, 238, .05), transparent 70%),
    rgba(10, 13, 20, .42);
  border: 1px solid var(--line);
  overflow: hidden;
}

/* 播报条 */
#announce {
  /* 居中不用 left:50% + translateX(-50%):那样"可用宽度"只剩容器的一半,
     shrink-to-fit 会把它掐到 173px,一条十三个字的播报硬生生折成两行,
     还正好折在「结界|区爆了」中间。改成左右都钉住 + margin:auto 居中,
     max-content 想多宽就多宽,到 max-width 才折。 */
  position: absolute; top: 8px; left: 12px; right: 12px;
  width: max-content; margin: 0 auto;
  /* z-index 是修出来的 bug(演出计划 §0 实测):原来是 auto,DOM 序又排在
     卡位之前 —— 375px 窄屏下 elementFromPoint 量到播报条中心盖着的是
     .meter-head,整条被守擂卡和防御读数压在底下。6 = 高于卡位(.card 悬停 5、
     角标 4),低于扑上去的那张卡(.lunge 8)和所有浮层:播报条是"念给你听的
     一行字",不该抢卡牌的镜头,但绝不能被盖掉。 */
  z-index: 6;
  /* 原来是 white-space:nowrap 又没有宽度上限,窄屏下整条溢出 #arena
     (它 overflow:hidden),左端直接被裁:「电脑2 落败 · 结界区爆了」实际
     渲染成「脑2 落败 · 结界区爆了」。改成夹在容器里 + 允许换行 ——
     宁可占两行,不许少一个字。 */
  max-width: calc(100% - 24px); text-align: center; line-height: 1.4;
  box-sizing: border-box;
  font-size: clamp(12px, 1.7vh, 15px); letter-spacing: 2px;
  color: var(--muted); pointer-events: none;
  padding: 3px 16px; border-radius: 20px;
  /* 底色从 .6 提到 .92:允许换行之后它会压在「累计攻击 / 还没翻牌」上面,
     半透的话两层字叠在一起谁都读不清 —— 跟 showcase 字幕条的 .96 同一族。 */
  background: rgba(10, 13, 20, .92); border: 1px solid transparent;
  opacity: 0; transition: opacity .2s;
}
#announce.on { opacity: 1; }
#announce.hot {
  color: #fff; border-color: var(--atk);
  text-shadow: 0 0 12px rgba(255, 61, 90, .8);
  box-shadow: 0 0 22px rgba(255, 61, 90, .3);
}
#announce.cool { color: var(--def); border-color: rgba(34, 211, 238, .5); }

/* ── 两个卡位:上下对称,谁攻擂谁的卡冲过去 ── */

.slot-row {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 16px; width: 96%;
  transition: filter .25s;
}
.slot-row .spent { justify-self: end; display: flex; align-items: center; gap: 4px; }
.slot-row .side-info { justify-self: start; min-width: clamp(150px, 21vw, 250px); }
/* 非攻擂方压暗一档 —— 一眼看出这回合谁在动 */
.slot-row:not(.is-attacker) { filter: brightness(.92); }

.slot-row .slot {
  width: var(--cw-lg); height: var(--ch-lg);
  border-radius: var(--r); position: relative;
  display: grid; place-items: center;
  border: 1px dashed rgba(125, 155, 195, .2);
}
.slot-row .slot:has(.card) { border-color: transparent; }
/* 攻方卡位原来还罩着一圈 40px 的红晕 —— 跟 .card.attacker 那圈红框说的是
   同一句话,而"非攻方整体压暗一档"已经把这件事讲清楚了。一屏至多一个光源
   (视觉基调 §2 铁律二),这一圈删掉。 */
/* 空卡位要看得出"牌会出现在这儿",否则半个屏幕像是坏了 */
.slot-row .slot:empty::after {
  content: "待翻牌"; font-size: 11px; letter-spacing: 3px;
  color: var(--muted); opacity: .45;
}
.slot-row:not(.is-attacker) .slot:empty::after { content: "无守擂卡"; }

.spent .card { margin-left: calc(var(--cw-sm) * -0.55); }
.spent .card:first-child { margin-left: 0; }
.spent .card.used { filter: saturate(.45) brightness(.65); }
.spent-tag {
  font-size: 10px; color: var(--muted); letter-spacing: 1px;
  margin-left: 8px; white-space: nowrap;
}

.side-info .none { color: var(--muted); font-size: 12px; }

/* ── 对战这一屏的主角:**还差几点打穿** ─────────────────────────────
   这一屏玩家真正要拿的主意只有一个 —— 这一下要不要押上资源(装法宝 / 烧
   技能)。做这个决定只依赖一个读数:守擂卡还剩多少防御。「累计攻击」是
   已经发生过的账(而且它和「已打进」几乎是同一个数:to_break + dealt =
   总防),单看它决定不了任何事;卡面的攻/防是这个决定的输入,给 --t-major。
   所以 --t-hero 全屏只给「还差 N」这一组。 */

/* 攻方那侧的累计攻击 —— 配角:它是战果回执,不是决策依据 */
.tally { line-height: 1.1; }
.tally b {
  display: block; font-size: var(--t-major); font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--atk);
}
.tally .lab {
  display: block; margin-top: 2px;
  font-size: var(--t-label); letter-spacing: 2px; color: var(--muted);
}
.tally .sub { display: block; font-size: 10.5px; letter-spacing: 1px; color: var(--muted); }
.tally.bump b { animation: bump var(--beat) var(--ease); }

/* 守方那侧的防御计量 */
.meter-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.meter-name {
  font-size: var(--t-body); font-weight: 700; letter-spacing: 1px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.meter-num {
  display: flex; align-items: baseline; gap: 5px; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--def);
}
.meter-num em {
  font-style: normal; font-size: var(--t-label); letter-spacing: 2px; color: var(--muted);
}
/* 主角读数**不发光**:辉光是"现在看这里"的记号,这一屏已经有攻/守两圈卡框
   在用它了(视觉基调 §2 铁律二)。层级靠尺寸,不靠再点一盏灯。 */
.meter-num b { font-size: var(--t-hero); font-weight: 700; }
.meter-num span { font-size: var(--t-label); letter-spacing: 2px; }
/* 青碧 = 防御 = 结构。快打穿了**不**改色 —— 颜色是语义不是强调(铁律一),
   "快没了"由那条防御条自己缩短来说。真打穿了才转朱红:那时确实落了一笔。 */
.meter-num.broken { color: var(--atk); }
.meter-num.broken em { display: none; }
.meter .bar {
  position: relative; height: 11px; margin: 5px 0 4px;
  border-radius: 6px; overflow: hidden;
  background: rgba(120, 150, 190, .16);
  border: 1px solid var(--line);
}
.meter .bar u {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, #0ea5b7, var(--def));
  box-shadow: 0 0 14px rgba(34, 211, 238, .5);
  transition: width .34s var(--ease);
}
/* 刚被打掉的那一段:红色余烬,渐隐 */
.meter .bar s {
  position: absolute; top: 0; bottom: 0; width: 0;
  background: var(--atk); box-shadow: 0 0 14px var(--atk);
  opacity: 0;
}
.meter .bar s.flash { animation: ember .62s ease-out; }
/* 已打进 / 总防是背景资料:它俩加起来就是上面那个主角数,重复播报没有意义,
   留着只为让人知道这场僵持了多久 */
.meter-foot { font-size: 10.5px; color: var(--muted); min-height: 15px; letter-spacing: 1px; }
.meter-foot b { font-size: var(--t-label); color: var(--ink); font-variant-numeric: tabular-nums; }
.meter-foot i { font-style: normal; font-variant-numeric: tabular-nums; }

/* ── 观战别桌:「还差 N 点打穿」变了就点一下头(第七批) ──────────────────
   跟 `.tally.bump b` 同一套语汇、同一个 keyframes —— 不为观战另发明一种
   "数字变了"的表现(基调 §5)。锚在 `#board.watching` 下,而那个 class 只在
   `snap.observer` 为真(= 别人那桌)时才挂上,自己那桌逐字不变。

   **防御条这里没有新规则,是故意的。** 上面 `.meter .bar u` 本来就带着
   `transition: width .34s` —— 但 `renderSlots` 每帧重建 innerHTML,**新元素的
   第一份计算样式就是新宽度**,过渡从来没有起点可滑,那条规则在对账这条路上
   一直是死的(它只对 `meterDamage()` 那种就地改宽度的路有效)。所以要让它活
   过来靠的不是再加一条 CSS,而是 board.js 的 `slideBar()`:先把起点设回上一帧,
   再设新值。再写一条 `#board.watching …` 只会让后人以为"过渡是观战才有的"。 */
#board.watching .meter-num.bump b { animation: bump var(--beat) var(--ease); }

/* 撞击区 */
#clash { position: relative; height: clamp(14px, 3vh, 32px); width: 100%; }
#clash::before {
  content: ""; position: absolute; left: 50%; top: 0; height: 100%; width: 1px;
  background: linear-gradient(rgba(255, 61, 90, .1), rgba(255, 61, 90, .5), rgba(255, 61, 90, .1));
}

/* ── 翻牌按钮 ── */
#flip-cta { margin-top: 2px; }
#flip-btn {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 24px; border-radius: 30px;
  background: linear-gradient(180deg, rgba(245, 197, 66, .2), rgba(245, 197, 66, .07));
  border: 1px solid var(--gold); color: var(--gold);
  animation: breathe 1.7s ease-in-out infinite;
}
#flip-btn:hover { color: #fff; box-shadow: 0 0 30px rgba(245, 197, 66, .55); }
.flip-ico { font-size: 21px; line-height: 1; }
.flip-txt { font-size: 15px; font-weight: 700; letter-spacing: 2px; }
.flip-sub { font-size: 10px; color: var(--muted); letter-spacing: 1px; }

/* ─────────────────────────── 卡牌 ─────────────────────────── */

.card {
  position: relative; flex: 0 0 auto;
  width: var(--cw); height: var(--ch);
  border-radius: var(--r); overflow: hidden;
  background: linear-gradient(160deg, #1d2637, #10151f);
  box-shadow: 0 0 0 1px rgba(160, 190, 230, .12), 0 5px 16px rgba(0, 0, 0, .6);
  cursor: pointer;
  transition: transform .18s var(--ease), box-shadow .18s;
}
.card:hover { transform: translateY(-4px) scale(1.03); z-index: 5; }
.card img { width: 100%; height: 100%; object-fit: cover; display: block; }

.card.sm { width: var(--cw-sm); height: var(--ch-sm); }
.card.lg { width: var(--cw-lg); height: var(--ch-lg); }

.card .fallback {
  width: 100%; height: 100%; padding: 5px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  text-align: center; line-height: 1.2;
}
.card .fallback .nm { font-size: 12px; font-weight: 700; }
.card .fallback .rr { font-size: 9px; color: var(--muted); letter-spacing: 1px; }

/* 角标数值 */
.card .nums {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: space-between; align-items: flex-end;
  padding: 14px 5px 3px;
  background: linear-gradient(transparent, rgba(4, 6, 10, .92));
  font-size: clamp(10px, 1.5vh, 13px); font-weight: 800;
  font-variant-numeric: tabular-nums; pointer-events: none;
}
.card .nums .a { color: var(--atk); text-shadow: 0 0 7px rgba(255, 61, 90, .85); }
.card .nums .d { color: var(--def); text-shadow: 0 0 7px rgba(34, 211, 238, .85); }
/* 有加成:数字提亮,增量缀在后面 —— 光看 17 攻不知道这 17 是怎么来的 */
.card .nums .up { color: #fff; }
.card .nums .a.up { text-shadow: 0 0 9px var(--atk), 0 0 20px rgba(255, 61, 90, .7); }
.card .nums .d.up { text-shadow: 0 0 9px var(--def), 0 0 20px rgba(34, 211, 238, .7); }
.card .nums .down { color: #b9c4d6; }
.card .nums i {
  font-style: normal; font-size: .72em; margin-left: 1px; opacity: .95;
}
.card .nums .a.up i { color: var(--ok); }
.card .nums .d.up i { color: var(--ok); }
.card .nums .down i { color: var(--muted); }
.card.sm .nums { padding: 10px 3px 2px; font-size: 10px; }
/* 战场正中那两张牌是全场主角,它们的攻/防就是老板说的「血量和攻击力」——
   比结界区那些大一档还多,手机上瞟一眼就能读到 */
.card.lg .nums { padding: 22px 7px 4px; font-size: var(--t-major); }
.card.lg .nums i { font-size: .58em; }

/* 牌背:点下翻牌到真牌到达之间的占位 */
.card.back {
  background:
    repeating-linear-gradient(45deg, rgba(255,61,90,.07) 0 8px, transparent 8px 16px),
    linear-gradient(160deg, #232f45, #131a28);
  box-shadow: 0 0 0 1px rgba(160,190,230,.16), 0 5px 16px rgba(0,0,0,.6);
}
.card.back::after {
  content: "🂠"; position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: clamp(24px, 4vh, 44px); color: rgba(255,61,90,.5);
}
.card.back.pending { animation: pulse .5s ease-in-out infinite alternate; }
@keyframes pulse { from { opacity: .55; } to { opacity: 1; } }

/* 状态 */
.card.defender { box-shadow: 0 0 0 2px var(--def), 0 0 26px rgba(34, 211, 238, .5), 0 6px 20px rgba(0, 0, 0, .7); }
.card.attacker { box-shadow: 0 0 0 2px var(--atk), 0 0 22px rgba(255, 61, 90, .45), 0 6px 20px rgba(0, 0, 0, .7); }
.card.spent   { filter: saturate(.5) brightness(.7); }
.card.picked  { box-shadow: 0 0 0 2px var(--gold), 0 0 24px rgba(245, 197, 66, .6); transform: translateY(-6px); }
.card .badge {
  position: absolute; top: -7px; left: -7px; z-index: 4;
  width: 21px; height: 21px; border-radius: 50%;
  background: var(--gold); color: #1a1206;
  font-size: 12px; font-weight: 800; line-height: 21px; text-align: center;
  box-shadow: 0 0 12px rgba(245, 197, 66, .6);
}

/* 动画状态 */
.card.enter { animation: dealIn .34s var(--ease) backwards; }
.card.hurt  { animation: shake .3s ease; }
.card.lunge-down { animation: lungeDown .4s var(--ease); z-index: 8; }
.card.lunge-up   { animation: lungeUp .4s var(--ease); z-index: 8; }
.card.shatter { animation: shatter .5s ease-in forwards; }

/* ─────────────────────────── 卡牌详情浮层 ─────────────────────────── */

/* 老板 2026-09-07:「竞猜层没必要盖住图鉴」,卡名悬浮也要看得到卡和介绍。
   详情是悬浮说明,必须高于竞猜面板(66)、图鉴(68)、区域查看 #zoneview(70),
   仍低于结算/终局模态(75)、裁决幕(76)、终局横幅(80)。 */
#detail {
  position: fixed; top: 58px; right: 12px; z-index: 72;
  width: clamp(230px, 23vw, 310px); max-height: calc(100dvh - 76px);
  overflow-y: auto;
  padding: 12px; border-radius: 13px;
  background: rgba(12, 16, 24, .95);
  border: 1px solid rgba(34, 211, 238, .35);
  box-shadow: 0 0 44px rgba(0, 0, 0, .78), 0 0 26px rgba(34, 211, 238, .14);
  animation: slideL .18s var(--ease);
  pointer-events: none;
}
#detail.left { right: auto; left: 12px; animation: slideR .18s var(--ease); }
#detail img {
  width: 100%; border-radius: 9px; display: block; margin-bottom: 9px;
  box-shadow: 0 5px 18px rgba(0, 0, 0, .6);
}
.d-name { font-size: 16px; font-weight: 700; letter-spacing: 1px; }
.d-tags { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0 9px; }
.d-tag {
  font-size: 10px; padding: 2px 8px; border-radius: 20px;
  border: 1px solid var(--line); color: var(--muted); letter-spacing: 1px;
}
.d-tag.r-SSR, .d-tag.r-SP { color: var(--gold); border-color: rgba(245, 197, 66, .5); }
.d-tag.r-SR { color: #c084fc; border-color: rgba(192, 132, 252, .45); }
/* 法宝阶别(天/专属天级/地/专属)沿用同一套配色语汇,不新起色板 */
.d-tag.r-天, .d-tag.r-专属天级 { color: var(--gold); border-color: rgba(245, 197, 66, .5); }
.d-tag.r-地 { color: var(--def); border-color: rgba(34, 211, 238, .35); }
.d-tag.attr { color: var(--def); border-color: rgba(34, 211, 238, .35); }
.d-tag.warn { color: var(--gold); border-color: rgba(245, 197, 66, .4); }

.d-stat {
  display: flex; gap: 14px; padding: 8px 10px; margin-bottom: 9px;
  border-radius: 9px; background: rgba(255, 255, 255, .035);
}
.d-stat div { flex: 1; text-align: center; }
.d-stat span { display: block; font-size: 10px; color: var(--muted); letter-spacing: 1px; }
.d-stat b { font-size: 21px; font-variant-numeric: tabular-nums; }
.d-stat .a b { color: var(--atk); text-shadow: 0 0 10px rgba(255, 61, 90, .55); }
.d-stat .d b { color: var(--def); text-shadow: 0 0 10px rgba(34, 211, 238, .55); }
.d-stat small { font-size: 10px; color: var(--muted); display: block; }

.d-faces { display: flex; gap: 8px; margin-bottom: 9px; }
.d-faces .face {
  flex: 1; text-align: center; padding: 6px 4px; border-radius: 8px;
  background: rgba(255,255,255,.035); border: 1px solid var(--line);
}
.d-faces .face span { display: block; font-size: 9.5px; color: var(--muted); letter-spacing: 1px; }
.d-faces .face b { font-size: 15px; font-variant-numeric: tabular-nums; }
.d-faces .face i { font-style: normal; }
.d-faces .face .a { color: var(--atk); }
.d-faces .face .d { color: var(--def); }
.d-faces .face.spirit { border-color: rgba(34,211,238,.4); }

.d-text {
  font-size: 12.5px; line-height: 1.72; color: #cdd7e6;
  padding: 9px 10px; border-radius: 9px;
  background: rgba(255, 255, 255, .03);
  border-left: 2px solid rgba(34, 211, 238, .45);
  white-space: pre-wrap;
}
.d-sec { font-size: 10px; letter-spacing: 2px; color: var(--muted); margin: 11px 0 5px; }
.d-bonus { display: flex; flex-direction: column; gap: 3px; }
.d-bonus li {
  display: flex; align-items: baseline; gap: 7px; list-style: none;
  font-size: 11.5px; padding: 3px 7px; border-radius: 6px;
  background: rgba(255, 255, 255, .03);
}
.d-bonus .bt { font-size: 9px; color: var(--muted); border: 1px solid var(--line); border-radius: 4px; padding: 0 4px; }
.d-bonus .bf { flex: 1; color: #cdd7e6; }
.d-bonus .bv { font-variant-numeric: tabular-nums; font-weight: 700; }
.d-bonus .bv .a { color: var(--atk); }
.d-bonus .bv .d { color: var(--def); }
.d-bonus ul, .d-bonus { margin: 0; padding: 0; }

/* ─────────────────────────── 决策抽屉 ─────────────────────────── */

#drawer {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
  /* 高度和棋盘让出的 padding 是同一个值 —— 否则要么遮住 HUD 要么留一条缝 */
  height: var(--drawer-h);
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, rgba(14, 19, 29, .97), rgba(9, 12, 18, .99));
  border-top: 1px solid rgba(245, 197, 66, .45);
  box-shadow: 0 -14px 46px rgba(0, 0, 0, .68), 0 -1px 26px rgba(245, 197, 66, .12);
  animation: riseUp .26s var(--ease);
}
.drawer-head {
  display: flex; align-items: center; gap: 16px;
  padding: 9px 18px; border-bottom: 1px solid var(--line);
}
.drawer-title { font-size: 14px; font-weight: 700; color: var(--gold); letter-spacing: 1px; }
.drawer-hint { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
/* 抽屉头上的对战读数(内容来自 board.clashLine,见 decide.js 的 .yg-clash)。
   抽屉一展开,#arena 就把盘面上那条「还差几点打穿」裁掉了 —— 这一行是替身,
   所以它只许占一行:多一行 = 棋盘再少一行,等于自己拆自己的台。 */
.drawer-clash {
  margin-top: 2px; font-size: var(--t-label); line-height: 1.4;
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.drawer-clash .who { color: var(--ink); }
/* 几个 span 之间没有空白节点,不给间距会连成"守擂还差4点打穿" */
.drawer-clash b.def {
  color: var(--def); font-size: var(--t-body); font-weight: 700; margin: 0 5px;
}
.drawer-clash .lab { color: var(--muted); }
.drawer-clash .foot { color: var(--muted); margin-left: 8px; }
.drawer-act { display: flex; align-items: center; gap: 12px; margin-left: auto; }
#drawer-count { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.drawer-body {
  flex: 1; min-height: 0; overflow: auto;
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px;
  padding: 12px 18px 16px;
}

.opt-card { display: flex; flex-direction: column; align-items: center; gap: 4px; width: var(--cw); }
.opt-card .cap { font-size: 11px; text-align: center; line-height: 1.25; }
.opt-btn {
  min-width: 108px; padding: 11px 20px; font-size: 14px;
  background: var(--panel-2);
  /* 选项文案里可以带换行(降神卡是「名字 + 卡面原文」两行),
     默认的 normal 会把 \n 压成空格,七张卡挤成七坨字 */
  white-space: pre-line; text-align: left; max-width: min(560px, 92vw);
  line-height: 1.55;
}
.opt-btn.sel {
  border-color: var(--gold); color: var(--gold);
  box-shadow: 0 0 20px rgba(245, 197, 66, .35) inset, 0 0 16px rgba(245, 197, 66, .25);
}

/* 分配点数滑杆 */
.alloc { width: min(560px, 100%); }
.alloc-preview { display: flex; align-items: center; justify-content: center; gap: 26px; margin-bottom: 12px; }
.alloc-preview div { text-align: center; }
.alloc-preview span { display: block; font-size: 10px; letter-spacing: 2px; color: var(--muted); }
.alloc-preview b { font-size: 34px; font-variant-numeric: tabular-nums; }
.alloc-preview .a b { color: var(--atk); text-shadow: 0 0 16px rgba(255, 61, 90, .6); }
.alloc-preview .d b { color: var(--def); text-shadow: 0 0 16px rgba(34, 211, 238, .6); }
.alloc-preview .arrow { font-size: 12px; color: var(--muted); }
.alloc input[type=range] {
  width: 100%; height: 26px; -webkit-appearance: none; appearance: none;
  background: transparent; cursor: pointer;
}
.alloc input[type=range]::-webkit-slider-runnable-track {
  height: 7px; border-radius: 4px;
  background: linear-gradient(90deg, var(--atk) var(--pct, 50%), var(--def) var(--pct, 50%));
}
.alloc input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; margin-top: -6px;
  width: 19px; height: 19px; border-radius: 50%;
  background: #fff; border: 2px solid var(--gold);
  box-shadow: 0 0 14px rgba(245, 197, 66, .7);
}
.alloc .ticks { display: flex; justify-content: space-between; font-size: 10px; color: var(--muted); }

/* ─────────────────────────── 战报 ─────────────────────────── */

#logpane {
  position: fixed; top: 50px; right: 0; bottom: 0; z-index: 58;
  width: min(360px, 84vw);
  display: flex; flex-direction: column;
  background: rgba(10, 13, 20, .96); border-left: 1px solid var(--line);
  box-shadow: -18px 0 48px rgba(0, 0, 0, .6);
  animation: slideL .22s var(--ease);
}
.log-head {
  display: flex; align-items: center;
  padding: 9px 14px; border-bottom: 1px solid var(--line);
  color: var(--gold); letter-spacing: 3px; font-size: 12px;
}
.log-head button { margin-left: auto; }
#log {
  flex: 1; margin: 0; padding: 8px 12px; list-style: none;
  overflow-y: auto; font-size: 12px; line-height: 1.65;
}
#log li { padding: 2px 0; border-bottom: 1px solid rgba(125, 155, 195, .07); }
#log li.sub { padding-left: 15px; color: var(--muted); }
#log li.deep { padding-left: 30px; color: var(--muted); opacity: .82; }
#log .hl { color: var(--gold); font-weight: 600; }
#log .dmg { color: var(--atk); font-weight: 700; }
#log .brk { color: #fff; background: rgba(255, 61, 90, .16); padding: 2px 6px; border-radius: 4px; font-weight: 700; }
#log .ok { color: var(--ok); }

/* ─────────────────────────── 图鉴 ───────────────────────────
 * 资料册,不是操作面板 —— 跟战报抽屉一样右侧停靠、无遮罩,大厅/对局
 * 随时可开都不挡事:没有全屏背景板吃点击,翻牌按钮和棋盘在它旁边照点不误。
 * 卡格子沿用构筑台(#build)那套 --build-cw + .cardgrid 的响应式尺寸,
 * 在 #codex 作用域下重新声明,不碰 build.js 那边的 #build 专属样式。
 *
 * 卡不直接当网格项,包一层 .codex-cell(codex.js 建):.card 自带
 * overflow:hidden,Chromium 给这种网格项算行高时会丢掉 aspect-ratio 撑出的
 * 高度,行全塌成 2px、96 张卡叠成一摞(老板 2026-08-28:「图鉴里妖怪的卡
 * 不要全部叠在一起」,病根即此)。塞进普通块级容器里行高就按内容算了。
 * 老板 2026-09-07:「竞猜层没必要盖住图鉴」。图鉴是玩家从顶栏主动开的,
 * 必须高于竞猜面板(66),不该被它埋掉;仍在区域查看 #zoneview(70)之下。
 */
#codex {
  position: fixed; top: 50px; right: 0; bottom: 0; z-index: 68;
  /* 桌面稍宽:520px 下 auto-fill 摆得开 4 列,翻起来少滚一半 */
  width: min(520px, 92vw);
  display: flex; flex-direction: column;
  background: rgba(10, 13, 20, .97); border-left: 1px solid var(--line);
  box-shadow: -18px 0 48px rgba(0, 0, 0, .6);
  animation: slideL .22s var(--ease);
  --build-cw: clamp(84px, 11vw, 118px);
}
.codex-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 12px; border-bottom: 1px solid var(--line);
  background: var(--panel-2);
}
.codex-head h2 {
  margin: 0; font-size: 14px; color: var(--gold); letter-spacing: 2px;
}
.codex-head .spacer { flex: 1; }
.codex-tabs { display: flex; gap: 6px; }
.codex-tab {
  padding: 6px 13px; font-size: 12.5px;
  background: rgba(8, 11, 17, .6); color: var(--muted);
}
.codex-tab.on {
  color: var(--ink); border-color: var(--gold); background: rgba(245, 197, 66, .14);
}
.codex-bar {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: 8px 12px; border-bottom: 1px solid var(--line);
}
.codex-bar input, .codex-bar select {
  padding: 6px 10px; border-radius: 7px; font-size: 12px; font-family: inherit;
  background: rgba(8, 11, 17, .8); border: 1px solid var(--line); color: var(--ink);
}
.codex-bar input { flex: 1; min-width: 96px; }
#codex .cardgrid { flex: 1; padding: 10px 10px 14px; }
#codex .codex-cell { min-width: 0; }
#codex .codex-cell .card { width: 100%; height: auto; aspect-ratio: 1 / 1.4; cursor: pointer; }
#codex .card.sm .nm { font-size: clamp(9px, .82vw, 12px); }
#codex .card.sm .st { font-size: clamp(9px, .8vw, 12px); }
/* 降神卡是整段文字,塞进 ~110px 的卡格里一行只剩两三个字 —— 横跨整行 */
#codex .godsheet { grid-column: 1 / -1; margin-bottom: 0; }
.codex-empty { grid-column: 1 / -1; text-align: center; padding: 24px 0; }

/* ─────────────────────────── 浮动特效 ─────────────────────────── */

.float {
  position: fixed; z-index: 70; pointer-events: none;
  font-weight: 800; font-variant-numeric: tabular-nums;
  animation: floatUp .95s var(--ease) forwards;
}
.float.dmg { font-size: clamp(30px, 5.2vh, 58px); color: #fff; text-shadow: 0 0 16px var(--atk), 0 0 40px var(--atk); }
.float.blk { font-size: clamp(15px, 2.4vh, 22px); color: var(--def); text-shadow: 0 0 12px var(--def); }
.float.note { font-size: 13px; color: var(--gold); text-shadow: 0 0 12px rgba(245, 197, 66, .8); }
.float s { font-size: .58em; color: var(--muted); margin-left: 7px; font-weight: 600; }

/* 打穿:整屏霓虹闪断 */
#flashlayer {
  position: fixed; inset: 0; z-index: 65; pointer-events: none; opacity: 0;
  background: radial-gradient(60% 50% at 50% 50%, rgba(255, 61, 90, .5), transparent 72%);
}
#flashlayer.go { animation: neonCut .5s ease-out; }
body.quake #board { animation: quake .4s ease; }

/* ─────────────────────────── 终局 / 启动 ─────────────────────────── */

#endbanner, #splash {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: flex-start; justify-content: center; text-align: center;
  overflow-y: auto;
  background: rgba(5, 7, 11, .85); backdrop-filter: blur(5px);
}
/* 盖住棋盘(10)但必须低于顶栏(20),否则「开新对局」按钮点不到 */
#splash { z-index: 15; }
.splash-inner h2 { color: var(--gold); margin: 0 0 10px; letter-spacing: 3px; text-shadow: 0 0 22px rgba(245, 197, 66, .45); }
.splash-inner p { color: var(--muted); margin: 0; line-height: 1.9; }
.end-inner {
  margin-block: auto;
  padding: 34px 54px; border-radius: 16px;
  background: rgba(14, 19, 29, .96); border: 1px solid rgba(245, 197, 66, .45);
  box-shadow: 0 0 70px rgba(0, 0, 0, .7), 0 0 40px rgba(245, 197, 66, .12);
  animation: dealIn .34s var(--ease);
}
.end-title {
  font-size: 30px; font-weight: 800; letter-spacing: 5px; color: var(--gold);
  text-shadow: 0 0 26px rgba(245, 197, 66, .6);
}
.end-title.lose { color: var(--atk); text-shadow: 0 0 26px rgba(255, 61, 90, .6); }
.end-sub { color: var(--muted); margin: 12px 0 20px; }

/* ─────────────────────────── keyframes ─────────────────────────── */

@keyframes dealIn  { from { opacity: 0; transform: translateY(-16px) scale(.86) rotateY(28deg); } }
@keyframes shake   { 0%,100% { transform: translateX(0); } 22% { transform: translateX(-6px); } 62% { transform: translateX(5px); } }
@keyframes lungeDown { 0%,100% { transform: translateY(0); }
                       45% { transform: translateY(34px) scale(1.06); } }
@keyframes lungeUp   { 0%,100% { transform: translateY(0); }
                       45% { transform: translateY(-34px) scale(1.06); } }
@keyframes shatter { to { opacity: 0; transform: scale(1.16) rotate(4deg); filter: brightness(2.4) blur(3px); } }
@keyframes bump    { 0%,100% { transform: scale(1); } 40% { transform: scale(1.28); } }
@keyframes floatUp { 0% { opacity: 0; transform: translate(-50%, 0) scale(.6); }
                     18% { opacity: 1; transform: translate(-50%, -12px) scale(1.12); }
                     100% { opacity: 0; transform: translate(-50%, -74px) scale(.95); } }
@keyframes ember   { 0% { opacity: .95; } 100% { opacity: 0; } }
@keyframes neonCut { 0% { opacity: 0; } 12% { opacity: 1; } 26% { opacity: .15; } 40% { opacity: .9; } 100% { opacity: 0; } }
@keyframes quake   { 0%,100% { transform: translate(0,0); } 25% { transform: translate(-5px, 3px); }
                     50% { transform: translate(4px, -3px); } 75% { transform: translate(-3px, -2px); } }
@keyframes breathe { 0%,100% { box-shadow: 0 0 12px rgba(245, 197, 66, .28); }
                     50% { box-shadow: 0 0 28px rgba(245, 197, 66, .6); } }
@keyframes riseUp  { from { transform: translateY(100%); } }
@keyframes riseDown { from { transform: translateY(-100%); } }
@keyframes slideL  { from { opacity: 0; transform: translateX(24px); } }
@keyframes slideR  { from { opacity: 0; transform: translateX(-24px); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* 矮屏:压掉一些留白,优先保住卡牌尺寸 */
@media (max-height: 760px) {
  #topbar { padding: 5px 14px; }
  .hud { padding: 3px 10px; }
}

/* ═════════════════════════ 备战面板 ═════════════════════════ */

#prep {
  position: fixed; inset: var(--panel-top, 46px) 0 0 0; z-index: 30;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, rgba(11,14,20,.97), rgba(9,12,18,.99));
  /* 备战面板不受棋盘那套垂直预算约束,卡该大一些 —— 这是做决策的地方,
     看不清卡面就没法决策 */
  --ch-lg: clamp(132px, 19vh, 210px);
  --ch: var(--ch-lg);
  --ch-sm: var(--ch-lg);
}

/* 竞猜面板(betting.js 自扫门前雪,z-index:30 那条基线就跟 #prep 打平)打开时
   必须是最上层可交互面 —— 战斗相位从顶栏「角斗场」入口开它时,同屏还挂着
   #yg-decide.bar.equip(56)/#ygx-layer(62)/#flashlayer(65),三个都比 30 高,
   面板自己的确认条(1/3/5 点 · 取消 · 确认下注)整条 elementFromPoint 全部
   命中它们(实测:命中的是 .yg-btn.chip.breaks,不是竞猜面板自己的按钮)。
   66 卡在 #flashlayer(65)之上、#zoneview/.float(70)之下 —— 够盖住那三层
   battle 专属浮层,又不会盖住 #roundend/#finalbanner(75)、裁决幕(76)、
   #endbanner(80)那几层终局/裁决模态(面板万一还开着,也不该抢在它们前面)。
   老板 2026-09-07 补:图鉴(68)与详情浮层(72)是例外 —— 它们由玩家主动
   打开/悬浮唤起,理应压在竞猜面板上。
   关闭时 betting.js 会摘掉 .open,z-index 退回自己那条 30 基线。 */
#betting-pane.open { z-index: 66; }

.prep-top {
  display: flex; align-items: center; gap: 20px;
  padding: 9px 20px; border-bottom: 1px solid var(--line);
  background: rgba(20,26,38,.6);
}
.prep-when b { font-size: var(--t-major); letter-spacing: 2px; color: var(--gold); }
.prep-when span { margin-left: 9px; font-size: 12px; letter-spacing: 2px; }
.prep-purse { display: flex; gap: 18px; align-items: baseline; }
/* 备战这一屏每一个动作(开卡包 / 买卡 / 进化 / 拓席位)都被金币闸住,所以
   金币是主角数字;愿力是整届的分、席位是容量,退一档;牌库/弃牌堆存量是
   边角情报,再退一档。老板 2026-08-26:「你要突出每个阶段的重点」。 */
.prep-purse .stat.gold { gap: 7px; }
.prep-purse .stat.gold b { font-size: var(--t-hero); line-height: 1; font-weight: 700; }
.prep-purse .stat.wish b { font-size: var(--t-major); }
.prep-purse .stat.seatbox { font-size: 10.5px; }
.prep-purse .stat.seatbox > b { font-size: 12.5px; }
.shopbar .libcount { opacity: .78; }
#prep-count b { font-size: 15px; }
.prep-note { margin-left: auto; font-size: 12.5px; min-height: 18px; }
/* 席位是不可退的大额支出,放在顶部信息条里,离「开卡包」隔着半个屏幕 */
.seatbox b { display: inline-flex; align-items: center; gap: 8px; }
.seat-buy { white-space: nowrap; font-weight: 600; }
/* 二次确认的「上膛」态 */
button.op.arm, button.arm {
  border-color: var(--atk); color: #fff;
  background: rgba(255, 61, 90, .22);
  box-shadow: 0 0 14px rgba(255, 61, 90, .45);
  animation: breathe 1s ease-in-out infinite;
}
.prep-note.ok { color: var(--ok); }
.prep-note.bad { color: var(--atk); }

/* 领域防呆(老板 2026-09-07):我的领域进行中,「准备完毕」变身「结束领域」——
   暖金描边提醒"这不是平时那颗按钮";上膛待确认时换成朱红(跟 button.arm 同一套
   语义:金 = 可交易之物/常态,朱红 = 二次确认/危险动作),别新造颜色。 */
#prep-ready.domain {
  border: 2px solid var(--gold);
  box-shadow: 0 0 14px rgba(245, 197, 66, .4);
}
#prep-ready.domain.domain-arm {
  border-color: var(--atk);
  color: #fff;
  background: rgba(255, 61, 90, .22);
  box-shadow: 0 0 14px rgba(255, 61, 90, .45);
  animation: breathe 1s ease-in-out infinite;
}

.prep-body {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--line);
}
.prep-row { background: rgba(12,16,24,.92); padding: 12px 20px 16px; }
.prep-row h3 {
  margin: 0 0 10px; font-size: 12px; letter-spacing: 3px; color: var(--def);
  display: flex; align-items: center; gap: 10px; font-weight: 600;
}
.tiny { font-size: 10px; letter-spacing: 0; }
.dim { color: var(--muted); }

/* ── 卡包:网格铺开,卡放大到"不点详情就能读完卡名+数值+技能前几行" ──
   老板 2026-08-28:「开出的卡包可以再放大一些」。开出的这几张是备战屏此刻的
   **决策对象**(买不买、买哪张),所以它们比牌库/法宝大一整档 —— 层级靠尺寸
   (基调 §2 铁律三);金币仍是全屏主角(--t-hero 在 .prep-purse),卡是对象
   不是读数,不跟它抢。牌库区不缩不藏:prep-body 本来就纵向滚,包再大也只是
   往下推,一屏横滚仍然为零(铁律五)。
   尺寸不走 --ch-lg(那一族被"战场安全网"的回归钉着),卡宽 = 网格轨道宽:
   桌面 auto-fill 一行放下整包 6 张;手机固定两列(见下面同断点的补充块)。 */
.shopbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.pack-row {
  display: grid; gap: 16px 12px;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  align-items: start;
  min-height: calc(var(--ch-lg) * .5);
}
.pack-row .pick { width: 100%; max-width: 264px; justify-self: center; }
.pick {
  position: relative; display: flex; flex-direction: column;
  align-items: center; gap: 6px; width: var(--cw-lg);
}
/* 卡随格子宽走(高度按 5:7 比例自撑)。卡是 .pick 的普通块级子元素,
   不直接当网格项 —— overflow:hidden 的元素直接进网格,Chromium 会把
   aspect-ratio 撑的高度算丢(图鉴叠牌就是这么来的,见 #codex 一节)。 */
#prep .pick .card { width: 100%; height: auto; aspect-ratio: 5 / 7; }
.pick .card { cursor: pointer; }
.pick:not(.poor) .card:hover {
  box-shadow: 0 0 0 2px var(--gold), 0 0 26px rgba(245,197,66,.55);
  transform: translateY(-6px) scale(1.04);
}
.pick.poor .card { filter: grayscale(.7) brightness(.6); cursor: not-allowed; }
.pick .price {
  position: absolute; top: -8px; right: -8px; z-index: 4;
  padding: 4px 10px; border-radius: 12px;
  background: var(--gold); color: #1a1206;
  font-size: 13px; font-weight: 800;
  box-shadow: 0 0 14px rgba(245,197,66,.6);
}
.pick .price.deal { background: var(--ok); box-shadow: 0 0 14px rgba(74,222,128,.6); }
.pick.poor .price { background: var(--muted); box-shadow: none; }
.pick .cap { font-size: var(--t-body); font-weight: 700; text-align: center; line-height: 1.3; }
.pick .cap .dim { font-weight: 400; font-size: var(--t-label); }
/* 技能文字截前三行(全文走「看技能」)。用 --ink 不用 --muted:
   这几行是"买不买"的判据,不是注脚 */
.pick .skill {
  align-self: stretch; padding: 0 2px;
  font-size: 12.5px; line-height: 1.5; color: var(--ink);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.pick .look { font-size: 10px; }
/* 手机:固定两列 —— auto-fill 在 375px 上只放得下一列,单列一张卡一屏,
   翻完一包要滚六屏;两列一张 ~160px 宽,名字/数值/技能行都在可读线上。
   断点跟全站同一档(700px),只是就近写在卡包自己这一段。 */
@media (max-width: 700px) {
  .pack-row { grid-template-columns: 1fr 1fr; gap: 14px 10px; }
}

/* ── 牌库:同名叠成一摞 ── */
.deck-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
.stack {
  position: relative; display: flex; flex-direction: column;
  align-items: center; gap: 4px; width: var(--cw-lg);
}
/* 叠放的视觉:身后垫两张 */
.stack.many .card::before, .stack.many .card::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  border-radius: var(--r); background: #1a2231;
  box-shadow: 0 0 0 1px rgba(160,190,230,.14);
}
.stack.many .card::before { transform: translate(4px, 4px); }
.stack.many .card::after { transform: translate(8px, 8px); }
.stack .count {
  position: absolute; top: -8px; right: -8px; z-index: 4;
  min-width: 24px; padding: 2px 7px; border-radius: 12px;
  background: var(--gold); color: #1a1206; text-align: center;
  font-size: 13px; font-weight: 800;
  box-shadow: 0 0 14px rgba(245,197,66,.55);
}
.stack .eq-tag {
  position: absolute; top: -8px; left: -8px; z-index: 4;
  background: var(--def); color: #04212a; border-radius: 10px;
  font-size: 11px; font-weight: 800; padding: 2px 6px;
}
.stack .ops, .pick .ops { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; }
.stack .ops { opacity: .4; transition: opacity .15s; }
.stack:hover .ops { opacity: 1; }
button.op {
  padding: 2px 7px; font-size: 10.5px; border-radius: 5px;
  background: rgba(255,255,255,.05);
}

/* ── 法宝:横排 ── */
.tres-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-start; }
.treasure {
  border: 1px solid var(--line); border-radius: 10px;
  padding: 9px 12px; min-width: 250px;
  background: rgba(255,255,255,.028);
}
.treasure.form-equipped { border-color: rgba(245,197,66,.5); }
.treasure.form-spirit { border-color: rgba(34,211,238,.5); }
.t-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.t-name { font-weight: 700; font-size: 13.5px; }
.t-num { font-size: 12px; font-variant-numeric: tabular-nums; }
.t-num .a { color: var(--atk); font-style: normal; }
.t-num .d { color: var(--def); font-style: normal; }
.t-grade { margin-left: auto; font-size: 10.5px; color: var(--muted); letter-spacing: 1px; }
.t-modes { display: flex; gap: 7px; }
.t-pick { flex: 1; font-size: 11.5px; min-width: 0; }
.t-spirit { font-size: 11.5px; padding: 5px 10px; white-space: nowrap; }
.t-spirit.on {
  border-color: var(--def); color: var(--def);
  box-shadow: 0 0 14px rgba(34,211,238,.25) inset;
}
.empty { color: var(--muted); font-size: 12px; padding: 18px 0; letter-spacing: 1px; }

/* 法宝合成条 */
.fuse-bar {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; margin-bottom: 10px; border-radius: 10px;
  background: rgba(245, 197, 66, .06); border: 1px dashed rgba(245, 197, 66, .3);
}
.fuse-bar.ready { border-style: solid; border-color: var(--gold); }
.fuse-lab { font-size: 12px; }
.fuse-lab b { color: var(--gold); font-variant-numeric: tabular-nums; }
.fuse-target { font-size: 11.5px; }
.treasure.picked { border-color: var(--gold); box-shadow: 0 0 16px rgba(245,197,66,.25) inset; }
.t-fuse {
  display: flex; align-items: center; gap: 6px;
  margin-top: 7px; font-size: 11px; color: var(--muted); cursor: pointer;
}

.prep-foot {
  display: flex; align-items: center; gap: 18px;
  padding: 11px 20px; border-top: 1px solid var(--line);
  background: rgba(20,26,38,.7);
}
#prep-count { font-size: 13px; }
#prep-count b { font-size: 19px; color: var(--gold); font-variant-numeric: tabular-nums; }
#prep-count.bad b { color: var(--atk); }
#prep-problems { font-size: 12px; }
.bad { color: var(--atk); }
button.big { margin-left: auto; padding: 10px 30px; font-size: 15px; letter-spacing: 3px; }

/* ═════════════════════════ 轮次结算 / 终局 ═════════════════════════ */

#roundend, #finalbanner {
  position: fixed; inset: 0; z-index: 75;
  display: flex; align-items: flex-start; justify-content: center; overflow-y: auto;
  background: rgba(5,7,11,.88); backdrop-filter: blur(6px);
}
.re-inner {
  margin-block: auto;          /* 同 .lobby-card:装得下居中,装不下顶端对齐 */
  min-width: min(680px, 92vw); max-height: 88dvh; overflow-y: auto;
  padding: 26px 34px; border-radius: 16px; text-align: center;
  background: rgba(14,19,29,.97); border: 1px solid rgba(245,197,66,.4);
  box-shadow: 0 0 70px rgba(0,0,0,.75);
  animation: dealIn .3s var(--ease);
}
.re-title { font-size: 22px; letter-spacing: 4px; color: var(--gold); }
/* 结算这一屏的重点是三件事:这一场我赢没赢、名次升还是降、这一轮拿了多少。
   原来只有一行 15px 的「你赢了这一场」,升降和进项要自己拿两轮的表去比。 */
.re-verdict {
  font-size: 15px; margin: 8px 0 18px; letter-spacing: 2px;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.re-rank { display: flex; align-items: baseline; gap: 10px; }
.re-rank b {
  font-size: var(--t-hero); font-weight: 700; line-height: 1; letter-spacing: 2px;
  font-variant-numeric: tabular-nums; color: var(--gold);
}
.re-verdict .verdict-word { font-size: var(--t-major); font-weight: 700; letter-spacing: 4px; }
.re-deltas {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline;
  gap: 6px; letter-spacing: 1px; color: var(--ink);
}
.re-deltas i {
  font-style: normal; font-size: 11.5px; padding: 1px 9px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* 升降 chip 的配色。选择器挂在 `.re-rank` 下**不是笔误**:ladder.js 的
   myLine() 把它拼进 `<span class="re-rank"><b>第 N 名</b>${move}</span>`,
   原来那两条挂在 `.re-deltas` 下的规则一个节点都匹配不到(实测「↑ 升 2 位」
   一直是 --ink 白),是两条死规则。Lead 2026-08-29 裁定:选择器对齐到 .re-rank。

   颜色**故意不用绿/红**:绿是这套基调里根本没有的色;红(朱红)是**裁决色**
   (基调 §1:红是判官笔落下的那一笔),而天梯升降不是裁决 —— 给它朱红会跟
   判负爆破那一幕抢语义。升降说的是"我的位置变了",用**金 / 灰这一对
   "有分量 / 没分量"**表达就够,一种新颜色都不加。
   只改颜色:`.re-rank i` 不加边框胶囊 —— 那会把这一屏改得更花。
   `名次没动` 那个无 class 的 <i> 保持现状。 */
/* 跟全站每一个当"小标签"用的 <i> 一样把斜体关掉:这个仓库里 `<i>` 是语义标签
   不是"斜体",style.css 里另有 11 处都显式 `font-style: normal`(.re-deltas i、
   .card .nums i、.char-chip i …)。中文没有真斜体,浏览器只能拿正体去**斜切**,
   「↑ 升 2 位」那四个字会歪得很脏 —— 唯独 .re-rank 底下漏了这一条。 */
.re-rank i      { font-style: normal; }
.re-rank i.up   { color: var(--gold); }
.re-rank i.down { color: var(--muted); }
/* 愿力从 #c084fc 紫改成暖金:紫是三色之外的**第四种主色**(基调 §2 铁律一
   不许),而按 §1「凡是能换东西的都是金」—— 愿力终局能折算,正是可交易之物。
   改完跟下面 `.coin` 视觉完全相同,这是**故意的**:两者是同一族(可交易),
   靠 chip 里的「愿力」/「金币」两个字区分,不靠再点一盏灯。 */
.re-deltas i.gain { color: var(--gold); border-color: rgba(245, 197, 66, .45); }
.re-deltas i.coin { color: var(--gold); border-color: rgba(245, 197, 66, .45); }
.re-verdict.win .verdict-word { color: var(--ok); }
.re-verdict.lose .verdict-word { color: var(--atk); }
.re-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; text-align: left; }
.re-cols h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: 3px; color: var(--def); font-weight: 600; }
.re-list { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.re-list li {
  display: flex; align-items: baseline; gap: 8px;
  padding: 5px 8px; border-radius: 6px; margin-bottom: 3px;
  background: rgba(255,255,255,.03);
}
.re-list li.mine, .re-list li.me { background: rgba(245,197,66,.13); }
.re-list li.champ { background: rgba(245,197,66,.2); }
.re-list .w { color: var(--ok); font-weight: 600; }
.re-list .vs { color: var(--muted); font-size: 11px; }
.re-list .l { color: var(--muted); }
.re-list .why { margin-left: auto; font-size: 10.5px; color: var(--muted); }
.re-list.ladder .rank {
  width: 20px; height: 20px; line-height: 20px; text-align: center;
  border-radius: 50%; background: rgba(255,255,255,.08);
  font-size: 11px; font-weight: 700; flex: 0 0 auto;
}
.re-list.ladder li.champ .rank { background: var(--gold); color: #1a1206; }
.re-list.ladder .nm { font-weight: 600; }
.re-list.ladder .sc { margin-left: auto; color: var(--gold); font-variant-numeric: tabular-nums; }
.re-list.ladder .gd, .re-list.ladder .wl { font-size: 10.5px; color: var(--muted); }
#re-next, #final-again { margin-top: 22px; }
#final-list { text-align: left; margin: 18px 0 0; min-width: min(400px, 80vw); }

/* ── 结算屏与封神榜:只强调"真的变了"的那两处 ─────────────────────────
   《视觉基调》铁律四:「不做氛围动画,不做待机呼吸灯,不做入场炫技。每一段
   动效都要能回答:哪个东西、变成了什么、因为谁。答不上来就删掉。」

   所以这里**故意没有**逐行 cascade 式入场(`li:nth-child(n){animation-delay}`
   那种瀑布揭示)。战果表和天梯表的每一行都不是"刚刚变的",逐行揭示答不出
   "什么变了",纯装饰,做了就是违背基调。`.re-inner` 现有的 dealIn 也原样不动。

   真正变了、因而配得上一段动效的只有两处:
     ① 「我第几名」+ 它旁边的 ↑升 N 位 / ↓降 N 位。名次是这一屏唯一"看完才
        知道、且决定下一轮打谁、在哪个场地"的读数,升降就是这一轮真发生的
        变化 —— 一次 --beat 档,轻微放大后落定,不做位移。
     ② 封神榜的冠军行。整届的结论 —— 一次 --stage 档。
   缓动只用 --ease,时长只用 --beat / --stage,不写裸数字。 */

/* 静态状态就是"看得见",动画只负责怎么进来。**不许**写成 opacity:0 + forwards
   —— 上面那条 `animation-duration:.01ms !important` 会在「减少动态」下把它拍到
   末帧/首帧,整块消失(showcase.js 里记着这条教训)。这里只动 transform:
   最坏情况也只是尺寸差一点,字一个都不会没。
   原点钉在自己的底边中心:基线不晃,左右各只多探出半格 —— 实测 bottom left
   会让放大的数字整个往右长,把它跟升降 chip 之间那 10px 的 gap 吃掉。 */
.re-rank b { transform-origin: bottom center; animation: reLand var(--beat) var(--ease); }
/* 升降 chip 晚半拍落地:先读到「我第几名」,再读到「升了几位」—— 这是这一屏
   的阅读次序,不是排队入场。 */
/* 只挂 .re-rank 那一族:ladder.js 的 myLine() 把升降 chip 拼在 `.re-rank` 里
   (`<span class="re-rank"><b>第 N 名</b>${move}</span>`),而 `.re-deltas` 里装的
   是 gain/coin 两枚,**永远不会有 up/down** —— 以前那半条选择器一个元素都匹配
   不上,正是它让「↑升 N 位」的配色规则悄悄失效了好几个版本。死选择器留着,
   下一个人还会以为它生效。 */
.re-rank i.up, .re-rank i.down {
  /* chip 往**右**长,躲开左边那个正在放大的数字 —— 两个都从中心长的话,
     实测在头几帧「第 2 名」和「↑ 升 2 位」会挨上(那 10px 的 gap 不够两边分)。 */
  transform-origin: left center;
  animation: reLand var(--beat) var(--ease) calc(var(--beat) / 2) backwards;
}
/* 1.1 不是拍的:名次那个数在 375px 上量得 ~151px 宽,从中心长出去两边各
   7.5px,刚好留得住它跟 chip 之间那 10px 的 gap。再大就撞上了。 */
@keyframes reLand { from { transform: scale(1.1); } }

/* 冠军行:那一笔暖金**落**在这一行上,再收进它本来就穿着的那层金
   (`.re-list li.champ` 的 rgba(245,197,66,.2),U-A 定的)。不是淡入 ——
   行里的名次、名字、愿力自始至终都在,动的只是"金落在谁身上"这件事。
   不点灯(没有 box-shadow 辉光):一屏至多一个光源,这一屏该亮的是结论本身。

   为什么动画挂在 #finalbanner 上、而不是直接挂在 li.champ 上:
   ladder.js 的 renderFinal() **每一帧都重写** #final-list 的 innerHTML
   (它没有 renderRoundEnd 那道 shownRound 闸门),而 done 阶段的长轮询大约
   每 25 秒回一帧 —— 动画写在 li 上就会每 25 秒重放一次,那正是铁律四点名
   不许的"待机呼吸灯"。#finalbanner 只在掀开的那一次从 display:none 变回
   可见,动画因此**只跑一次**;它把一个可继承的数推给冠军行,行本身静态就是
   那层 .2 的金,重画多少次都不会再动。
   (治本的钩子写在交付报告里:renderFinal 该有 renderRoundEnd 那样的闸门。) */
@property --champ-land {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
#finalbanner { animation: champLand var(--stage) var(--ease); }
@keyframes champLand { from { --champ-land: 1; } }
/* 静态(--champ-land 为 0)算出来就是 rgba(245,197,66,.2),与上面那条
   `.re-list li.champ` 逐位相同 —— 动效关掉也好、浏览器不认 @property 也好,
   落地的颜色一个像素都不差。 */
#finalbanner .re-list li.champ {
  background: rgba(245, 197, 66, calc(.2 + .34 * var(--champ-land, 0)));
}

/* 「减少动态」:这两处全关掉,静态样式与开着时**逐字相同** —— 两段动效动的
   只有 transform 和那个数,颜色、字号、位置一处没碰(基调铁律四)。 */
@media (prefers-reduced-motion: reduce) {
  .re-rank b,
  .re-rank i.up, .re-rank i.down,
  #finalbanner { animation: none; }
}

/* 这里原来有一条 `@media (max-width: 1100px) { .prep-body { grid-template-columns: 1fr 1.6fr } }`。
 * 删掉的理由:`.prep-body` 是 `display: flex`,`grid-template-columns` 对它**从来没生效过** ——
 * 是上一版网格式备战面板留下的死规则。
 * 但它不只是"没用",它现在是**活的陷阱**:横屏块把 `.prep-body` 改成了 grid,而 812 < 1100,
 * 这条会当场复活并跟横屏那份两栏比例打架(眼下靠源码顺序压住了,下一个人挪一下位置就炸)。
 * 死规则留着比删掉贵。 */

/* ─────────────────────────── 大厅 ─────────────────────────── */
/* 层级同 splash:盖住棋盘(10)但低于顶栏(20),否则顶栏按钮点不到 */

/* **不许用 `align-items: center` 居中一个可能装不下的盒子。** 那是 flex 居中的
 * 经典死角:子盒子比容器高时,溢出朝上下两头长,而 scrollTop 到不了负数 ——
 * 顶上那一截**永远够不着**。实测(改前):375×812 竖屏 `.lobby-card` 高 1009px、
 * 顶在 y=-98、名字输入框在 y=-22,`#lobby.scrollTop` 已经是 0 还是够不着;
 * 812×375 横屏更狠(卡片顶在 -108)。老板一直没撞上,是因为他走「加入」那半边
 * (在下半截,滚得到)或者直接点 /r/<房间码> 链接。
 * 改由子盒子自己 `margin: auto`:**富余为正时照样居中,富余为负时 auto 归零**,
 * 内容从顶端开始、一路滚得到底 —— 这是 CSS 规范写死的行为。
 * 不用 `align-items: safe center`:Safari 17 之前不认,老板的机器版本未知,不赌。 */
#lobby {
  position: fixed; inset: 0; z-index: 16;
  display: flex; align-items: flex-start; justify-content: center;
  background: rgba(5, 7, 11, .9); backdrop-filter: blur(6px);
  /* 铺满整屏(inset:0)是**故意的** —— 它得压住棋盘,又得低于顶栏,不然顶栏
     按钮点不到。但"铺到顶栏底下"不等于"内容画到顶栏底下":实测标题
     「跟朋友一起玩」落在 y=31,而顶栏下沿在 57(竖屏 84),整句被压得看不见。
     所以内边距顶上留出 --panel-top —— 跟 #prep / #waiting / #build 一个口径,
     只不过它们是靠 inset 让位,这一块是靠 padding 让位。 */
  padding: calc(var(--panel-top, 46px) + 14px) 20px 20px;
  overflow-y: auto;
}
.lobby-card {
  position: relative;
  margin-block: auto;          /* 见上面 #lobby 那段:装得下就居中,装不下就顶端对齐 */
  width: min(880px, 100%);
  padding: clamp(20px, 3.4vh, 34px) clamp(20px, 3vw, 40px);
  border-radius: 16px;
  background: rgba(14, 19, 29, .97);
  border: 1px solid rgba(245, 197, 66, .32);
  box-shadow: 0 0 70px rgba(0, 0, 0, .7), 0 0 40px rgba(245, 197, 66, .09);
}
.lobby-card h2 {
  margin: 0 0 4px; color: var(--gold); letter-spacing: 3px;
  text-shadow: 0 0 22px rgba(245, 197, 66, .4);
}
.lobby-card h3 { margin: 0 0 6px; color: var(--ink); font-size: 15px; letter-spacing: 1px; }
.lobby-card p { margin: 0 0 12px; line-height: 1.8; font-size: 13px; }

.lobby-row { display: flex; gap: 10px; align-items: center; margin: 10px 0; flex-wrap: wrap; }
.lobby-row .fld.wide { flex: 1; }
/* 输入框是 width:100% 的 flex 项,会把同一行那个标签文字挤成 min-content ——
   「你的名字」于是断成「你的名 / 字」两行(实测标签高 36px = 两行)。
   标签不许断行:它是这一栏在说什么,断了就是一塌糊涂;要缩就缩输入框。 */
.lobby-row .fld.wide { white-space: nowrap; }
.lobby-row .fld.wide input { width: 100%; min-width: 0; }

.lobby-two {
  display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 16px;
}
.lobby-opt {
  padding: 16px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line);
}
.lobby-opt#join-panel.focus { border-color: rgba(34, 211, 238, .5); }

.code-input, .link-input {
  flex: 1; min-width: 0;
  padding: 9px 12px; border-radius: 8px;
  background: rgba(8, 11, 17, .8); border: 1px solid var(--line);
  color: var(--ink); font-size: 15px; font-family: inherit;
}
.code-input {
  letter-spacing: 5px; text-transform: uppercase; text-align: center;
  font-size: 20px; font-weight: 700; color: var(--gold);
}
.link-input { font-size: 12px; color: var(--def); }
.code-big {
  color: var(--gold); letter-spacing: 8px; font-size: 30px;
  text-shadow: 0 0 26px rgba(245, 197, 66, .5);
}

/* 必须限定在 #lobby 里:HUD 的席位量表也叫 .seats(.gauge.seats),不限定的话
   这条的 margin/gap 会串过去,把两个 HUD 各撑高 28px —— 那是战场的预算。 */
#lobby .seats { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.seat {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 14px; border-radius: 9px; font-size: 13px;
  background: var(--panel); border: 1px solid var(--line); min-width: 148px;
}
.seat b {
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center; font-size: 11px;
  background: rgba(125, 155, 195, .18); color: var(--muted);
}
.seat.on    { border-color: rgba(74, 222, 128, .45); }
.seat.on b  { background: rgba(74, 222, 128, .22); color: var(--ok); }
.seat.off   { border-color: rgba(255, 61, 90, .4); }
.seat.off i { color: var(--atk); font-style: normal; font-size: 11px; }
.seat.ai    { opacity: .66; }
.seat.open  { border-style: dashed; opacity: .55; }

button.big { padding: 11px 30px; font-size: 15px; }

.toast {
  position: absolute; left: 50%; bottom: -14px; transform: translate(-50%, 100%);
  padding: 9px 18px; border-radius: 8px; font-size: 13px; white-space: nowrap;
  background: rgba(28, 36, 52, .97); border: 1px solid rgba(245, 197, 66, .4);
  color: var(--gold); box-shadow: 0 6px 24px rgba(0, 0, 0, .5);
}

/* 对手的隐藏区 —— 只知道有这么一张 */
.card.cardback {
  background:
    repeating-linear-gradient(45deg,
      rgba(125, 155, 195, .07) 0 6px, transparent 6px 12px),
    linear-gradient(160deg, #1b2231, #10141d);
  border-color: rgba(125, 155, 195, .28);
  box-shadow: inset 0 0 22px rgba(0, 0, 0, .5);
}
.card.cardback::after {
  content: "妖"; position: absolute; inset: 0;
  display: grid; place-items: center;
  color: rgba(125, 155, 195, .3); font-size: calc(var(--ch-sm) * .34);
}

@media (max-width: 720px) {
  .lobby-two { grid-template-columns: 1fr; }
}

/* ─────────────────────────── 等别人 ─────────────────────────── */

#waiting {
  position: fixed; inset: var(--panel-top, 46px) 0 0 0; z-index: 14;
  display: flex; align-items: flex-start; justify-content: center; overflow-y: auto;
  background: rgba(5, 7, 11, .88); backdrop-filter: blur(5px);
}
/* 见 #lobby 那段:居中浮层不许用 align-items 居中一个可能装不下的盒子。
 * 这四处(#waiting / #zoneview / #finalbanner / #endbanner)跟大厅是同一个病:
 * 6 人局的等人名单、封神榜、退场区堆满的展开面板都可能比视口高,
 * 而一旦比视口高,顶上那一截就永远够不着(scrollTop 到不了负数)。
 * 同一条修法:容器 flex-start + 自己 margin:auto,富余为正照样居中。 */
.waiting-inner {
  margin-block: auto;
  width: min(460px, 90vw);
  padding: 26px 30px; border-radius: 14px; text-align: center;
  background: rgba(14, 19, 29, .96); border: 1px solid var(--line);
}
.waiting-inner h2 {
  margin: 0 0 16px; color: var(--gold); font-size: 17px; letter-spacing: 2px;
}
.waiting-inner p { margin: 14px 0 0; font-size: 12px; }
.waiting-list { display: flex; flex-direction: column; gap: 7px; }
.wait-row {
  display: flex; justify-content: space-between; align-items: center;
  padding: 9px 14px; border-radius: 8px; font-size: 13px;
  background: var(--panel); border: 1px solid var(--line);
}
.wait-row .who { color: var(--ink); }
.wait-row .who i { color: var(--muted); font-style: normal; margin: 0 5px; }
.wait-row .tag { font-size: 11px; color: var(--muted); }
.wait-row.done { border-color: rgba(74, 222, 128, .38); }
.wait-row.done .tag { color: var(--ok); }
.wait-row.busy .tag { animation: waitPulse 1.5s ease-in-out infinite; }

@keyframes waitPulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }

.warn {
  margin: 10px 0; padding: 11px 14px; border-radius: 8px;
  font-size: 12px; line-height: 1.8;
  background: rgba(255, 61, 90, .09); border: 1px solid rgba(255, 61, 90, .34);
  color: #ffb3c0;
}
.warn b { color: var(--atk); }
.warn code {
  padding: 1px 6px; border-radius: 4px; font-size: 11px;
  background: rgba(8, 11, 17, .7); color: var(--gold);
}

.lobby-sep {
  height: 1px; margin: 16px 0 12px;
  background: linear-gradient(90deg, transparent, var(--line), transparent);
}
.lobby-opt .tiny { font-size: 11px; margin: 4px 0 0; }

/* ─────────────────────────── 房间规则设置(econ.js) ─────────────────────────── */
/*
 * 默认收起的一条 "⚙ 赛制规则 · 摘要",点开才是完整旋钮列表 —— 大部分人
 * 不会改,不该被一堆选项劝退。手机优先:可点区域 ≥40px,<select> 字号
 * ≥16px(否则 iOS 会因为"输入框小于16px"自动放大整页)。
 */
.econ-mount { margin: 4px 0 14px; }

/* ───────── 大厅 · 选角色 ─────────
   跟隔壁赛制规则面板同一套折叠壳(.econ-panel 的形),只是里面换成 6 张
   角色牌。角色是开本时分配的(手册 p7/p77),线上做成入座后自选。 */
.chars-mount { margin: 4px 0 10px; }

.char-panel {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel);
}
.char-panel > summary {
  display: flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 8px 14px;
  cursor: pointer; list-style: none;
  color: var(--ink); font-size: 13px;
}
.char-panel > summary::-webkit-details-marker { display: none; }
.char-panel > summary::marker { content: ""; }
.char-panel > summary::after {
  content: "▸"; margin-left: auto; flex: 0 0 auto;
  color: var(--muted); font-size: 11px;
  transition: transform .16s var(--ease);
}
.char-panel[open] > summary::after { transform: rotate(90deg); }
.char-panel > summary .char-title { flex: 0 0 auto; white-space: nowrap; color: var(--gold); }
.char-panel > summary .char-sum {
  flex: 1 1 auto; color: var(--muted); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.char-body {
  display: grid; gap: 8px; padding: 10px 14px 4px;
  border-top: 1px solid var(--line);
  /* 桌面两列、手机一列 —— 每张牌要放得下技能名和一行话 */
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}

.char-chip {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 9px 12px; min-height: 40px; width: 100%;
  text-align: left; white-space: normal; line-height: 1.45;
  border-radius: 9px; border: 1px solid var(--line);
  background: rgba(12, 16, 24, .55); color: var(--ink);
  cursor: pointer; transition: border-color .14s var(--ease), background .14s var(--ease);
}
.char-chip b { font-size: 13.5px; letter-spacing: 1px; }
.char-chip i { font-style: normal; font-size: 11px; letter-spacing: 1px; color: var(--gold); }
.char-chip span { font-size: 11.5px; color: var(--muted); }
.char-chip em { font-style: normal; font-size: 11px; color: var(--atk); }
.char-chip:hover:not(:disabled) { border-color: rgba(245, 197, 66, .5); }

.char-chip.picked {
  border-color: var(--gold); background: rgba(245, 197, 66, .14);
}
.char-chip.picked b::after { content: " ✓"; color: var(--gold); }
/* 被别人选走的:一眼看出点不动,但技能文字照样读得到 —— 得知道自己错过了什么 */
.char-chip.taken, .char-chip:disabled { opacity: .42; cursor: not-allowed; }

.char-foot { margin: 6px 14px 12px; }

/* 座位表上那一枚角色徽章 */
.seat-char {
  margin-left: auto; flex: 0 0 auto; font-style: normal;
  font-size: 10.5px; letter-spacing: 1px; padding: 1px 7px; border-radius: 20px;
  color: var(--gold); border: 1px solid rgba(245, 197, 66, .38);
  background: rgba(245, 197, 66, .1);
}

.econ-panel {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel);
}
.econ-panel > summary {
  display: flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 8px 14px;
  cursor: pointer; list-style: none;
  color: var(--ink); font-size: 13px;
}
.econ-panel > summary::-webkit-details-marker { display: none; }
.econ-panel > summary::marker { content: ""; }
.econ-panel > summary::after {
  content: "▸"; margin-left: auto; flex: 0 0 auto;
  color: var(--muted); font-size: 11px;
  transition: transform .16s var(--ease);
}
.econ-panel[open] > summary::after { transform: rotate(90deg); }
.econ-panel > summary .econ-title {
  flex: 0 0 auto; white-space: nowrap;
}
.econ-panel > summary .econ-gear { color: var(--gold); }
.econ-panel > summary .econ-sum {
  flex: 1 1 auto; color: var(--muted); font-weight: 400; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.econ-panel > summary .econ-ro {
  flex: 0 0 auto; font-size: 10px; letter-spacing: 1px; white-space: nowrap;
  color: var(--atk); border: 1px solid rgba(255, 61, 90, .4);
  border-radius: 20px; padding: 2px 8px; margin-left: 4px;
}

.econ-body {
  display: flex; flex-direction: column; gap: 2px;
  padding: 2px 14px 12px; border-top: 1px solid var(--line);
}
.econ-row { padding: 8px 0; border-bottom: 1px dashed var(--line); }
.econ-row:last-child { border-bottom: none; }
.econ-row-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.econ-field {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex: 1 1 180px; min-width: 0; min-height: 40px;
  color: var(--ink); font-size: 13px; cursor: pointer;
}
.econ-panel.readonly .econ-field { cursor: not-allowed; color: var(--muted); }
.econ-label { min-width: 0; }

.econ-select {
  flex: 0 0 auto; max-width: 100%;
  min-height: 40px; padding: 8px 10px;
  font-size: 16px; /* ≥16px:防止手机上聚焦时被 iOS 强制缩放页面 */
}
.econ-select:disabled { opacity: .5; cursor: not-allowed; }

.econ-switch.switch {
  flex: 1 1 180px; min-width: 0; min-height: 40px;
  font-size: 13px; color: var(--ink);
}
.econ-panel.readonly .econ-switch.switch { color: var(--muted); cursor: not-allowed; }
.econ-switch.switch input:disabled + .track { opacity: .5; }

.econ-hint {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  padding: 0; font-size: 13px; color: var(--muted);
  background: var(--panel-2); border: 1px solid var(--line);
}
.econ-hint[aria-expanded="true"] { color: var(--def); border-color: rgba(34, 211, 238, .5); }

.econ-hint-text {
  margin: 6px 0 0; padding: 8px 10px;
  background: rgba(8, 11, 17, .5); border: 1px solid var(--line); border-radius: 7px;
  color: var(--muted); font-size: 12px; line-height: 1.7;
}

@media (max-width: 480px) {
  .econ-panel > summary { padding: 8px 10px; }
  .econ-body { padding: 2px 10px 10px; }
  .econ-field, .econ-switch.switch { flex-basis: 100%; }
  .econ-select { flex: 1 1 auto; width: 100%; }
}

/* ─────────────────────────── 自由构筑 ─────────────────────────── */
/*
 * 构筑界面的卡要**比棋盘上的大** —— 棋盘靠位置和动画传达信息,这里全靠
 * 看清卡面挑卡。所以不复用 --cw-sm(那是垫牌尺寸),自己定一套。
 */

#build {
  position: fixed; inset: var(--panel-top, 46px) 0 0 0; z-index: 18;
  display: flex; flex-direction: column;
  background: var(--bg);
  --build-cw: clamp(92px, 7.6vw, 132px);
  --build-ch: calc(var(--build-cw) * 1.4);
}
.build-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 16px; border-bottom: 1px solid var(--line);
  background: var(--panel-2);
}
.build-head h2 {
  margin: 0; font-size: 15px; color: var(--gold); letter-spacing: 2px;
}
.build-head .spacer { flex: 1; }
.build-count { font-size: 14px; }
.build-count b { font-size: 20px; }
.build-count.ok b { color: var(--ok); }
.build-count.bad b { color: var(--atk); }
.build-cost { font-size: 12px; color: var(--gold); }
.build-cost b { font-size: 16px; }
.build-cost .dim { color: var(--muted); font-size: 11px; }
.build-note { font-size: 12px; }
.build-note.ok { color: var(--ok); }
.build-note.bad { color: var(--atk); }
.build-others { display: flex; gap: 8px; font-size: 12px; }
.build-others .who { color: var(--muted); }
.build-others .who.done { color: var(--ok); }

/* 卡组区不能被挤成一条缝 —— 它和卡池一样重要 */
.build-body {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: 1fr minmax(340px, 0.72fr);
}
.build-col { min-height: 0; display: flex; flex-direction: column; padding: 10px 14px; }
/* 卡组和法宝各占一块确定的高度。用 flex:1 的话卡组会把法宝挤成一条缝 */
.build-col.mine {
  display: grid;
  /* 标题 / 卡组 / 法宝块 —— 后两块各拿一份确定的高度 */
  grid-template-rows: auto minmax(0, 1fr) minmax(0, 0.85fr);
  border-left: 1px solid var(--line);
  background: rgba(19, 25, 37, .45);
}
#build-tre-wrap { min-height: 0; display: flex; flex-direction: column; }
#build-tre-wrap .tre-list { flex: 1; }
.build-col h3 {
  margin: 4px 0 8px; font-size: 13px; color: var(--ink); letter-spacing: 1px;
  flex: 0 0 auto;
}
.build-bar { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
.build-bar input, .build-bar select {
  padding: 6px 10px; border-radius: 7px; font-size: 12px; font-family: inherit;
  background: rgba(8, 11, 17, .8); border: 1px solid var(--line); color: var(--ink);
}
.build-bar input { flex: 1; min-width: 110px; }

.cardgrid {
  min-height: 0; overflow-y: auto; align-content: start;
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(var(--build-cw), 1fr));
  padding: 2px 2px 10px;
}
#build-pool { flex: 1; }
/* 构筑界面里的卡统一放大,盖掉 .card.sm 的棋盘尺寸 */
#build .card.sm {
  width: 100%; height: auto; aspect-ratio: 1 / 1.4;
  cursor: pointer;
}
#build .card.sm .nm { font-size: clamp(9px, .82vw, 12px); }
#build .card.sm .st { font-size: clamp(9px, .8vw, 12px); }
.cardgrid .card.owned { outline: 2px solid var(--ok); outline-offset: -2px; }
.pick-n {
  position: absolute; right: -5px; bottom: -5px; z-index: 3;
  min-width: 21px; padding: 1px 5px; border-radius: 10px;
  font-size: 12px; font-weight: 700; text-align: center;
  background: var(--gold); color: #1a1204;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
}

/* 法宝只有 10 件,缩一点让它一屏看完,不用滚 */
.tre-list {
  min-height: 0; overflow-y: auto; padding: 2px;
  display: grid; gap: 7px; align-content: start;
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--build-cw) * .72), 1fr));
}
.tre-list .card { opacity: .55; transition: opacity .15s, transform .15s; }
.tre-list .card:hover { opacity: .9; }
.tre-list .card.on {
  opacity: 1; outline: 2px solid var(--gold); outline-offset: -2px;
  box-shadow: 0 0 18px rgba(245, 197, 66, .35);
}

@media (max-width: 980px) {
  #build { --build-cw: clamp(84px, 13vw, 112px); }
  .build-body { grid-template-columns: 1fr; grid-template-rows: 1.15fr 1fr; }
  .build-col.mine { border-left: none; border-top: 1px solid var(--line); }
  .tre-list { max-height: 22vh; }
}

/* ═════════════════════════════════════════════════════════════════════
 * 手机适配 —— 单一断点收拢所有"屏幕小"的改动。
 * 原则:宽屏的规则一个字都不动;这里只加规则、只在这个断点里生效。
 *
 * **2026-08-31 加了第二个条件(横屏)。** 逗号 = 或,前半截逐字未动,所以
 * 竖屏行为逐字节不变;横屏(812×375 这类)白拿这一整块 —— HUD 压成一行省
 * 128px、结界区自己横滚、44px 命中区、iOS 16px 输入框、安全区,**每一条横屏
 * 都需要**。复制一份出去就是两份要同时维护,迟早对不上。
 * 那些"因为窄才那么做、横屏该反过来"的规则(结算双栏 / 图鉴右停靠 / 翻牌
 * 按钮的位置 / 卡位行排法),在下面**横屏专属块**里逐条改回来。
 * ═════════════════════════════════════════════════════════════════════ */
@media (max-width: 700px), (orientation: landscape) and (max-height: 500px) {

  /* ── 1. 顶栏:单行紧凑。标题横排缩小(不许竖排单字,所以 nowrap)、
     「开房/加入」常驻;次要工具收进 details.more-tools 的 ⚙ 折叠里。 ── */
  #topbar {
    min-height: 56px; padding: 6px 10px; gap: 8px;
    flex-wrap: wrap; row-gap: 6px;   /* 极窄机型(<~340px)兜底换行,绝不允许横向溢出 */
  }
  #topbar h1 { font-size: 14px; letter-spacing: 1px; flex-shrink: 0; white-space: nowrap; }
  #topbar h1 em { font-size: 8px; letter-spacing: 2px; margin-left: 5px; }
  .tools { gap: 6px; }
  .tools .sep { display: none; }               /* 分隔线是纯装饰,窄屏先省 */
  #new-game { padding: 8px 12px; font-size: 12.5px; }

  .more-tools { display: block; position: relative; }  /* 手机上要有盒子才能挂浮层 */
  .more-tools > summary {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; border-radius: 7px; padding: 0;
    background: var(--panel-2); border: 1px solid var(--line);
    color: var(--ink); font-size: 17px;
  }
  /* 默认折叠;[open] 时才展开成一张浮在顶栏下面的设置卡片 */
  .more-tools > .more-tools-body {
    display: none !important;
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 25;
    flex-direction: column; align-items: stretch; gap: 10px;
    width: max(220px, 62vw);
    padding: 12px 14px; border-radius: 12px;
    background: rgba(14, 19, 29, .98); border: 1px solid var(--line);
    box-shadow: 0 14px 36px rgba(0, 0, 0, .6);
  }
  .more-tools[open] > .more-tools-body { display: flex !important; }
  .more-tools-body .sep { display: none; }
  .more-tools-body .fld { justify-content: space-between; font-size: 12.5px; }
  .more-tools-body .switch { justify-content: space-between; }

  /* 这几个面板用 inset/top 硬编码顶栏高度来定位,顶栏在窄屏变矮/变高了,
     这里必须跟着改,不然要么露一条缝要么盖住内容第一行。 */
  #prep, #waiting, #build { inset: var(--panel-top, 56px) 0 0 0; }
  #logpane { top: 56px; }

  /* ── 2. 消灭横向溢出 ── */

  /* HUD 原来在 375×812 上会折成三行、**单侧就吃掉 94px**(两侧 188px),
     而中间的 #arena 只分到 156px —— 内容需要 263px,于是 overflow:hidden 把
     守擂读数和累计攻击齐齐切掉:真机上战况一个数都看不到(改前截图见
     交付报告)。HUD 是配角条,不该比主战场还高。这里把它压成**一行**:
     量表改成"标签 + 数值一行、进度条当下划线",退场那一项直接省掉(退场堆
     角标 .pile-tag 上已经写着同一个数)。省下的 ~128px 全部还给战场。 */
  .hud {
    flex-wrap: nowrap; padding: 3px 8px; gap: 8px; min-height: 0; overflow: hidden;
  }
  .hud .who { font-size: 12px; gap: 5px; min-width: 0; flex: 0 1 auto; }
  .hud .who .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .hud .role { font-size: 8.5px; padding: 1px 5px; letter-spacing: 1px; flex: 0 0 auto; }
  .hud .stats { flex-wrap: nowrap; gap: 7px; min-width: 0; }
  .hud .deckpile { width: 22px; height: 28px; font-size: 11px; }
  .hud .deckpile::after { display: none; }   /* 三层牌背在这个尺寸只剩噪点 */
  .hud .gauge {
    position: relative; min-width: 0; flex: 0 0 auto; padding-bottom: 3px;
    flex-direction: row; align-items: baseline; gap: 4px;
  }
  .hud .gauge .g-lab { display: contents; }
  .hud .gauge .g-lab span { font-size: 9px; }
  .hud .gauge .g-lab b { font-size: 11px; }
  .hud .gauge .g-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; }
  .hud .stat { font-size: 9.5px; gap: 3px; }
  .hud .stat b { font-size: 11.5px; }
  .hud .stat.minor { font-size: 9px; }
  .hud .stat.minor b { font-size: 11px; }
  .hud .stat.gone { display: none; }
  .stat { font-size: 10px; gap: 4px; }
  .stat b { font-size: 12.5px; }

  /* 结界区(最多 6 席)是主要撑宽元凶:自己横向滚动,不让页面滚。
     两侧的退场堆 / 手牌堆挪到结界行下方,并排一行。 */
  .barrier-wrap {
    display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
    grid-template-areas: "bar bar" "dis hand";
  }
  .barrier { grid-area: bar; max-width: 100%; overflow-x: auto; justify-content: flex-start; padding: 4px 6px; }
  .side-pile.discard { grid-area: dis; }
  .side-pile.hand { grid-area: hand; }
  /* 退场区/手牌区是配角小卡堆,不用像桌面那样留那么大的空盒子外框 */
  .side-pile { padding: 3px 6px; min-height: calc(var(--ch-sm) + 4px); }
  /* 对手那两堆在手机上收成两枚**计数芯片**:他的手牌本来就只画牌背(只有
     张数是信息),退场堆点一下照样能开全部。这一行原来占 70px,现在 22px,
     省下的近 50px 全部还给战场 —— 手机上那才是主体。
     自己那两堆保持实卡:法宝要看得见才拿得起来。 */
  .side.enemy .side-pile {
    min-height: 22px; min-width: 0; padding: 3px 9px; align-items: baseline;
  }
  .side.enemy .side-pile .card { display: none; }
  .side.enemy .side-pile .pile-tag { position: static; font-size: 10px; }

  /* 攻防卡位行:info 列(累计攻击力 / 守擂条)原来有 min-width:150~250px 的
     硬下限,窄屏会把它挤出 #arena 视口(overflow:hidden 只是把溢出藏起来,
     不是修好);改成跟 slot 一起排、宽度交给可用空间,退场堆自己单独一行。 */
  .slot-row {
    grid-template-columns: auto 1fr;
    grid-template-areas: "spent spent" "slot info";
    width: 100%; gap: 4px 10px;   /* 行/列间距都收紧,把省下的高度还给卡位 */
  }
  /* 已翻/垫牌那一摞在手机上**只留计数**:它自己占一整行(卡位行是
     "spent spent" / "slot info" 两行),一摞 3 张就吃掉 68px,而 #arena 一共
     才两百多 —— 主角牌就是被它挤出视口的。而"已翻 N 张"本来就写在累计攻击
     底下、"垫牌 N"也一直在标签上,牌面在 46px 见方时根本认不出是谁。 */
  .slot-row .spent { grid-area: spent; justify-self: start; flex-wrap: wrap; }
  .slot-row .spent .card { display: none; }
  .slot-row .spent-tag { margin-left: 0; }
  .slot-row .slot { grid-area: slot; }
  .slot-row .side-info { grid-area: info; min-width: 0; justify-self: stretch; }

  /* 备战面板顶部信息条(轮次/金币/愿力/结界/提示)允许换行 */
  .prep-top { flex-wrap: wrap; row-gap: 8px; }
  .prep-purse { flex-wrap: wrap; row-gap: 6px; }
  .prep-note { margin-left: 0; flex: 1 0 100%; }

  /* 结算页:本轮战果 / 天梯两栏在窄屏挤不下,改单栏 */
  .re-cols { grid-template-columns: 1fr; gap: 14px; }
  .re-inner { width: min(420px, 92vw); min-width: 0; padding: 22px 18px; }

  /* ── 3. 棋盘卡在窄屏更大:--ch 系列加上 vw 维度(min(vh, vw)),
     守擂卡(--ch-lg)的 vw 系数最大,全场依旧它最大。
     老板反馈"对战区太小卡都卡不清"——主战场是游戏主体,vw 系数再加大一档
     (36vw→46vw),让 --ch-lg 在 375px 宽的手机上从原来卡在 vw 地板值的
     148px 顶到 ~172px(实测数字见任务卡);结界区(--ch)是配角,系数
     维持不动,把窄屏多出来的宽度优先分给主战区。 */
  :root {
    --ch: clamp(88px, min(12.8vh, 21vw), 166px);
    --ch-lg: clamp(96px, min(22.5vh, 46vw,
                   (var(--stage-h) - 2 * var(--ch) - var(--ch-sm) - 212px) / 2), 230px);
    --ch-sm: clamp(64px, min(7.4vh, 13vw), 94px);
    /* 主角数字也跟 vw 联动:纯 vh 会在窄屏把 side-info 那一列撑出卡位行 */
    --t-hero:  clamp(28px, min(5.4vh, 11.5vw), 44px);
    --t-major: clamp(16px, min(2.8vh, 6vw), 24px);
  }
  body.drawer-open {
    --ch: clamp(66px, min(9.2vh, 16vw), 120px);
    --ch-lg: clamp(84px, min(15.5vh, 35vw,
                   (var(--stage-h) - 2 * var(--ch) - var(--ch-sm) - 170px) / 2), 196px);
    --ch-sm: clamp(50px, min(5.6vh, 10vw), 72px);
    --t-hero:  clamp(22px, min(4vh, 9vw), 34px);
    --t-major: clamp(14px, min(2.2vh, 4.8vw), 19px);
  }

  /* 决策抽屉一展开就占掉 27vh。那一刻玩家在读抽屉,结界区退到最小 ——
     否则两张主角牌会被 #arena 切掉,连"还差几点打穿"都读不到。 */
  body.drawer-open .barrier-wrap { --ch: clamp(42px, 5.6vh, 58px); }
  body.drawer-open .side.enemy .barrier-wrap { display: none; }
  /* 抽屉开着时 #arena 一定装不下全部内容(它 overflow:hidden、居中摆放,
     于是上下同时裁 —— 被裁掉的正好是对手那张卡和「还差几点打穿」)。
     改成顶对齐:要裁就裁**我方那一行**,因为我方那张卡这一刻本来就画在
     抽屉里(allocate 有 p.card,cards/order 的选项卡就是它),而对手那张
     哪儿都没有第二份。
     body.yg-cta(登场底部条)**故意不加**这一条:那一刻要把法宝拖到我方
     卡上,落点被裁掉就等于没法拖。 */
  body.drawer-open #arena { justify-content: flex-start; }

  /* ── 4. 点击目标:窄屏常规按钮至少 44px 高(图标按钮 / 卡组里内联的
     小操作按钮 .op 保持原样,否则会撑坏那些本来就设计得很密的区域)。 ── */
  button:not(.icon):not(.op) { min-height: 44px; }

  /* 翻牌按钮钉在拇指区,叠上手机安全区,免得被 Home 指示条挡住。
     抬到自己 HUD 那一条(约 36px + 棋盘 10px 内边距)上面 —— 压住幽冥/席位/
     金币这几个判负读数比压住结界区糟得多。 */
  #flip-cta {
    position: fixed; left: 50%; z-index: 40;
    bottom: calc(54px + env(safe-area-inset-bottom));
    transform: translateX(-50%);
  }

  /* ── 5. 输入 / 下拉字号统一 ≥16px —— iOS 上 <16px 的输入框一聚焦就会把
     整页强制放大,交互全乱。 ── */
  select, input[type=number], input, .link-input,
  .build-bar input, .build-bar select {
    font-size: 16px;
  }

  /* ── 6. 卡牌详情浮层:窄屏没有鼠标 hover,原来贴右上角的悬浮卡片改成
     底部抽屉 —— 固定在底部、内部自己滚,不盖满全屏。
     #detail.left 是桌面端"卡在右半屏就翻到左边"的变体,窄屏统一成同一个
     底部抽屉,不再分左右。
     老板反馈"点开卡牌显示太大了会挡住其他的区域"——实测 60vh 在 375×812
     上是 487px,盖住六成屏幕看不到棋盘。压到 42vh,同时把卡图从整版大图
     改成左浮的小缩略图、文字绕图走(省下来的高度让给下面数值来源/装备
     明细区,继续靠 #detail 自带的 overflow-y 滚动兜底),核心诉求是弹出时
     战场主体仍然露在外面。 ── */
  #detail, #detail.left {
    position: fixed; inset: auto 0 0 0; top: auto;
    width: 100%; max-width: 100%; max-height: 42vh;
    border-radius: 16px 16px 0 0;
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
    pointer-events: auto;          /* 抽屉要能被手指滚动 */
    animation: riseUp .2s var(--ease);
  }
  /* 卡图改左浮小缩略图,名字/标签绕图排;下面这些内容区一律 clear 掉
     浮动、独占一行 —— 不管某张卡是否同时带"数值来源"和"装备"两段附加
     信息(可能出现 0~2 组 .d-sec/.d-bonus),都会跟着从浮动下方另起一行,
     不用按具体张数特殊处理。 */
  #detail img {
    float: left; width: 62px; height: 62px; object-fit: cover;
    margin: 0 10px 6px 0; border-radius: 8px;
  }
  #detail .d-name { margin: 0; font-size: 14px; }
  #detail .d-tags { margin: 5px 0 0; }
  #detail .d-stat, #detail .d-faces, #detail .d-text,
  #detail .d-sec, #detail .d-bonus { clear: both; }

  #detail .d-stat { padding: 6px 8px; margin-bottom: 6px; gap: 8px; }
  #detail .d-stat b { font-size: 16px; }
  #detail .d-faces { margin-bottom: 6px; }
  #detail .d-faces .face { padding: 4px 3px; }
  #detail .d-text { font-size: 12px; line-height: 1.48; padding: 7px 8px; }
  #detail .d-sec { margin: 7px 0 4px; }
  #detail .d-bonus { gap: 2px; }

  /* ── 7. 图鉴:窄屏改成顶部抽屉,不是右侧停靠。翻牌按钮钉在屏幕底部
     (见上面 #flip-cta 的 fixed bottom 规则),右侧停靠在 375px 宽的屏幕
     上宽度接近全屏,会正好盖住居中的翻牌按钮;改成顶部下拉、限高度,
     战场下半部分(含翻牌按钮)始终露在外面,跟 #detail 的底部抽屉是同一个
     "开着也不挡战场主操作"的思路,只是方向反过来。 ── */
  #codex {
    top: 56px; right: 0; left: 0; bottom: auto;
    width: auto; max-height: 58vh;
    border-left: none; border-bottom: 1px solid var(--line);
    border-radius: 0 0 16px 16px;
    animation: riseDown .2s var(--ease);
  }
  .codex-bar input, .codex-bar select { font-size: 16px; }
  #btn-codex, #codex-close { min-width: 40px; min-height: 40px; }
}

/* ═════════════════════════════════════════════════════════════════════
 * 横屏专属(2026-08-31)—— 老板:「手机版应该横屏玩,视角应该是横屏才更舒服。」
 *
 * **判据为什么是这两条:**
 * · `max-height: 500px` 才是"矮"的判据。横屏的痛点是 375px 高,不是 812px 宽
 *   —— 全站原有断点清一色 `max-width`(1100/980/720/700/640/480),812 宽一条
 *   都命中不了,于是横屏被当成"窄桌面"渲染却只有 375px 高。实测后果:#arena
 *   被压成 **2px**、「还差 N 点打穿」被 .deckpile 盖住、大厅名字输入框顶到
 *   y=-108 滚不到(横屏根本进不去游戏)。**所以不许再往 max-width 上堆。**
 *   500 这个数:覆盖 iPhone 全系横屏(375/390/428/430/440),离竖屏 812 和
 *   桌面(≥700)都远,不会误伤。
 * · `orientation: landscape` 挡住"又窄又矮"的窗口(360×400 那种)——
 *   那种尺寸下横屏这套左右并排的版面是错的,该继续走竖屏那套。
 *
 * 这一块只放**横屏跟竖屏该做得不一样**的东西;两者一致的紧凑规则一律靠上面
 * 那个媒体查询列表复用,不在这里复制。
 * ═════════════════════════════════════════════════════════════════════ */
@media (orientation: landscape) and (max-height: 500px) {

  /* ── 度量衡:横屏富余的是**宽**,主角读数该跟宽走 ──────────────────
     竖屏那一档 `min(5.4vh, 11.5vw)` 在 375px 高时算出 20px、被 clamp 下限
     摁回 28px —— 等于主角字号在横屏一直卡在地板上,再宽也不长。改成以 vw
     为主:812×375 实测 33.8px,932×430 实测 38.7px。两档之间仍是 ≈1.8 的比,
     同屏依旧只出现三档(基调 §3)。**只重定义值,不新增 --t-hero 的用处。** */
  :root {
    --t-hero:  clamp(26px, min(9vh, 4.4vw), 42px);
    --t-major: clamp(15px, min(5vh, 2.4vw), 22px);
  }

  /* ── 大厅:横屏下**进不去游戏**的那条硬堵 ─────────────────────────
     #lobby 是 `display:flex; align-items:center; overflow-y:auto`,而
     .lobby-card 高 591px > 视口 375px。这是 flex 居中的经典死角:溢出朝两头
     长,而 scrollTop 到不了负数 —— 实测卡片顶在 **y=-108**、`<h2>` 在 -87、
     **名字输入框在 -38**,`#lobby.scrollTop` 已经是 0 还是够不着。
     老板刚说"横屏才更舒服",实际是横屏连名字都填不了。
     改成从顶部排、该滚就滚,一个像素都能到达。
     **不用 `align-items: safe center`**:Safari 17 之前不认,老板的机器版本
     未知,不赌。 */
  /* 居中那条死角已经在基础规则里按 margin:auto 修掉了(横竖屏共用一份)。
     这里只做横屏该做的两件事:刘海在横屏转到**左右两侧**,贴边处按最坏情况留白;
     内边距收一档,把省下的高度还给内容。 */
  #lobby {
    padding: calc(var(--panel-top, 46px) + 10px)
             max(14px, env(safe-area-inset-right))
             14px
             max(14px, env(safe-area-inset-left));
  }
  /* 横屏有的是宽度:两栏并排比一栏矮一半。720 那条单栏规则是纯"窄"的判断,
     在 667×375 的小横屏上会命中,这里明确改回来。 */
  .lobby-two { grid-template-columns: 1fr 1fr; }
  /* 竖向节奏收一档:这一屏的高度全花在段间距上了,字号一个都不动 */
  .lobby-card { padding: 16px clamp(16px, 2.6vw, 28px); }
  .lobby-card p { margin-bottom: 8px; line-height: 1.55; }
  .lobby-row { margin: 7px 0; }

  /* ⚙ 折叠浮层:手机块给的是 `max(220px, 62vw)` —— 那是按 375px 宽算的,
     在 812 上是 503px,半个屏幕的设置面板盖在棋盘/大厅上。横屏按内容给宽。 */
  .more-tools > .more-tools-body { width: min(320px, 62vw); }

  /* ══ 对战屏:三列 ══════════════════════════════════════════════
     竖屏把五条横带**上下堆**:对手 HUD / 对手结界区 / 战场 / 我的结界区 /
     我的 HUD。每条 48~137px,五条要 460px —— 横屏可用高度只有 375 减顶栏。
     **堆不下,这是算术不是手艺**:实测 #arena 被压成 **2px**(内容要 104),
     两条 HUD 和两排结界区互相重叠 25px,「还差 N 点打穿」被牌库堆盖住,
     「累计攻击」被顶栏压住。

     改成左中右三列,而且**每一列都读「上 = 对手 / 下 = 我」** —— 一条空间语法
     贯穿全屏,不用在"上下是敌我"和"左右是敌我"两套编码之间来回换脑子。
     战场**保持上下对撞**、不横过来:#clash 那条竖轴是这局游戏的因果轴
     (攻方的牌从上往下砸、伤害飘字生在那儿),横过来等于重新发明这游戏的读法。
     战场变成"列"之后就拿到整个棋盘的高度,而不再是两条 auto 吃剩的。

     列宽的账(可分宽 = 视口 − 内边距 20 − 列间距 16):
       667 → HUD 120 + 结界区 233 + 战场 278     844 → 143 + 295 + 370
       812 → HUD 138 + 结界区 284 + 战场 354     932 → 158 + 300 + 438
     结界区是三列里唯一按内容定宽的那一列 —— 它要塞下 6 席一行。 */
  #board {
    grid-template-columns:
      clamp(120px, 17vw, 168px) minmax(0, 1fr) clamp(232px, 35vw, 300px);
    /* 上下**不是** 1fr 1fr:对手那半格是**摘要**(席位一排 + 两枚计数芯片),
       我这半格里有真卡(手牌要用手指拿起来拖到台上)。对手用不完的那点高度
       让给我,比两边一样高、然后两边都不够用强。 */
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas: "hudE arena barE"
                         "hudM arena barM";
    gap: 6px 8px;
    padding: 6px max(10px, env(safe-area-inset-right))
             8px max(10px, env(safe-area-inset-left));
  }
  /* .side 只是竖屏用来把 HUD 和结界区绑成一条横带的盒子;横屏这两样分居两列,
     所以让它在网格里消失,四个子元素各认各的格。**board.js 一行不用改** ——
     结构、DOM 序、类名全不动,纯 CSS 重排。 */
  .side.enemy, .side.mine { display: contents; }
  #hud-1 { grid-area: hudE; }
  #hud-0 { grid-area: hudM; }
  .side.enemy > .barrier-wrap { grid-area: barE; }
  .side.mine  > .barrier-wrap { grid-area: barM; }
  #arena { grid-area: arena; }

  /* ── 左列:HUD 竖排 ────────────────────────────────────────────
     横屏的 HUD 是一根窄柱子,不是一条横带。顺序仍然 = 信息层级:
     判负进度(幽冥 / 席位)两条量表在上,金币 / 愿力那一档在下 ——
     对战中它们不会变,而两条量表**直接决定会不会判负**。
     牌库堆竖着贴在两条量表左边:它是"还能翻几张"的存量,跟量表读的是同一件事。 */
  .hud {
    flex-direction: column; align-items: stretch; justify-content: flex-start;
    gap: 5px; padding: 6px 8px;
  }
  /* 面板只包住自己的内容,不拉满整格 —— 空出来的那一截是**真的空地**
     (棋盘底色),翻牌按钮就落在那儿,不压在 HUD 的深色盒子上。
     实测:内容底 317,按钮顶 321。 */
  #hud-1, #hud-0 { align-self: start; }
  .hud .who { font-size: 12px; gap: 6px; }
  .hud .stats {
    display: grid; margin-left: 0; gap: 4px 7px; align-items: center;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "deck youming" "deck seats" "gold wish";
  }
  .hud .deckpile { grid-area: deck; align-self: stretch; height: auto; }
  .hud .gauge.youming { grid-area: youming; }
  .hud .gauge.seats   { grid-area: seats; }
  .hud .stat.gold { grid-area: gold; }
  .hud .stat.wish { grid-area: wish; }
  /* 数字靠右成列:两条量表的 N/M 对齐才瞟得准(tabular-nums 已在别处给过) */
  .hud .gauge .g-lab b { margin-left: auto; }

  /* ── 右列:两排结界区 ──────────────────────────────────────────
     竖屏那条 `"bar bar" / "dis hand"` 的网格照用(从上面的手机块继承),
     它本来就是"席位一排、两堆并排在下"的形状,在一根柱子里正合适。
     席位卡按**列宽**定尺寸而不是视口高度 —— 约束它的是这一列有多宽:
       (列宽 − 内边距 16 − 5 道 6px 间距)/ 6 席。实测 667→30.7px、812→37.4、
       932→42(封顶)。这几张是**计数**不是要读的牌(判负进度靠「席位 N/M」
       和满席那条朱红实线说),有先例:body.drawer-open 那一档就是 42~58px。
     横滚兜底仍然留着(.barrier 的 overflow-x:auto),所以永远不会切。 */
  .side > .barrier-wrap {
    /* 宽度是主约束(6 席一行),但高度也得进来 —— 底部决策条一掀开,这一格
       就从 199px 掉到 141px。只按 vw 算的话,席位排和两堆加起来会顶出格子。 */
    --cw: clamp(24px, min(4.6vw, 8.6vh), 42px);
    --ch: calc(var(--cw) * 1.4);
    --ch-sm: clamp(32px, min(5.6vw, 12vh), 58px);
    --cw-sm: calc(var(--ch-sm) / 1.4);
    align-content: start;
    /* **必须压掉基础规则那句 `align-items: center`。** `.barrier` 带
       `overflow-x:auto`,于是它那条网格轨道的自动最小尺寸是 0、可以被压扁;
       压扁之后 center 会让它**朝上下两头各溢出一半** —— 实测我这排结界区
       的顶边跑到 179,而格子从 192 才开始,正好骑在对手那两枚计数芯片上。
       改成 start:万一还是装不下,也只朝下走,不会往上撞别人。 */
    align-items: start;
  }
  /* 退场堆按内容给宽,剩下的全归手牌 —— **手牌是这一屏唯一要用手指拿起来的
     东西**(把法宝拖到台上那张卡上),它挤成一摞 24px 的小方块就等于拿不起来。
     退场堆只是个可点开的计数,不跟它抢。 */
  .side > .barrier-wrap { grid-template-columns: auto minmax(0, 1fr); }
  /* 「席位 N/M」从行尾挪到行首**单独一行**。两个理由:①它是老板点名必须读得到的
     读数之一,挤在 6 张卡右边、还压在横滚区里就是在赌他会去划;②它原来吃掉
     44px 宽,让出来正好够第 6 席。不换第二种颜色、不加光,只是换了位置。 */
  .barrier { flex-wrap: wrap; }
  .barrier .cap {
    order: -1; flex: 1 0 100%;
    display: flex; align-items: baseline; gap: 6px;
    padding: 0 2px 3px; margin: 0; border-left: 0;
    align-self: auto;
  }
  .barrier .cap br { display: none; }

  /* ── 中列:战场 ────────────────────────────────────────────────
     手机块把卡位行拆成了两行("spent spent" / "slot info"),那是因为窄屏
     info 列有 150px 的硬下限、一行排不开。横屏中列有 278~438px,一行排得开,
     而且**省下的那一行高度直接还给卡**。所以改回 `1fr auto 1fr`:
     卡在正中(#clash 那条竖轴才对得上它),已翻堆在左,读数在右。 */
  .slot-row {
    grid-template-columns: 1fr auto 1fr;
    grid-template-areas: none;
    gap: 4px 10px; width: 100%;
  }
  .slot-row .spent { grid-area: auto; justify-self: end; }
  .slot-row .slot { grid-area: auto; }
  .slot-row .side-info { grid-area: auto; justify-self: start; min-width: 0; }
  /* 三位数的「还差 N」在 667 上会顶出 info 列。**宁可折行也绝不裁切** ——
     被切掉的数字比换行的数字糟得多。 */
  .meter-num { flex-wrap: wrap; row-gap: 2px; }

  /* 台上那两张卡各拿战场列的一半高度。手机块那份 --ch-lg 要减掉两排结界区和
     垫牌(它们在竖屏跟战场抢同一列高度),横屏它们在**别的列**,再减就白缩一大截。 */
  :root {
    --ch-lg: clamp(84px, min(46vh, (var(--stage-h) - 62px) / 2), 200px);
  }
  /* 抽屉展开时同理:少的是高度,按剩下的高度重算,不套竖屏那份。
     `--ch` 也要跟着收:它是抽屉里那几张选项卡的尺寸,抽屉矮了卡不收就会被
     自己的抽屉裁掉(手机块那份 `clamp(66px, …)` 的下限是按 812 高定的)。 */
  body.drawer-open {
    --ch-lg: clamp(72px, min(34vh, (var(--stage-h) - 54px) / 2), 150px);
    --ch: clamp(38px, 11.5vh, 70px);
    --ch-sm: clamp(30px, 8vh, 50px);
  }
  /* 抽屉一开,棋盘只剩 ~160px,两个 HUD 竖排的下半截会被抽屉压住 ——
     实测「席位 N/M」那条量表整个被 .drawer-title 盖掉。
     **砍掉金币 / 愿力那一档**:它们在一场对战里根本不会变,而幽冥 / 席位两条
     **直接决定会不会判负**(手机块早就按同一条理由把「退场」那项省掉了)。
     省下的高度让两条量表留在抽屉上面。 */
  body.drawer-open .hud .stat.minor { display: none; }
  body.drawer-open .hud { gap: 3px; padding: 4px 8px; }
  body.drawer-open .hud .stats { gap: 3px 7px; }

  /* ── 翻牌按钮:左下角 ──────────────────────────────────────────
     横屏是**双手横握**,两个拇指停在左右下角,**正中央下方是死区**
     (竖屏恰恰相反,所以那边钉在底部中央是对的,照抄过来就是把主操作
     放进最难够的地方)。对战中要碰的只有两样:翻牌、从手牌拖法宝 ——
     手牌堆在右列下半格(右拇指),翻牌就落在左下角(左拇指)。
     左列下半格是我的 HUD,竖排之后底下本来就留白,按钮落在那片空处、
     不压任何读数(验收判据:按钮四角 elementFromPoint 不许命中 .gauge/.stat)。 */
  #flip-cta {
    left: max(10px, env(safe-area-inset-left)); right: auto;
    bottom: calc(10px + env(safe-area-inset-bottom));
    transform: none;
  }
  #flip-btn { padding: 8px 14px; gap: 7px; }

  /* ══ 浮层与常驻条 ═══════════════════════════════════════════════
     这一族的共同毛病是**高度是按竖屏猜的**:手机块给的 42vh / 58vh 在 812×375 上
     只有 157 / 217px,一张卡的数值读不完、一页图鉴只露得出大半张卡。 */

  /* ── 图鉴:改回右侧停靠(手机块把它改成了顶部下拉)──────────────────
     手机块那么做的**理由是**:375px 宽的右侧栏几乎占满屏,会正好盖住居中的
     翻牌按钮。横屏这个前提没了 —— 翻牌按钮在**左下角**(见上面对战屏那段),
     右侧栏永远跟它打不着。而顶部下拉在横屏是灾难:实测头 63 + 筛选条 55 =
     118px 的壳,只给卡池留 **99px**,而一张卡 126px 高 —— **卡是被切着看的**,
     内容 1626px 要滚 16.4 屏。 */
  #codex {
    top: var(--panel-top, 46px); bottom: 0; right: 0; left: auto;
    width: min(420px, 52vw); max-height: none;
    border-left: 1px solid var(--line); border-bottom: none;
    border-radius: 16px 0 0 16px;
    padding-right: env(safe-area-inset-right);
  }
  .codex-head { padding: 6px 10px; }
  .codex-bar { padding: 6px 10px; gap: 6px; }
  #codex-grid { padding: 2px 10px 10px; }

  /* ── 卡牌详情:改成右侧栏 ─────────────────────────────────────────
     手机块把它做成底部抽屉 `max-height: 42vh` —— 横屏只有 **157px**,
     一张卡的"数值来源 + 装备明细"读不完。横屏余量在宽度上,靠右站一整条。
     跟图鉴同一边:两者永不同时打开(点卡看详情时图鉴是收着的)。 */
  #detail, #detail.left {
    top: var(--panel-top, 46px); bottom: 0; right: 0; left: auto;
    width: min(340px, 42vw); max-width: none; max-height: none;
    border-radius: 16px 0 0 16px;
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  }

  /* ── 决策抽屉:按比例走,不按竖屏那个下限 ──────────────────────────
     `--drawer-h` 的下限 196px 是给竖屏定的("至少放得下两行选项"),
     在 375px 高的屏上它就是 **52%**;实测降神卡那一问的抽屉占到 y 107–375 = **71%**。
     横屏按比例给,并把抽屉头压成一行。
     **不做侧边停靠**:showcase.js:386 读 `drawer.getBoundingClientRect().top` 来避让,
     把抽屉挪到右边会让那条避让读到一个没有意义的值 —— 那是别人的文件,
     不为了好看去动别人的前提。 */
  :root { --drawer-h: clamp(112px, 46vh, 190px); }
  .drawer-head { padding: 6px 12px; }
  .drawer-title { font-size: var(--t-body); }

  /* ── 观战:桌位面板要有高度上限 ───────────────────────────────────
     6 人局一轮 3 张桌 + 表头,底部升起的面板没有上限就会顶出屏幕。 */
  #spectate-pane {
    max-height: calc(100dvh - 24px); display: flex; flex-direction: column;
    bottom: calc(10px + env(safe-area-inset-bottom));
  }
  #spectate-list { overflow-y: auto; min-height: 0; }
  /* 观战横幅:竖屏钉在底部中央(那儿是拇指区);横屏正中央下方是**战场**,
     而且是死区。挪到右下角 —— 跟左下角的翻牌按钮分居两角,两者本来就永不同屏。 */
  #spectate-bar {
    left: auto; right: max(10px, env(safe-area-inset-right)); margin-inline: 0;
    bottom: calc(10px + env(safe-area-inset-bottom));
    max-width: min(420px, 54vw);
  }

  /* ══ 备战:双栏 + 包卡横躺 ═══════════════════════════════════════
     改前实测(812×375):顶部信息条折成两行 81px + 底部 68px,`.prep-body`
     只剩 **169px**,而内容 1531px —— **要滚 9.1 屏**;一张卡包卡 **244×342**
     摆在 169px 的窗口里,连卡名都露不出来(截图上只看得见三个卡框的顶边)。

     病根:`.pack-row` 按 `auto-fill minmax(196px, 1fr)` 排 —— **卡宽 = 轨道宽、
     卡高按 5:7 自撑,整条路上没有任何高度预算**。横屏富余的是宽、缺的是高,
     这条判据正好反了。

     所以横屏做两件事:
     ① **body 改左右两栏**:左边「妖怪集结」(这一屏的**决策对象** —— 买不买),
        右边「牌库 + 法宝」(存量清单)。两栏各自滚,滚动长度直接减半;更要紧的是
        "在卖什么"和"我已经有什么"从上下隔着九屏变成**并排**。这才是横屏该拿的
        好处,不是把竖屏缩一遍。
     ② **每张包卡横躺**:卡图在左,卡名 / 技能 / 看技能在右。竖着排的话
        卡 + 卡名 + 三行技能 = 一张要 210px 高,而这一栏只有 ~150px;横过来之后
        一张的高度就是卡的高度,不够的那一维用横屏富余的宽去换。
        **三行技能一行都没省** —— 上一轮已经判过「这几行是买不买的判据,不是注脚」,
        横屏没有理由推翻它。 */
  .prep-top { flex-wrap: nowrap; padding: 5px 14px; gap: 14px; }
  .prep-purse { flex-wrap: nowrap; gap: 14px; }
  /* 提示语在竖屏要独占一行(窄);横屏有的是宽,让它回到同一行的右端 */
  .prep-note { flex: 0 1 auto; margin-left: auto; min-height: 0; }
  .prep-foot { padding: 6px 14px; }

  .prep-body {
    display: grid; overflow: hidden; gap: 1px;
    grid-template-columns: 1.25fr 1fr;
    /* 两行都得能压缩:写 `auto` 的话牌库会按内容吃满(实测它要 393px),
       法宝那一栏只剩 20px —— 一整栏被挤没了 */
    grid-template-rows: minmax(0, 1.1fr) minmax(0, 1fr);
    grid-template-areas: "shop deck"
                         "shop tres";
  }
  /* 三栏各自滚:头上的金币和脚下的「准备完毕」永远不动,只滚你正在看的那一栏 */
  .prep-row.shop { grid-area: shop; overflow-y: auto; }
  .prep-row.deck { grid-area: deck; overflow-y: auto; }
  .prep-row.tres { grid-area: tres; overflow-y: auto; }
  .prep-row { padding: 8px 14px 12px; }
  .prep-row h3 { margin-bottom: 6px; }
  /* 「开卡包」是这一栏的主操作,不许被自己那一栏的滚动带走 */
  .shopbar {
    position: sticky; top: 0; z-index: 2;
    margin-bottom: 8px; gap: 8px; padding-bottom: 6px;
    background: rgba(12, 16, 24, .96);
  }

  /* 卡尺寸交给**高度**:一张横躺的包卡,它的高度就是卡的高度。
     --cw-lg 也要在 #prep 上重算一遍 —— :root 那份是拿**根上**的 --ch-lg 算的,
     跟备战面板自己这份不是一个数(这个坑现成的规则里就埋着)。 */
  #prep {
    /* 一屏能露出 1.4 张 —— 相邻两张能对着看,而不是一次只见一张。
       6 张全塞进来是做不到的(一张读得清就要 ~150px 高,而这一栏只有 213px),
       这一屏本来就是"读完再挑",滚是对的,**滚得少**才是目标。 */
    --ch-lg: clamp(96px, min(33vh, (var(--stage-h) - 180px)), 138px);
    --cw-lg: calc(var(--ch-lg) / 1.4);
  }
  .pack-row { grid-template-columns: repeat(auto-fill, minmax(264px, 1fr)); gap: 10px; }
  .pack-row .pick { max-width: none; }
  #prep .pick {
    display: grid; width: 100%;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "card cap"
                         "card skill"
                         "card look";
    /* 三行都按内容:第三行写 1fr 会把"卡比文字高"那截空白**留在文字列底下**,
       实测一张要 180px(卡才 124)—— 一屏就只露得出 1.18 张。改 auto 之后
       一张 = 卡高,一屏露 1.7 张,相邻两张能对着看。 */
    grid-template-rows: auto auto auto;
    align-items: start; align-content: start; gap: 3px 10px;
  }
  #prep .pick > .card {
    grid-area: card; width: var(--cw-lg); height: var(--ch-lg); aspect-ratio: auto;
  }
  #prep .pick .cap { grid-area: cap; text-align: left; }
  #prep .pick .skill { grid-area: skill; padding: 0; }
  #prep .pick .look { grid-area: look; justify-self: start; align-self: start; }

  /* ══ 组卡:改回左右双栏 ═════════════════════════════════════════
     `max-width: 980px` 那条把卡池和我的卡组叠成上下两栏 —— 它问的是**窄**,
     而横屏 844 宽是**有宽度没高度**,判反了。实测(844×390,改前):卡池网格
     高 **59px**、内容 299px,一次看一行半,组一副 16 张要滚十几次。
     改回双栏之后每栏拿满高度,而且"池子在左、我的在右"跟拖拽方向一致。 */
  .build-body {
    grid-template-columns: 1fr minmax(268px, 0.7fr);
    grid-template-rows: minmax(0, 1fr);
  }
  .build-col.mine { border-left: 1px solid var(--line); border-top: none; }
  .build-col { padding: 6px 12px; }
  .tre-list { max-height: none; }
  .build-head { padding: 5px 12px; gap: 8px; }
  /* 卡宽跟**高度**一起联动:横屏缺的是高,只按 vw 算会让一栏只放得下一行 */
  #build { --build-cw: clamp(64px, min(9vw, 15vh), 96px); }

  /* ══ 结算:唯一的动作不许要先找 ═══════════════════════════════════
     `.re-inner` 是 `max-height: 88dvh; overflow-y:auto` —— 375 高时只有 330px。
     实测把天梯补到 6 行(6 人局)之后内容 460px,「进入下一轮」掉到 y=412,
     **要在面板里滚 132px 才够得着这一屏唯一的按钮**。
     名次是主角、要读,可以滚;按钮是出口、要按,不许滚走。所以把面板改成
     "标题 / 判语 / 可滚的两栏 / 钉住的按钮"四行,只让中间那栏滚。 */
  .re-inner {
    display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto;
    row-gap: 8px; overflow: hidden;
  }
  .re-cols { overflow-y: auto; min-height: 0; }
  /* 两栏一滚,「本轮战果 / 天梯」两个栏头就滚没了 —— 滚到一半分不清左右两列
     各是什么。钉住它们:栏头是**这一列在说什么**,跟出口按钮一样不该被滚走 */
  .re-cols h4 {
    position: sticky; top: 0; z-index: 1;
    margin: 0 0 6px; padding: 2px 0 4px;
    background: rgba(14, 19, 29, .97);
  }
  #re-next { justify-self: center; margin: 0; }

  /* ── 结算:上面那块把它压成了单栏,横屏要改回双栏 ──────────────────
     `.re-cols { 1fr }` 与 `.re-inner { width: min(420px,92vw) }` 是**窄**的
     判断(375px 宽塞不下两栏);横屏有 812px 宽、缺的是高度,单栏只会让这一屏
     更长、把「进入下一轮」推得更远。 */
  .re-cols { grid-template-columns: 1fr 1fr; gap: 8px 18px; }
  .re-inner {
    min-width: min(680px, 92vw); width: auto;
    max-height: calc(100dvh - 16px); padding: 16px 22px;
  }
}

/* ------------------------------------------------------------------
   场上卡牌挂着的法宝 —— **把法宝的卡面贴在它身上**。
   老板 2026-08-26:「法宝……战斗的时候都没有图片」。原来这里是一个 ⚔ 角标,
   装上去之后是什么全靠 title;而 emoji 当图标是视觉基调 §2 铁律六点名禁止的。
   现在沿卡右缘竖排真卡面(至多两张,多的用 +N 收口)。

   金是"香火钱/可交易之物"(基调 §1),法宝正在这一类里,所以描边用金是语义
   对的;但**只描一道 1px 的边,不发光** —— 一屏至多一个光源,对战屏那盏灯
   要留给攻/守两圈卡框。

   场上是公开信息,equipment 本来就发给双方(views.py card_brief /
   _player_view)。只加在场上的卡上,手牌区的法宝(treasure-card)不套。
   ------------------------------------------------------------------ */
.card.equip-glow::after {
  content: "";
  position: absolute; inset: 0; z-index: 3;
  border-radius: var(--r);
  box-shadow: inset 0 0 0 1px var(--gold, #f5c542);
  pointer-events: none;
}
.equip-rack {
  position: absolute; top: 3px; right: 3px; z-index: 6;
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  width: 24%; max-width: 34px; pointer-events: none;
}
.eq-thumb {
  width: 100%; aspect-ratio: 5 / 7; border-radius: 3px;
  object-fit: cover; display: block;
  background: linear-gradient(160deg, #1d2637, #10151f);
  box-shadow: 0 0 0 1px var(--gold, #f5c542), 0 2px 6px rgba(0, 0, 0, .65);
  /* 查不到图时是个 <span>,里头放法宝名的头一个字 */
  font-size: 10px; font-weight: 700; color: var(--gold);
  text-align: center; line-height: 1.9;
}
.eq-thumb.spent { filter: grayscale(1) brightness(.6); }   /* 失灵的法宝 */
.eq-more {
  padding: 0 4px; border-radius: 3px; font-size: 9px; font-weight: 700;
  background: rgba(8, 11, 17, .85); color: var(--gold);
  box-shadow: 0 0 0 1px var(--line);
}
/* 结界区/垫牌那种小卡上只留一张,再多就盖住卡脸了 */
.card.sm .equip-rack { width: 36%; max-width: 22px; top: 2px; right: 2px; }
.card.sm .equip-rack .eq-thumb:nth-child(n + 2), .card.sm .eq-more { display: none; }

/* ------------------------------------------------------------------
   派生卡的记号:器灵形态 / 闪光X 借的是本体的卡面(法宝卡双面合印,全库
   只有一张照片)。两面的数值不是一组(玄冥真水装备加成 2/2、器灵形态 4/4),
   所以必须让人看出"这不是本体"。
   按基调铁律一,这不是加新颜色的场合 —— 一枚灰阶小标,靠位置和明度说话。
   字面由 card.js 从卡名里现算(器灵 / 闪光),前端不写死任何一张派生卡。
   ------------------------------------------------------------------ */
.derived-tag {
  position: absolute; left: 3px; top: 3px; z-index: 5;
  padding: 0 5px; border-radius: 3px;
  font-size: 9px; letter-spacing: 1px; line-height: 15px;
  background: rgba(8, 11, 17, .82); color: var(--ink);
  border: 1px solid var(--line); pointer-events: none;
}
.card.sm .derived-tag { left: 2px; top: 2px; padding: 0 3px; font-size: 8px; line-height: 13px; }
.card.lg .derived-tag { font-size: 10px; line-height: 17px; }


/* ───────────────────────── 降神卡 ─────────────────────────
   顶栏下面那条常驻小条 + 图鉴里的降神页。降神卡没有卡图,全是文字,
   所以这一套完全不复用 .card 那一组样式。 */

/* 这一条待在普通流里(#board 靠它自然下移),但它下面那几块面板是
   position: fixed 的,量不到它 —— godcard.js 量完把底边写进 --panel-top */
#godstrip {
  /* body 是 column flex:光给 max-width + margin auto 的话,auto 外边距会让
     它缩到内容宽度再居中(实测只有 240px 宽)。得显式给宽度 */
  margin: 0 auto; padding: 7px 14px; width: min(1180px, 100%);
  box-sizing: border-box;
  /* 必须定位 + 抬 z:雨夜底景 .scene 是 position:fixed z-index:0,而它那层
     linear-gradient 是**不透明**的。普通流里的块级元素画在"定位元素"之前,
     所以不抬的话整条被底景盖死(它 pointer-events:none,点得到但看不见,
     查了半天才发现) */
  position: relative; z-index: 21;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(90deg, rgba(245, 197, 66, .10), rgba(245, 197, 66, 0) 70%);
  font-size: 12.5px;
}
.godstrip-main { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.god-mine { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.god-mine.spent { opacity: .5; }
.god-name { color: var(--gold); font-weight: 700; letter-spacing: .5px; }
.god-short { color: var(--muted); }
.god-used, .god-armed, .god-ready {
  padding: 1px 8px; border-radius: 999px; font-size: 11px; border: 1px solid var(--line);
}
.god-armed { color: var(--gold); border-color: var(--gold); }
.god-ready { color: var(--muted); }
.god-used { color: var(--muted); text-decoration: line-through; }
.godstrip-toggle {
  margin-left: auto; padding: 3px 10px; font-size: 11.5px;
  background: transparent; color: var(--muted);
}
/* 领域里的秒表(只有姜子牙本人看得到 —— 别人被整块挡在等待屏上) */
.god-domain {
  padding: 2px 10px; border-radius: 999px; font-size: 11.5px;
  border: 1px solid var(--gold); color: var(--gold);
  background: rgba(245, 197, 66, .10);
}
.god-domain b { font-variant-numeric: tabular-nums; font-size: 13px; }
.godstrip-others {
  display: flex; flex-wrap: wrap; gap: 6px 16px; padding: 7px 2px 2px;
  border-top: 1px dashed var(--line); margin-top: 6px;
}
.god-other { font-size: 11.5px; color: var(--muted); }
.god-other.spent { opacity: .45; }
.god-other b { color: var(--ink); font-weight: 600; }

.godsheet {
  padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2); margin-bottom: 10px;
}
.godsheet h4 { margin: 0 0 3px; font-size: 15px; color: var(--gold); }
.godsheet h4 em { font-style: normal; color: var(--ink); font-weight: 600; }
.godsheet .god-when { margin: 0 0 7px; font-size: 11px; color: var(--muted); letter-spacing: 1px; }
.godsheet .god-text { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--ink); }

/* 八卦洞天开出来的那一席:混放,跟普通席位的同名叠放不是一回事 */
.slot.special { border-color: var(--gold); box-shadow: 0 0 14px rgba(245, 197, 66, .22) inset; }
/* 规则席位角标(老板 2026-09-07):潘多拉/杂技/洞天共用一个壳,
   之前只有 .special 才画角标 —— 现在潘多拉/杂技也要出,选择器放宽到全部 .slot */
.slot .seat-tag {
  position: absolute; left: 3px; top: 3px; z-index: 3;
  padding: 0 5px; border-radius: 5px; font-size: 9.5px; letter-spacing: 1px;
  background: rgba(245, 197, 66, .85); color: #16110a; font-weight: 700;
}
/* 规则席位若恰好还没人入座(空席也上棋盘):画成带角标的空框,虚线区分于
   已经落子的席位,宽度/占位不变(仍是 .slot.filled 那一层壳撑着) */
.slot.filled.seat-empty {
  border-style: dashed; background: rgba(255, 255, 255, .015); box-shadow: none;
}

/* 窄屏:顶栏本来就折成两行了,这一条必须压成一行 —— 手机屏幕总共就这么高,
   常驻条占三行等于把备战面板挤没。一句话说明收起来(卡面原文在图鉴里,
   真要发动时抽屉上还会再念一遍) */
@media (max-width: 640px), (orientation: landscape) and (max-height: 500px) {
  #godstrip { padding: 5px 10px; font-size: 11.5px; }
  .god-short { display: none; }
  /* 不许换行:换行就是两行,常驻条在手机上占两行太贵。名字自己省略号 */
  .godstrip-main { flex-wrap: nowrap; }
  .god-mine { gap: 7px; min-width: 0; flex-wrap: nowrap; overflow: hidden; }
  .god-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .god-used, .god-armed, .god-ready { white-space: nowrap; flex: 0 0 auto; }
  .godstrip-toggle { margin-left: auto; flex: 0 0 auto; padding: 3px 6px; }
  .godstrip-others { max-height: 25vh; overflow-y: auto; }
  /* 窄屏那条 `button:not(.icon):not(.op) { min-height:44px }` 会把这个折叠钮
     撑到 44px,整条常驻条跟着变成 54px —— 22px 是白丢的,而战场正缺这点。
     命中区改用伪元素外扩(仿 button.op),视觉高度压回去。 */
  #godstrip .godstrip-toggle { min-height: 0; position: relative; }
  #godstrip .godstrip-toggle::after { content: ""; position: absolute; inset: -11px -4px; }
}

/* ───────────────────────── 海克斯栏(powers.js) ─────────────────────────
   谁选了哪个人物技能、哪几条契约之力,以及手上攥着几张妖怪复制器。
   老板管这套叫「海克斯」,2026-08-26 点名:「选择了的海克斯也得显示出来」。

   **为什么这一条是单色的**:视觉基调 §1 把三色钉死成语义 —— 朱红是落笔的
   裁决、青碧是结构骨架、暖金是能换东西的香火钱。契约之力三样都不是,它是
   "我这局是谁"。按 §0「真不是,就用 --ink / --muted 的明度差去区分」,这一条
   通篇走灰阶:人物技能用 --ink 实心,契约用 --ink 常规,已翻面靠透明度和
   删除线,主动/被动只由那枚小标签的文字区分。唯一的例外是妖怪复制器的库存
   —— 它是**攥在手里能换卡的道具**,那就是香火钱,给 --gold,语义对得上。

   **不给 z-index**:一旦成为层叠上下文,下拉就被关在里面,备战面板(z:30)
   一盖就没了。让它保持 z-index:auto,只把两个浮层单独抬到 33 —— 高于棋盘
   (10)与备战面板(30),低于抽屉(55)/详情(60)/结算(75)。 */

#powerstrip {
  /* body 是 column flex:只给 max-width + margin auto 的话,auto 外边距会把它
     缩到内容宽度再居中(#godstrip 当年实测只剩 240px)。得显式给宽度 */
  margin: 0 auto; padding: 5px 14px; width: min(1180px, 100%);
  box-sizing: border-box; position: relative;
  background: rgba(19, 25, 37, .55);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-label);
}
.pw-main { display: flex; align-items: center; gap: 8px; min-width: 0; }
.pw-lab { flex: 0 0 auto; font-size: 10px; letter-spacing: 3px; color: var(--muted); }
.pw-none { color: var(--muted); }
/* 常驻条的职责是"提醒我带了什么",不是"把契约读给我听"——所以任何宽度下
   都只占一行:芯片超过 3 枚就把一行话收起来只留名字,再挤不下就交给「全场」
   下拉。它多占一行,战场就少一行(#board 是 auto/1fr/auto,arena 拿的是余量)。 */
.pw-chips {
  display: flex; align-items: center; gap: 6px; flex-wrap: nowrap;
  min-width: 0; overflow: hidden;
}
.pw-chips:has(.pw-chip:nth-of-type(4)) .pw-short { display: none; }

.pw-chip {
  position: relative; display: inline-flex; align-items: baseline; gap: 5px;
  flex: 0 1 auto; min-width: 0; max-width: 100%; padding: 2px 9px; border-radius: 999px;
  font: inherit; font-size: var(--t-label); text-align: left;
  background: rgba(28, 36, 52, .72); border: 1px solid var(--line); color: var(--ink);
  transition: border-color var(--fast) var(--ease);
}
/* 触屏命中区外扩到 ~40px(仿 button.op 的做法):视觉不动、不占布局 */
.pw-chip::after { content: ""; position: absolute; inset: -9px -2px; }
.pw-chip { overflow: hidden; }   /* 压缩到极限时截断,别把名字压出边框 */
.pw-chip:hover { border-color: rgba(125, 155, 195, .5); }
.pw-chip:focus-visible { outline: 2px solid var(--def); outline-offset: 2px; }
/* 名字任何时候都不许被压扁 —— 一行话可以没有,名字没了这条就白挂了 */
.pw-chip b { flex: 0 0 auto; font-weight: 700; white-space: nowrap; }
/* 人物技能只比契约重一档(实心底 vs 描边),不另点一盏灯 */
.pw-chip.k-ch { border-color: rgba(160, 190, 230, .34); }
.pw-chip.k-ch b { color: #fff; }
.pw-kind {
  flex: 0 0 auto; font-style: normal; font-size: 9px; letter-spacing: 1px;
  padding: 0 4px; border-radius: 3px;
  background: rgba(125, 155, 195, .2); color: var(--muted);
}
.pw-chip.k-ch .pw-kind { background: rgba(232, 237, 247, .82); color: #10141d; }
.pw-short {
  flex: 0 1 auto; min-width: 0; color: var(--muted); font-size: 10.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 手册 p81:「【主动契约】每回合至多使用 1 次,使用后翻面」——
   用过的就画成翻过去的样子:压暗 + 斜纹 + 名字划掉,不靠颜色 */
.pw-chip.used {
  opacity: .48; border-style: dashed;
  background:
    repeating-linear-gradient(45deg, rgba(125, 155, 195, .1) 0 5px, transparent 5px 10px),
    rgba(20, 26, 38, .8);
}
.pw-chip.used b { text-decoration: line-through; }
.pw-used {
  flex: 0 0 auto; font-style: normal; font-size: 9px;
  letter-spacing: 1px; color: var(--muted);
}
/* 复制器是能换卡的道具 = 香火钱,这一处用金是语义对的 */
.pw-copier { flex: 0 0 auto; font-size: 10.5px; color: var(--gold); white-space: nowrap; }
.pw-toggle {
  margin-left: auto; flex: 0 0 auto; padding: 3px 10px; border-radius: 999px;
  font-size: var(--t-label); background: transparent; color: var(--muted);
}

/* 「全场」下拉:整桌一人一行。契约是公开信息(手册 p81 正面朝上摆着),
   所以别人的也在里面 —— 但常驻条上只留自己的,不然手机上一行放不下。 */
.pw-all {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 33;
  max-height: min(52vh, 420px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 7px;
  padding: 9px 14px 11px; border-radius: 0 0 var(--r) var(--r);
  background: rgba(14, 19, 29, .985);
  border: 1px solid var(--line); border-top: none;
  box-shadow: 0 16px 38px rgba(0, 0, 0, .62);
}
.pw-p { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; min-width: 0; }
/* 下拉里有的是纵向空间:芯片换行铺开,不像常驻条那样单行截断/横滚 */
.pw-all .pw-chips {
  flex-wrap: wrap; overflow: visible;
  -webkit-mask-image: none; mask-image: none;
}
.pw-all .pw-chip { flex: 0 0 auto; }
.pw-p.me { padding-left: 7px; border-left: 2px solid rgba(232, 237, 247, .6); }
.pw-who {
  flex: 0 0 auto; min-width: 64px; font-size: var(--t-label);
  font-weight: 700; color: var(--ink);
}
.pw-who i { font-style: normal; color: var(--muted); margin-left: 3px; }

/* 卡面全文:一行话常驻在芯片上,全文点开才看 —— 45 条契约的全文糊在屏幕上
   等于没显示 */
.pw-card {
  position: absolute; left: 14px; top: 100%; z-index: 33;
  width: min(520px, calc(100% - 28px));
  max-height: min(46vh, 360px); overflow-y: auto;
  padding: 10px 13px 12px; border-radius: var(--r);
  background: rgba(14, 19, 29, .99);
  border: 1px solid rgba(160, 190, 230, .28);
  box-shadow: 0 16px 38px rgba(0, 0, 0, .66);
}
.pw-card-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.pw-card-head b { font-size: var(--t-body); color: #fff; letter-spacing: 1px; }
.pw-card-kind { font-size: 10px; letter-spacing: 2px; color: var(--muted); }
.pw-card-who { font-size: 10.5px; color: var(--muted); }
.pw-card-close {
  margin-left: auto; flex: 0 0 auto; padding: 2px 8px; font-size: 13px; line-height: 1;
  background: transparent; color: var(--muted);
}
.pw-card-short { margin: 7px 0 0; font-size: var(--t-label); color: var(--ink); }
/* 翻面 = 这回合用掉了,是一次已经落下的裁决,朱红在这里是语义对的 */
.pw-card-flag { margin: 6px 0 0; font-size: 11px; color: var(--atk); }
.pw-card-text {
  margin: 8px 0 0; padding: 8px 10px; border-radius: var(--r);
  font-size: var(--t-label); line-height: 1.75; color: var(--ink);
  background: rgba(8, 11, 17, .6); border: 1px solid var(--line);
}

/* 窄屏:这一条只许占一行。一行话收起来(点开看全文),别人的收进「全场」;
   卡面全文改成底部抽屉,跟 #detail 走同一个手势 —— 开着也不挡战场主体。 */
@media (max-width: 640px), (orientation: landscape) and (max-height: 500px) {
  #powerstrip { padding: 4px 10px; }
  .pw-main { flex-wrap: nowrap; }
  .pw-short { display: none; }   /* 窄屏一律只留名字,全文点开看 */
  /* 4 枚芯片的名字在 375px 上排不下。压缩会互相压字,截断会丢名字 ——
     所以这一条自己横向滚(页面本身仍然不滚,见视觉基调 §2 铁律五),
     芯片一律不压缩;要一次看全整桌的,右边还有「全场」下拉。 */
  .pw-chips {
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    /* 右缘淡出:告诉手指"这里还能划",否则最后一枚芯片被硬切开像是画坏了 */
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
  }
  .pw-chips::-webkit-scrollbar { display: none; }
  .pw-chip { flex: 0 0 auto; }
  .pw-chip { padding: 1px 7px; font-size: 10.5px; }
  .pw-copier { font-size: 9.5px; }
  .pw-toggle { padding: 2px 8px; font-size: 10.5px; }
  .pw-all { max-height: 46vh; }
  /* 卡面全文在窄屏是**顶部下拉**,不是底部抽屉 —— 跟 #codex 同一个理由:
     屏幕下半是拇指区(翻牌按钮、决策抽屉都在那儿),把只读的资料压在那里
     一定会跟正在等你拍板的东西打架(实测决策抽屉 z:55 直接把它盖没)。
     从常驻条底下拉出来,谁弹的谁挂着,战场下半永远露在外面。 */
  .pw-card {
    position: fixed; left: 0; right: 0; bottom: auto;
    top: var(--panel-top, 88px); width: 100%; max-height: 46vh;
    border-radius: 0 0 16px 16px;
    animation: riseDown var(--beat) var(--ease);
  }
  /* 窄屏那条 `button:not(.icon):not(.op) { min-height: 44px }` 会把芯片撑到
     44px 高,这一条就成了三行 —— 命中区改由 .pw-chip::after 外扩解决,
     视觉高度必须压回去。用 #powerstrip 前缀是为了压过那条的特异度。 */
  #powerstrip .pw-chip, #powerstrip .pw-toggle, #powerstrip .pw-card-close {
    min-height: 0;
  }
}

/* 天阶已满时,池子里没带的天阶提示"点了会顶掉最早那件" */
.treasure-card.swaps { opacity: .55; }
.treasure-card.swaps:hover { opacity: 1; outline: 1px dashed var(--gold); }

/* ═════════════════ 切视角(看别人那桌)═════════════════
 *
 * 老板 2026-08-29:「对战环节玩家可以自由切换视角观看其他选手的对战。」
 * 取代了原来那条只给观战席用的 #obs-tables 横条。
 *
 * 为什么两块都 fixed:战场的高度预算 --stage-h 只减顶栏 + 常驻条,谁在正常
 * 流里插一条,#arena 就把"还差几点打穿"直接切掉(它 overflow:hidden,不缩)。
 * 375×812 上实测差 107px,一条 30px 的桌位条足够让读数消失。
 *
 * 配色:观战本身是**结构**信息(我在看哪一段天梯、哪一桌),所以用青碧
 * (基调 §1「青碧是校勘线」);只有"轮到你了"那一下才是朱红 —— 那是真的
 * 有人在等你落笔。一屏至多一个光源:平时这两块一点辉光都没有,只有 .hot
 * 亮起来,而那一刻棋盘上放的是别人那桌,盘面本来就不该抢戏。 */

#btn-spectate.on { color: var(--def); border-color: rgba(34, 211, 238, .5); }

/* ── 桌位面板:底部升起,不是顶部下拉。选桌是要按的,按的东西落在
      下半屏(基调 §2 铁律五)。桌面端也照这个位置,省得两套。 ── */
/* 居中用 left/right:0 + margin:auto,不用 translateX ——
   transform 要留给升起动画,两个人抢一个属性一定会横向弹一下 */
#spectate-pane {
  position: fixed; z-index: 60;
  left: 0; right: 0; margin-inline: auto;
  bottom: calc(12px + env(safe-area-inset-bottom));
  width: min(420px, calc(100vw - 20px));
  background: var(--panel-2); backdrop-filter: blur(10px);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .5);
  animation: spectateRise .18s var(--ease);
  overflow: hidden;
}
@keyframes spectateRise { from { transform: translateY(14px); opacity: 0; } }
/* 抽屉展开时把面板抬到抽屉上面 —— 选桌的时候手里那个决策还在等着,
   把它盖住等于逼人二选一 */
body.drawer-open #spectate-pane {
  bottom: calc(var(--drawer-h) + 10px + env(safe-area-inset-bottom));
}
#spectate-pane .sp-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 9px 8px 7px 14px;
  font-size: var(--t-label); color: var(--muted);
  border-bottom: 1px solid var(--line);
}
#spectate-pane .sp-head .icon { min-width: 32px; min-height: 32px; }
#spectate-list { list-style: none; margin: 0; padding: 6px; }

.sp-row {
  display: grid; gap: 2px 10px; width: 100%;
  grid-template-columns: 1fr auto; align-items: center;
  padding: 9px 11px; min-height: 44px;   /* 触屏命中区 */
  font: inherit; text-align: left; cursor: pointer;
  color: var(--ink); background: transparent;
  border: 1px solid transparent; border-radius: var(--r);
}
.sp-row:hover { background: rgba(125, 155, 195, .1); }
.sp-row.on { border-color: rgba(34, 211, 238, .45); background: rgba(34, 211, 238, .08); }
.sp-row.over { color: var(--muted); }
.sp-venue {
  grid-column: 1 / -1; font-size: var(--t-label); color: var(--muted);
  letter-spacing: .04em;
}
.sp-vs { font-size: var(--t-body); }
.sp-vs i { font-style: normal; color: var(--muted); margin: 0 3px; }
.sp-tail { display: flex; align-items: center; gap: 6px; justify-self: end;
  font-size: var(--t-label); }
.sp-mine { color: var(--def); border: 1px solid rgba(34, 211, 238, .4);
  border-radius: 5px; padding: 1px 5px; }
.sp-live { color: var(--ok); }
.sp-live::before {
  content: ""; display: inline-block; width: 6px; height: 6px;
  margin-right: 5px; border-radius: 50%; background: var(--ok);
  vertical-align: middle;
}
.sp-over { color: var(--muted); }

/* ── 观战横幅:我在看谁 + 一步回自己那桌 ── */
/* 高度跟翻牌按钮取同一个数(见窄屏那条 #flip-cta):抬到自己 HUD 那一条
   上面 —— 压住幽冥 / 席位 / 金币这几个判负读数,比压住结界区糟得多。
   两者永不同屏(观战时翻牌按钮一定收着),所以共用这个位置不会撞。 */
#spectate-bar {
  position: fixed; z-index: 41;
  left: 0; right: 0; margin-inline: auto; width: fit-content;
  bottom: calc(54px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 8px;
  max-width: calc(100vw - 16px);
  padding: 5px 6px;
  background: var(--panel-2); backdrop-filter: blur(8px);
  border: 1px solid rgba(34, 211, 238, .32); border-radius: 999px;
}
/* 名字这一段是唯一可以被挤扁的:窄屏上「轮到你了 N 秒」和「回我这桌」
   一个字都不许省 —— 那两样是行动指令,名字只是说明 */
#spectate-who {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  font: inherit; font-size: var(--t-body); color: var(--ink);
  background: transparent; border: 0; cursor: pointer;
  padding: 4px 6px; min-height: 0;
}
#spectate-who .sp-vs {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#spectate-who .sp-over { flex: none; }
/* 眼睛用两个圆画,不用 emoji(基调 §2 铁律六点名禁止 emoji 当图标) */
.sp-eye {
  width: 15px; height: 9px; flex: none;
  border: 1.5px solid var(--def); border-radius: 50% / 60% 60% 40% 40%;
  position: relative;
}
.sp-eye::after {
  content: ""; position: absolute; inset: 1.5px;
  margin: auto; width: 3px; height: 3px;
  border-radius: 50%; background: var(--def);
}
/* 平时不发光 —— 一屏至多一个光源,而观战时那盏灯该留给盘面。
   只有"轮到你了"(.hot)才把它升成朱红:那才是真的有人在等你落笔 */
#spectate-back {
  padding: 6px 12px; min-height: 0; white-space: nowrap;
  border-color: rgba(34, 211, 238, .38); color: var(--ink);
}
#spectate-bar.hot #spectate-back {
  background: linear-gradient(180deg, #ff5c74, #e02748);
  border-color: #ff7f92; color: #fff; font-weight: 600;
}

/* 轮到你了 —— 这是全屏唯一该发光的地方 */
#spectate-turn {
  display: flex; align-items: baseline; gap: 3px; flex: none;
  padding: 2px 10px; border-left: 1px solid var(--line);
  color: var(--atk); font-size: var(--t-label); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
#spectate-turn .sp-left { font-size: var(--t-major); font-weight: 700; }
#spectate-turn .sp-unit { font-size: var(--t-label); }
#spectate-bar.hot {
  border-color: rgba(255, 61, 90, .6);
  box-shadow: 0 0 0 1px rgba(255, 61, 90, .18), 0 0 22px rgba(255, 61, 90, .3);
  animation: spectateNudge 1.6s var(--ease) infinite;
}
@keyframes spectateNudge {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255, 61, 90, .18), 0 0 14px rgba(255, 61, 90, .22); }
  50%      { box-shadow: 0 0 0 1px rgba(255, 61, 90, .3),  0 0 26px rgba(255, 61, 90, .42); }
}
@media (prefers-reduced-motion: reduce) {
  #spectate-bar.hot { animation: none; }
  #spectate-pane { animation: none; }
}

@media (max-width: 700px) {
  /* 窄屏:横幅铺满一行,名字自己省略号收口,「回我这桌」永远看得见 */
  #spectate-bar { left: 8px; right: 8px; width: auto; max-width: none; }
  #spectate-who { flex: 1 1 auto; min-width: 0; }
  #spectate-back { flex: none; }
  /* 轮到你了的那一刻,375px 上塞不下「谁 vs 谁 + 倒计时 + 回我这桌」三样。
     牺牲名字:上下两条 HUD 本来就写着这一桌是谁跟谁,而倒计时和那个按钮
     是**要动手的东西**,一个字都不能省(基调 §2 铁律三:层级靠取舍,
     不是靠把三样都塞进去挤成「朋…」)。 */
  #spectate-bar.hot .sp-vs, #spectate-bar.hot #spectate-who .sp-over {
    display: none;
  }
  /* 面板落在观战横幅上面,两块不叠 */
  #spectate-pane { bottom: calc(100px + env(safe-area-inset-bottom)); }
}

/* ═════════════════ 感知性能:骨架占位 / 乐观反馈 / 触屏命中区 ═════════════════ */
/* (UX/Interaction 层:以下全部只动 transform / opacity / filter,不碰布局属性;
   动画都能走合成器,不制造强制同步布局) */

/* ── 卡图骨架:卡框+名字文字先出,图 download+decode 完淡入盖上。
   卡容器尺寸由 --cw/--ch 定死,图什么时候到、到不到,布局都不动 ── */
.card { isolation: isolate; }   /* 卡内自成层叠上下文:骨架的负 z 只垫在图下,不掉到卡背后 */
.card img { transition: opacity .28s ease; }
.card img.ld { opacity: 0; }
.card .fallback.skel { position: absolute; inset: 0; z-index: -1; overflow: hidden; }
.card.loading .fallback.skel::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: -70%; width: 65%;
  background: linear-gradient(105deg, transparent, rgba(160, 190, 230, .1), transparent);
  animation: shimmer 1.15s ease-in-out infinite;
}
@keyframes shimmer { to { transform: translateX(260%); } }

/* 详情浮层的卡图:按卡面实测比例(1061×1483)预留高度,图后到不跳版。
   窄屏媒体查询里 62×62 的定宽高写法优先级不受影响(两维都定死时比例失效)。 */
#detail img {
  aspect-ratio: 1061 / 1483;
  object-fit: cover;
  background: linear-gradient(160deg, #1d2637, #10151f);
}

/* ── 乐观反馈:点击瞬间的「受理中」画面,回包对账重画后自然消失(prep.js) ── */
/* 原地按下 + 呼吸变暗:进化/升级/器灵切换/合成/席位/开包按钮 */
.op-wait {
  pointer-events: none;
  transform: scale(.97);
  filter: brightness(.82);
  transition: transform .12s var(--ease), filter .12s ease;
  animation: opWait .7s ease-in-out .15s infinite alternate;
}
@keyframes opWait { to { opacity: .55; } }

/* 目标卡「飞出」:买入/弃牌的一瞬卡先变暗缩小离场,落定交给重画 */
.op-out { pointer-events: none; }
.op-out .card {
  transform: translateY(12px) scale(.86);
  opacity: .25;
  transition: transform .3s var(--ease), opacity .3s ease;
}
.op-out .price, .op-out .count, .op-out .cap, .op-out .ops, .op-out .look {
  opacity: .25; transition: opacity .3s ease;
}

/* 开包中:上一包的货整体退灰,表示"正在被替换" */
.op-stale { pointer-events: none; }
.op-stale .pick {
  opacity: .35; filter: saturate(.5);
  transition: opacity .2s ease, filter .2s ease;
}
.op-stale .empty { opacity: .4; transition: opacity .2s ease; }

/* ── 按压态:按下的那一帧(纯 CSS,0ms)就有形变,不等 JS 更不等网络 ── */
.card:active { transform: translateY(-1px) scale(.965); transition-duration: .06s; }
.pick:not(.poor) .card:active {
  transform: translateY(-2px) scale(.965);
  box-shadow: 0 0 0 2px var(--gold), 0 0 16px rgba(245, 197, 66, .4);
}
.pick.poor .card:active { transform: none; }
button.op:active:not(:disabled) { transform: translateY(1px) scale(.94); }
.t-spirit:active:not(:disabled) { transform: scale(.95); }

/* ── 触屏命中区:小操作钮视觉不动,命中区用伪元素外扩到 ≥40px(不占布局)。
   横向只扩 2px:.ops 一排按钮间距 4px,再宽就会互相抢点 ── */
button.op, .t-spirit, .shopbar button.primary, .fuse-bar button.primary { position: relative; }
button.op::after, .t-spirit::after { content: ""; position: absolute; inset: -10px -2px; }
.shopbar button.primary::after, .fuse-bar button.primary::after {
  content: ""; position: absolute; inset: -5px -2px;
}
.t-fuse { min-height: 40px; }
.t-fuse input { width: 16px; height: 16px; accent-color: var(--gold); }
.fuse-bar .fuse-target { padding-top: 8px; padding-bottom: 8px; }
