/* ============================================================
   发条星球  —  样式表
   军事仪表盘质感：深冷黑基底 + 霓虹强调 + 切角与角标
   桌面 / 移动端自适应

   【设计令牌】
   颜色一律写成「RGB 通道 + 变量」两段式：
     --cyan-rgb: 78, 226, 255;   通道
     --cyan:     rgb(var(--cyan-rgb));   实体色
     rgba(var(--cyan-rgb), .3)           任意透明度
   这样只需改一处通道值，全站强调色一起换；也不会出现
   "某个控件写死了旧色、改主题时它不变" 的漏网。
   ============================================================ */

:root {
  /* ---- 基底：比原来更深的冷黑，霓虹才有地方"发光" ---- */
  --bg:        #05070f;
  --bg-2:      #0b1120;
  --bg-3:      #141d36;

  /* ---- 表面：三层递进，面板之间才有前后关系 ---- */
  --sunk:      rgba(4, 7, 16, 0.80);     /* 凹槽：血条内槽、卡槽 */
  --surface:   rgba(13, 20, 39, 0.84);   /* 常规面板 */
  --surface-2: rgba(24, 35, 62, 0.88);   /* 抬升：选中态、弹窗 */
  --glassy:    rgba(10, 17, 34, 0.52);   /* 半透明衬底 */
  --panel:     var(--surface);           /* 旧名兼容 */
  --panel-brd: var(--line);              /* 旧名兼容 */

  /* ---- 描边：HUD 的"线"必须看得见，否则整片糊成一团 ---- */
  --line-rgb:  122, 176, 255;
  --line:      rgba(var(--line-rgb), 0.20);
  --line-soft: rgba(var(--line-rgb), 0.09);
  --line-hot:  rgba(94, 226, 255, 0.55);

  /* ---- 强调色（三主色：青=信息/玩家 · 紫=成长 · 品红=危险/传说）---- */
  --cyan-rgb:   78, 226, 255;
  --violet-rgb: 150, 122, 255;
  --pink-rgb:   255, 95, 162;
  --ok-rgb:     62, 226, 140;
  --warn-rgb:   255, 194, 71;
  --danger-rgb: 255, 92, 122;
  --cyan:   rgb(var(--cyan-rgb));
  --violet: rgb(var(--violet-rgb));
  --pink:   rgb(var(--pink-rgb));
  --green:  rgb(var(--ok-rgb));
  --amber:  rgb(var(--warn-rgb));
  --red:    rgb(var(--danger-rgb));

  --text:      #e8f1ff;
  --text-dim:  #93a6c8;
  --text-mute: #5e6f8e;

  /* ---- 字体：数字与标签走等宽，是"HUD 味"的一大半来源 ---- */
  --font-ui: system-ui, -apple-system, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* ---- 形状：锐一点。圆角过大会把"仪表盘"读成"网页卡片" ---- */
  --r-lg: 14px;
  --r-md: 10px;
  --r-sm: 6px;
  --bevel: 12px;          /* 切角尺寸 */
  --bevel-s: 7px;         /* 小控件切角 */
  /* 切角遮罩：右上 + 左下各切一刀，一眼就是"装备面板" */
  --cut-lg: polygon(var(--bevel) 0, 100% 0, 100% calc(100% - var(--bevel)), calc(100% - var(--bevel)) 100%, 0 100%, 0 var(--bevel));
  --cut-sm: polygon(var(--bevel-s) 0, 100% 0, 100% calc(100% - var(--bevel-s)), calc(100% - var(--bevel-s)) 100%, 0 100%, 0 var(--bevel-s));

  --shadow:    0 16px 46px rgba(0, 0, 0, 0.66);
  --shadow-sm: 0 6px 20px rgba(0, 0, 0, 0.46);
  /* 面板内壁的一条上缘高光：塑料感 → 金属感 */
  --edge-hi: inset 0 1px 0 rgba(190, 225, 255, 0.10);

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* ============================================================
     间距尺度（2026-10-16）
     ------------------------------------------------------------
     改前：padding 用过 17 种取值（1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,20,26），
     gap 用过 11 种。**1px 与 2px 的差别肉眼读不出来**，但它会让每块面板的
     呼吸各不相同 —— 那正是"说不上哪里别扭"的来源。
     现在收进一套 8 级尺度（4px 基准，含半级）。新写样式一律从这里取；
     旧的散值按就近原则归位（只动间距，不动布局意图）。
     ------------------------------------------------------------ */
  --s1: 2px;    /* 紧贴的两行文字之间 / 图标与字号的关系 */
  --s2: 4px;    /* 同类元素的最小间隙 */
  --s3: 6px;    /* 列表项之间 */
  --s4: 8px;    /* 控件内部 */
  --s5: 10px;   /* 卡片内部 / 成组控件之间 */
  --s6: 13px;   /* 卡片之间 */
  --s7: 18px;   /* 区块之间 */
  --s8: 26px;   /* 面板内边距 / 主区块分隔 */

  /* ============================================================
     动效曲线（2026-10-16）
     ------------------------------------------------------------
     改前全站 19 个 @keyframes 里，绝大多数是"脉冲 / 闪烁 / 扫光"——
     那是**警戒语言**，用来喊"注意这里"。但界面里有一半的时刻不是警告，
     而是"好消息"（解锁、升级、结算）。全是脉冲的结果是：好消息读起来
     也像警报，情绪上永远是绷着的。
     这里补三条曲线，把"好消息"和"警告"分开：
       --ez-out   减速收尾：东西**进场**（从无到有，要停得稳）
       --ez-soft  极缓的进出：**呼吸**（可无限循环，看不出起止）
       --ez-pop   过冲回弹：**强调**（点下去、拿到手，要有一点弹性）

     **分界线（重要，别一刀切统一）**：
       · 用新曲线：进场（rise / fadeIn / upIn）、主按钮扫光、托盘呼吸、
         卡片与胶囊的落位 —— 这些是"呈现"，属于好消息那一侧。
       · **保持硬曲线**：HUD 危血脉冲、出界警告、BOSS 狂暴与预警、
         技能键催促（want/deny）—— 这些是**警告**，它们读起来绷着是对的。
         把它们也软化会让"我快死了"这件事变得含糊。
     ------------------------------------------------------------ */
  --ez-out: cubic-bezier(0.16, 0.84, 0.28, 1);
  --ez-soft: cubic-bezier(0.45, 0.05, 0.55, 0.95);
  --ez-pop: cubic-bezier(0.2, 0.9, 0.3, 1.24);

  /* ============================================================
     暖色通道（2026-10-16）
     ------------------------------------------------------------
     为什么要有这一条：这套配色的每个颜色原本都是**图例**——
     青=玩家/信息、紫=成长、琥珀=警戒、品红=质变。它们为"说清信息"服务，
     没有一个是为"让人高兴"服务的。于是界面情绪从开始页到结算页永远是
     同一副"作战终端"的脸：冷、硬、绷着。

     这条通道**不替换**上面任何一色，只补一个缺口：**表达"好事"**。
     两条纪律：
       ① **不抢语义**。暖色只出现在"拿到好处"的时刻（解锁 / 奖励 / 结算 /
          主按钮），绝不用于血量、警戒、异常——那些已经有主了。
       ② **换语言，不只换色**。冷色通道的语言是**锐利**（切角、细亮边、
         硬阴影、等宽数字）；暖色通道的语言是**柔**（大圆角、弥散的光晕、
         无边框的渐变）。所以"界面变柔"靠的是形状与光，不只是色相。
          光换色不改形状，读起来只是"换了套皮肤"，还是硬的。
     ------------------------------------------------------------ */
  --warm-rgb:     255, 152, 108;   /* 主暖：日落橘。与 --warn（金黄）刻意拉开——
                                      金黄读作"注意"，橘读作"温暖"，两者不能混 */
  --blush-rgb:    255, 176, 196;   /* 副暖：暖粉。只做渐变末端与柔光，不单独当主色 */
  --ember-rgb:    255, 118, 88;    /* 深暖：用在需要一点重量的暖色文字/描边上 */
  --warm:   rgb(var(--warm-rgb));
  --blush:  rgb(var(--blush-rgb));
  --ember:  rgb(var(--ember-rgb));
  /* 暖色的三段渐变：标题、主按钮、奖励徽章共用，读起来才是同一套语言 */
  --warm-grad: linear-gradient(100deg, rgb(var(--warm-rgb)), rgb(var(--blush-rgb)) 62%, #ffd9c2);
  /* 暖光晕：几乎所有暖色元素共用的"发光"写法（弥散、无边界） */
  --warm-halo: 0 0 34px -6px rgba(var(--warm-rgb), 0.55);
}

/* ---------------------------------------------------------------
   通用：角标（HUD 的"锁定框"）
   用一个伪元素铺 8 条渐变，画出四角的 L 形括号。
   比 clip-path 好在：不裁掉 box-shadow，面板的外发光还在。
   --------------------------------------------------------------- */
.tech-corners,
.sheet, .boss-bar, .hangar, .pause-kit { position: relative; }
.tech-corners::before,
.sheet::before, .boss-bar::before, .hangar::before, .pause-kit::before {
  content: '';
  position: absolute;
  inset: 4px;
  pointer-events: none;
  z-index: 3;
  --cc: var(--corner-color, rgba(var(--line-rgb), 0.55));
  background:
    linear-gradient(var(--cc), var(--cc)) 0 0 / 9px 1.5px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 0 0 / 1.5px 9px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 100% 0 / 9px 1.5px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 100% 0 / 1.5px 9px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 0 100% / 9px 1.5px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 0 100% / 1.5px 9px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 100% 100% / 9px 1.5px no-repeat,
    linear-gradient(var(--cc), var(--cc)) 100% 100% / 1.5px 9px no-repeat;
}

/* 图标是内联矢量：基线对齐要显式给，否则行内会多出一条下沉空隙 */
.ico-svg { flex: none; vertical-align: -0.14em; }

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
  overscroll-behavior: none;
}

#app { position: fixed; inset: 0; }

#game {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* ============================ HUD ============================ */

.hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 10;
  padding: calc(10px + var(--safe-t)) calc(10px + var(--safe-r)) calc(10px + var(--safe-b)) calc(10px + var(--safe-l));
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transition: opacity 0.28s ease;
}

/* 开始页 / 结算页隐藏 HUD */
.hud.hide { opacity: 0; }

.hud-top { display: flex; gap: 10px; align-items: flex-start; }
.hud-bottom { display: flex; gap: 10px; align-items: flex-end; }

.panel {
  background: linear-gradient(180deg, var(--surface-2), var(--surface) 62%);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: var(--shadow-sm), var(--edge-hi);
}

/* --- 生命 / 经验 --- */
.panel-life {
  width: min(38vw, 250px);
  padding: 9px 11px 10px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.bar {
  position: relative;
  height: 15px;
  border-radius: 5px;
  background: var(--sunk);
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--line-soft), inset 0 2px 6px rgba(0, 0, 0, 0.6);
}
/* 刻度尺：每 10% 一道暗刻线，血条从"进度条"变成"仪表" */
.bar::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(90deg,
    rgba(2, 5, 12, 0.55) 0 1px, transparent 1px 10%);
}
.bar i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  border-radius: 5px;
  transition: width 0.14s linear;
}
/* 前进端的高光扫边：血量变化时看得出"往哪边在动" */
.bar i::after {
  content: '';
  position: absolute;
  right: 0; top: 0; bottom: 0;
  width: 8px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.75));
  filter: blur(1px);
}
.bar-hp i { background: linear-gradient(90deg, rgb(var(--ok-rgb)), var(--green) 60%, #a3e635); }
/* 低血量：斜纹危险带 + 缓移，比单纯变红更能拉住注意力 */
.bar-hp.low i {
  background-image:
    repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.30) 0 5px, transparent 5px 11px),
    linear-gradient(90deg, var(--red), var(--amber) 72%);
  background-size: 22px 100%, 100% 100%;
  animation: hazardSlide 0.85s linear infinite;
}
@keyframes hazardSlide { to { background-position: 22px 0, 0 0; } }
.bar-hp.crit i { animation: pulseHp 0.6s ease-in-out infinite; }
@keyframes pulseHp { 50% { filter: brightness(1.7); } }
.bar-xp { height: 9px; }
.bar-xp i { background: linear-gradient(90deg, var(--cyan), var(--violet) 60%, rgb(var(--pink-rgb))); }

/* 堡垒「移动堡垒」建筑进度条（2026-10-04）：琥珀色，明显区别于血条/经验条 ——
   它是**局内第二条成长轴**（每 8 张升级卡 1 级），必须一眼读成"在建东西"。
   pop：升级那一刻整条闪一下（由 hud.js 的 fortPopT 挂/摘类）。 */
.bar-fort { height: 8px; margin-top: 4px; }
.bar-fort i { background: linear-gradient(90deg, #b45309, var(--amber) 55%, #fde68a); }
.bar-fort span { font-size: 9px; color: var(--amber); text-shadow: 0 1px 3px #000; }
.bar-fort.pop { animation: fortPop 0.7s ease-out; }
@keyframes fortPop {
  0% { filter: brightness(2.1) saturate(1.4); transform: scaleX(1.03); }
  100% { filter: none; transform: none; }
}
.bar-fort.hide { display: none; }

/* 开局护罩（2026-10-20）：与 fortBar 同一套体量，颜色走**紫**——
   它不是成绩（暖色），也不是危险（红）；它是一条"正在倒数的宽限"，
   与电离环同一个色族，玩家会把"紫色 = 环境规则"连起来。 */
.bar-guard { height: 8px; margin-top: 4px; }
.bar-guard i { background: linear-gradient(90deg, #6d28d9, #a78bfa 60%, #ddd6fe); }
.bar-guard span { font-size: 9px; color: #c4b5fd; text-shadow: 0 1px 3px #000; }
.bar-guard.hide { display: none; }

.bar span {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.4px;
  color: #04101f;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.28);
}
.bar-xp span { font-size: 9px; color: var(--text); text-shadow: 0 1px 3px #000; }

/* --- 统计 --- */
.panel-stats {
  display: flex;
  justify-content: space-around;
  gap: clamp(16px, 4vw, 54px);
  padding: 8px 20px;
  min-width: 250px;
  margin: 0 auto;
}
.stat { text-align: center; min-width: 0; }
.stat b {
  display: block;
  font-family: var(--font-mono);
  font-size: 18px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  line-height: 1.15;
  color: var(--text);
  text-shadow: 0 0 14px rgba(var(--cyan-rgb), 0.22);
}
.stat em {
  font-style: normal;
  font-size: 9.5px;
  letter-spacing: 0.16em;
  color: var(--text-mute);
}

.hud-btns { display: flex; gap: var(--s4); }
/* 顶部按钮 = 设备键：切角 + 金属渐变 + 图标居中 */
.icon-btn {
  pointer-events: auto;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  color: var(--cyan);
  clip-path: var(--cut-sm);
  box-shadow: var(--edge-hi);
  cursor: pointer;
  transition: transform 0.12s, color 0.12s, background 0.12s;
}
.icon-btn:hover { color: var(--text); background: linear-gradient(180deg, var(--bg-3), var(--surface-2)); }
.icon-btn:active { transform: scale(0.92); }
.icon-btn.off { opacity: 0.45; }

/* --- 武器 --- */
.panel-weapon {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px 8px 10px;
  max-width: 46vw;
}
.wpn-icon {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  color: var(--cyan);
  background: linear-gradient(145deg, rgba(var(--cyan-rgb), 0.2), rgba(var(--violet-rgb), 0.2));
  border: 1px solid rgba(var(--line-rgb), 0.2);
  flex: none;
}
.wpn-meta { min-width: 0; }
.wpn-meta b {
  display: block;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wpn-meta em { font-style: normal; font-size: 10px; color: var(--cyan); }

/* --- buff 图标 --- */
.buffs { display: flex; gap: 6px; flex-wrap: wrap; flex: 1; align-self: flex-end; }
.buff {
  width: 38px; height: 38px;
  border-radius: var(--r-sm);
  display: grid; place-items: center;
  color: var(--cyan);
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  border: 1px solid var(--line);
  position: relative;
  overflow: hidden;
  box-shadow: var(--edge-hi), var(--shadow-sm);
}
.buff i {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  background: rgba(var(--cyan-rgb), 0.35);
  transition: height 0.1s linear;
}

/* --- 出环警告（电离安全区） ---
   出界是**持续状态**，可原来只有一条 2 秒就消失的 toast + 一圈通用边缘脉冲：
   玩家读到的是"刚才好像闪了一下"，而不是"我正在掉血、该往哪跑"。
   这条 chip 在 `arenaOut > 0` 期间**常驻**，把"正在受罚"变成看得见的事实。
   与 buff 同排、同尺寸语言，但用紫色 + 脉冲边框表明它是"警告"不是"增益"。 */
.arena-warn {
  display: none;
  align-self: flex-end;
  height: 38px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  align-items: center;
  gap: 7px;
  color: #e9d5ff;
  background: linear-gradient(180deg, rgba(76,29,149,0.55), rgba(30,17,56,0.72));
  border: 1px solid rgba(167,139,250,0.62);
  box-shadow: var(--shadow-sm), 0 0 14px rgba(167,139,250,0.30);
  animation: arenaWarnPulse 0.72s ease-in-out infinite;
  white-space: nowrap;
}
.arena-warn.show { display: flex; }
.arena-warn b { font-size: 12px; font-weight: 700; letter-spacing: 0.02em; }
.arena-warn em {
  font-style: normal; font-size: 11px; font-weight: 600;
  color: #fca5a5; font-variant-numeric: tabular-nums;
}
@keyframes arenaWarnPulse {
  0%, 100% { box-shadow: var(--shadow-sm), 0 0 10px rgba(167,139,250,0.22); }
  50%      { box-shadow: var(--shadow-sm), 0 0 20px rgba(167,139,250,0.55); }
}
/* 脉冲边框由全局的 prefers-reduced-motion 兜底（见文件尾那条），不另写一份 */

/* --- 冲刺冷却 --- */
.dash-wrap {
  margin-left: auto;
  /* 两个指示器并排：冲刺（圆，常用）在右、冲击波（方，救场）在左 */
  display: flex; align-items: center; gap: 10px;
}
.dash-ring {
  position: relative;
  width: 62px; height: 62px;
  border-radius: 50%;
  background: var(--panel);
  border: 1px solid var(--panel-brd);
  display: grid; place-items: center;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.dash-ring span {
  font-size: 11px; font-weight: 700; color: var(--text);
  position: relative; z-index: 2;
  /* 冷却中这里显示剩余秒数，逐帧换数字 —— 不等宽的话整串会左右抖 */
  font-variant-numeric: tabular-nums;
}
.dash-cd {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 100%;
  background: rgba(4, 7, 16, 0.82);
  transition: height 0.08s linear;
}
.dash-ring.ready { border-color: rgba(var(--cyan-rgb), 0.6); box-shadow: 0 0 16px rgba(var(--cyan-rgb), 0.32); }
/* 就绪脉冲：HUD 侧补的"事件"（机体外圈那个充能环负责余光可见）。
   用外发光而不是 transform 扩散 —— 这个圆是 overflow:hidden 的，
   缩放出来的光环会被自己裁掉。 */
.dash-ring.pulse { animation: dashPulse 0.45s ease-out; }
@keyframes dashPulse {
  0%   { border-color: rgba(var(--cyan-rgb), 0.95); box-shadow: 0 0 0 0 rgba(var(--cyan-rgb), 0.5), var(--shadow); }
  100% { border-color: rgba(var(--cyan-rgb), 0.6);  box-shadow: 0 0 22px rgba(var(--cyan-rgb), 0.5), var(--shadow); }
}
/* 环上刻度：每 30° 一道短刻线（conic 铺满后用径向 mask 抠成环带） */
.dash-ring::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  background: repeating-conic-gradient(from -90deg,
    rgba(var(--line-rgb), 0.55) 0 1.2deg, transparent 1.2deg 30deg);
  -webkit-mask: radial-gradient(circle, transparent 58%, #000 60% 68%, transparent 70%);
  mask: radial-gradient(circle, transparent 58%, #000 60% 68%, transparent 70%);
  opacity: 0.8;
}

/* --- 冲击波冷却（唯一的主动技能；与冲刺环同一套语言，只换形状与色系） ---
   方形而不是第二个圆：两个同样大小的圆并排时，"哪个是哪个"要靠读文字。 */
.blast-ring {
  position: relative;
  width: 62px; height: 62px;
  border-radius: 18px;
  background: var(--panel);
  border: 1px solid var(--panel-brd);
  display: grid; place-items: center;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.blast-ring span {
  font-size: 11px; font-weight: 700; color: var(--text);
  position: relative; z-index: 2;
  /* 冷却中换剩余秒数，逐帧换数字 —— 不等宽的话整串会左右抖 */
  font-variant-numeric: tabular-nums;
}
.blast-cd {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 100%;
  background: rgba(4, 7, 16, 0.82);
  transition: height 0.08s linear;
}
.blast-ring.ready { border-color: rgba(var(--violet-rgb), 0.62); box-shadow: 0 0 16px rgba(var(--violet-rgb), 0.34); }
/* 就绪脉冲：与冲刺环同一手法（overflow:hidden 里 transform 会被裁掉，用外发光） */
.blast-ring.pulse { animation: blastPulse 0.5s ease-out; }
@keyframes blastPulse {
  0%   { border-color: rgba(var(--violet-rgb), 0.95); box-shadow: 0 0 0 0 rgba(var(--violet-rgb), 0.5), var(--shadow); }
  100% { border-color: rgba(var(--violet-rgb), 0.62); box-shadow: 0 0 22px rgba(var(--violet-rgb), 0.5), var(--shadow); }
}
/* "该按了"：被围住时这一格转成呼吸态 —— 是**局势**在喊，不是冷却在喊。
   冷却读数回答"能不能按"（连续量，看这里）；"该不该按"由敌人回答
   （机体外圈那圈只在被围时出现的紫环 + 这里的呼吸）。 */
.blast-ring.want { animation: blastWant 1.1s ease-in-out infinite; }
@keyframes blastWant {
  0%, 100% { border-color: rgba(var(--violet-rgb), 0.62); box-shadow: 0 0 14px rgba(var(--violet-rgb), 0.26), var(--shadow); }
  50%      { border-color: rgba(var(--violet-rgb), 0.98); box-shadow: 0 0 24px rgba(var(--violet-rgb), 0.62), var(--shadow); }
}
/* 冷却中按下：抖一下 + 转警示色。机体上另有一圈"扩散到一半又收回去"的红环，
   两处合起来回答"我按了、被拒了、是哪个键" —— 静默失败会被读成游戏坏了。 */
.blast-ring.deny { animation: blastDeny 0.26s ease-out; }
@keyframes blastDeny {
  0%   { transform: translateX(0);    border-color: rgba(var(--danger-rgb), 0.92); }
  25%  { transform: translateX(-3px); }
  60%  { transform: translateX(3px); }
  100% { transform: translateX(0);    border-color: rgba(var(--panel-brd)); }
}

/* --- 飘字提示 --- */
.toast {
  position: absolute;
  top: 26%;
  left: 50%;
  transform: translate(-50%, -12px);
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s5) 20px;
  /* 形状从"警告条"（小圆角 + 硬边）放宽成胶囊（2026-10-16）：
     toast 里两种消息混着 —— 有"跟上安全区"这种提醒，也有"解锁""签下契约"
     这种好消息，而**形状是两者共用的**（颜色由 JS 按消息给）。
     胶囊形 + 弥散投影对两种消息都成立：好消息读起来柔和，提醒读起来也不至于
     像报错。真正要"硬"的警告另有地方（出界条、BOSS 血条），不靠 toast 承担。 */
  border-radius: 999px;
  background: rgba(var(--danger-rgb), 0.14);
  border: 1px solid rgba(var(--danger-rgb), 0.5);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #ffe4e6;
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  box-shadow: 0 12px 34px -10px rgba(0, 0, 0, 0.62);
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity 0.25s var(--ez-out), transform 0.3s var(--ez-pop);
}
/* 入场带一点回弹（--ez-pop）：提示条"弹"出来比"淡"出来更容易被余光抓到，
   而战斗中的玩家九成时间不会直视屏幕上方。 */
.toast.show { opacity: 1; transform: translate(-50%, 0) scale(1); }

/* --- 成就解锁横幅（2026-10-19） ---
   与 toast 分开的两条理由：
     ① **位置**：toast 在 26% 高度（读场面的余光区），成就放在顶部 8% ——
        它是"这一秒你做成了一件事"，要有自己的地盘，不能被战斗提示淹没。
     ② **形状**：toast 是胶囊（一句话），成就是**徽章 + 两行字**（一件东西）。
   配色不写死：`--ach-color` 由 JS 按档位（铜/银/金）注入。
   动效走"好消息"那一条（--ez-pop 弹入 + 光晕呼吸），不是警戒脉冲 ——
   与 ui 的暖色通道同一条纪律：好消息不该长得像警告。 */
.ach-toast {
  position: absolute;
  /* 位置（2026-10-20）：改前在 top 8%，正好被 HUD 顶部条压住 ——
     那一带是血量/经验/战区铭牌三块面板，玩家在战斗中永远看不到横幅的头。
     现在落在 **HUD 下沿之下、屏幕上半部**（15%）：既在视线自然落点，
     又不会和 Boss 血条（视口 76~126px）打架。 */
  top: calc(15% + var(--safe-t));
  left: 50%;
  transform: translate(-50%, -14px) scale(0.92);
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s4) var(--s7) var(--s4) var(--s4);
  border-radius: var(--r-lg);
  background: linear-gradient(100deg,
    rgba(20, 26, 44, 0.94),
    color-mix(in srgb, var(--ach-color, #f7c948) 16%, rgba(20, 26, 44, 0.94)));
  border: 1px solid color-mix(in srgb, var(--ach-color, #f7c948) 55%, transparent);
  box-shadow: 0 14px 40px -12px rgba(0, 0, 0, 0.7),
              0 0 30px -8px var(--ach-glow, rgba(247, 201, 72, 0.5));
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity 0.28s var(--ez-out), transform 0.34s var(--ez-pop);
}
/* 入场（2026-10-20）：从"淡入"改成**弹跳 + 光晕闪一下**。
   改前只是 opacity + translate，玩家在战斗里几乎注意不到；
   而"你做成了一件事"值得一次明确的出场。弹跳用 --ez-pop（超调），
   光晕则在 0.5 秒里从 2 倍收到 1 倍 —— 光的扩散比位移更容易被余光抓到。 */
.ach-toast.show { opacity: 1; transform: translate(-50%, 0) scale(1); animation: achPop 0.55s var(--ez-pop) both; }
@keyframes achPop {
  0%   { opacity: 0; transform: translate(-50%, -18px) scale(0.80); }
  62%  { opacity: 1; transform: translate(-50%, 2px) scale(1.06); }
  100% { opacity: 1; transform: translate(-50%, 0) scale(1); }
}
/* 徽章：一枚圆底 + 图标。圆底是"奖章"，不是按钮 —— 不切角（切角是控件语言） */
.ach-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 28%,
    color-mix(in srgb, var(--ach-color, #f7c948) 62%, #fff),
    var(--ach-color, #f7c948));
  box-shadow: inset 0 -2px 6px rgba(0, 0, 0, 0.28);
  color: #14182a;
}
.ach-ico .ico-svg { width: 21px; height: 21px; }
.ach-body { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.ach-body em {
  font-style: normal;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--ach-color, #f7c948);
  text-transform: uppercase;
}
.ach-body b { font-size: 16px; font-weight: 800; color: #f4f8ff; letter-spacing: 0.04em; }

/* --- 结算评级徽章（2026-10-19） ---
   与标题并排的一枚方章。**切角**（--cut-sm）而不是圆：这是"评级"，
   属于装备/仪表语言，不是奖章（奖章是 .ach-ico 的圆形）。
   颜色由 JS 按档位注入（S 金 / A 靛 / B 青 / C 灰）。 */
.over-rank-row { display: flex; align-items: center; gap: var(--s5); }
.over-rank {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 46px; height: 46px;
  font: 800 26px/1 var(--font-mono);
  color: #10141f;
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--rank-color, #67e8f9) 78%, #fff),
    var(--rank-color, #67e8f9));
  clip-path: var(--cut-sm);
  box-shadow: 0 8px 22px -8px var(--rank-color, #67e8f9);
}

/* --- 结算页的成就墙（2026-10-19） ---
   只列**本局新解锁**与**最近解锁**的几条：全部 24 条会把结算页变成表格。
   已解锁 = 实心徽章 + 名字；未解锁 = 虚线边 + 灰字 + 进度（"还差多少"）。
   进度条是这条墙上最要紧的一笔：没有它，玩家只知道"没拿到"，不知道"差多少"。 */
.ach-wall { display: none; flex-direction: column; gap: var(--s4); margin: var(--s6) 0 0; }
.ach-wall.show { display: flex; }
.ach-row {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s4) var(--s5);
  border-radius: var(--r-md);
  background: rgba(148, 163, 184, 0.07);
  border: 1px solid rgba(148, 163, 184, 0.14);
}
.ach-row.done {
  background: color-mix(in srgb, var(--ach-color, #f7c948) 12%, transparent);
  border-color: color-mix(in srgb, var(--ach-color, #f7c948) 42%, transparent);
}
.ach-row.locked { opacity: 0.62; }
.ach-row .ach-ico { width: 30px; height: 30px; }
.ach-row.locked .ach-ico {
  background: transparent;
  border: 1.5px dashed rgba(148, 163, 184, 0.5);
  color: rgba(148, 163, 184, 0.75);
  box-shadow: none;
}
.ach-row.locked .ach-ico .ico-svg { width: 16px; height: 16px; }
.ach-txt { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; flex: 1 1 auto; }
.ach-txt b { font-size: 13.5px; font-weight: 800; color: #eef4ff; }
.ach-txt em { font-style: normal; font-size: 11.5px; color: var(--text-dim); }
/* 进度：一条细槽 + 已填部分。只在未解锁时出现。 */
.ach-bar { height: 4px; border-radius: 999px; background: rgba(148, 163, 184, 0.18); overflow: hidden; }
.ach-bar i { display: block; height: 100%; border-radius: 999px; background: var(--ach-color, #f7c948); }
/* --- 成就整页（2026-10-20） ---
   24 条排成两列网格（窄屏一列）。每条一行：徽章 / 名字+条件 / 进度。
   为什么是网格而不是长列表：24 条一列排下来要滚两屏，玩家永远看不到后面；
   两列之后一屏基本装得下，**"一共有多少、我拿到几个"** 这件事一眼成立。 */
.sheet-ach { width: min(760px, 100%); }
.ach-page-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s5); }
.ach-page-head em {
  font: 800 13px var(--font-mono); font-style: normal;
  color: var(--warm); font-variant-numeric: tabular-nums;
}
.ach-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4);
  margin: var(--s5) 0 var(--s7);
}
@media (max-width: 640px) { .ach-grid { grid-template-columns: 1fr; } }
/* 组标题：跨两列，横贯整行 —— 圆点 + 档位名 + 该档计数。
   线上那个圆点是档位色本身，所以"铜/银/金"不只靠文字分辨。 */
.ach-group {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: var(--s4);
  margin: var(--s4) 0 calc(var(--s2) * -1);
  font-size: 11.5px; letter-spacing: .16em; font-weight: 800;
  color: var(--text-dim); text-transform: uppercase;
}
.ach-group i {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--ach-color);
  box-shadow: 0 0 10px -1px var(--ach-color);
}
.ach-group b { color: var(--text); }
.ach-group em {
  font: 800 11.5px var(--font-mono); font-style: normal;
  color: var(--ach-color); font-variant-numeric: tabular-nums;
}
.ach-group::after {
  content: ''; flex: 1 1 auto; height: 1px;
  background: linear-gradient(90deg, rgba(148,163,184,.28), transparent);
}

.ach-open {
  display: inline-flex; align-items: center; gap: var(--s3);
  margin-top: var(--s5);
}
.ach-open b { font: 800 13px var(--font-mono); color: var(--warm); }

.ach-pct {
  flex: 0 0 auto;
  font: 800 12px var(--font-mono);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* ============================ 触屏层 ============================ */

.touch { position: absolute; inset: 0; z-index: 6; display: none; touch-action: none; }
body.is-touch .touch { display: block; }

.joy-base {
  position: absolute;
  width: 118px; height: 118px;
  margin: -59px 0 0 -59px;
  border-radius: 50%;
  border: 1.5px solid rgba(var(--line-rgb), 0.28);
  background: radial-gradient(circle, rgba(var(--cyan-rgb), 0.08), rgba(5, 8, 18, 0.28));
  opacity: 0;
  transition: opacity 0.14s;
  pointer-events: none;
}
.joy-base.on { opacity: 1; }
.joy-knob {
  position: absolute;
  left: 50%; top: 50%;
  width: 52px; height: 52px;
  margin: -26px 0 0 -26px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, rgba(var(--cyan-rgb), 0.85), rgba(99, 102, 241, 0.7));
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
}

.dash-btn {
  position: absolute;
  right: calc(22px + var(--safe-r));
  bottom: calc(96px + var(--safe-b));
  width: 84px; height: 84px;
  border-radius: 50%;
  border: 1.5px solid rgba(var(--violet-rgb), 0.55);
  background: rgba(var(--violet-rgb), 0.18);
  color: var(--text);
  font-size: 15px;
  font-weight: 700;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  touch-action: none;
}
.dash-btn:active { background: rgba(var(--violet-rgb), 0.42); transform: scale(0.94); }
/* 冷却中：按钮整体压暗并报剩余秒数（文案由 hud.js 写入）。
   触屏没有键盘的"按了没反应"的困惑，所以这里的降级态就是全部的反馈。 */
.dash-btn.cooling {
  border-color: rgba(var(--violet-rgb), 0.24);
  background: rgba(var(--violet-rgb), 0.07);
  color: rgba(226, 232, 240, 0.45);
  font-variant-numeric: tabular-nums;
}

/* 触屏的冲击波键：方形（与圆形冲刺键一眼可分），摆在冲刺键左边。
   两个中心对齐在 same y —— 右手拇指从一个滑到另一个不用重新找高度。 */
.blast-btn {
  position: absolute;
  right: calc(118px + var(--safe-r));
  bottom: calc(102px + var(--safe-b));
  width: 72px; height: 72px;
  border-radius: 22px;
  border: 1.5px solid rgba(var(--violet-rgb), 0.55);
  background: rgba(var(--violet-rgb), 0.16);
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  touch-action: none;
}
.blast-btn:active { background: rgba(var(--violet-rgb), 0.42); transform: scale(0.94); }
.blast-btn.cooling {
  border-color: rgba(var(--violet-rgb), 0.22);
  background: rgba(var(--violet-rgb), 0.06);
  color: rgba(226, 232, 240, 0.42);
  font-variant-numeric: tabular-nums;
}
/* 被围住：与 HUD 那一格同一条信息 —— 触屏玩家的余光同样扫不到角落 */
.blast-btn.want { animation: blastWant 1.1s ease-in-out infinite; }
/* 冷却中按下：触屏没有"我按了没反应"的困惑（手指有按钮的触感），
   但补一次抖动仍然便宜，且让两端的规则一致 */
.blast-btn.deny { animation: blastDeny 0.26s ease-out; }

/* 堡垒部署（G）：只有堡垒机体才有这个动作，所以默认藏着，由 JS 按机体显隐。
   放在冲击波键**上方**一格 —— 三个键的拇指轨迹是"下→上"，最不常用的放最上。 */
.deploy-btn {
  position: absolute;
  right: calc(118px + var(--safe-r));
  bottom: calc(186px + var(--safe-b));
  width: 72px; height: 56px;
  border-radius: 18px;
  border: 1.5px solid rgba(var(--warn-rgb), 0.55);
  background: rgba(var(--warn-rgb), 0.14);
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  touch-action: none;
  display: none;
}
body.chassis-bastion .deploy-btn { display: block; }
.deploy-btn:active { background: rgba(var(--warn-rgb), 0.40); transform: scale(0.94); }
/* 已部署：按钮常亮，读作"我现在是站着的" —— 部署态锁移动，
   玩家必须随时知道自己在哪个姿态（配合 Sound.deploy 的一对音效）。 */
.deploy-btn.on {
  border-color: rgb(var(--warn-rgb));
  background: rgba(var(--warn-rgb), 0.30);
  box-shadow: 0 0 18px -4px rgba(var(--warn-rgb), 0.8);
}

/* --- 部署态收益牌（2026-10-16）---
   玩家原话「看不懂部署后能干嘛」。部署换来四样东西，之前一样都没写在界面上。
   放在 HUD 底栏（buff 排与冲刺环之间），锚定期间才出现：
   收起时它说的全是假话，留在屏上就是误导。
   用暖色（好消息通道）而不是青色 —— 这是"我现在换到了什么"，
   不是"出事了"；出界警示与危血脉冲都在附近，色温必须分开。 */
.deploy-panel {
  display: none;
  flex-direction: column;
  gap: 3px;
  padding: 6px 10px 7px;
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--warm-rgb), 0.38);
  background: linear-gradient(160deg, rgba(var(--warm-rgb), 0.16), rgba(12, 18, 34, 0.72));
  box-shadow: 0 8px 24px -10px rgba(var(--warm-rgb), 0.7);
  animation: dpIn 0.22s var(--ez-out) both;
}
.deploy-panel.on { display: flex; }
@keyframes dpIn { from { opacity: 0; transform: translateY(6px) scale(0.97); } }
.dp-head { display: flex; align-items: baseline; gap: 6px; }
.dp-head b {
  font-size: 11.5px; font-weight: 800; letter-spacing: 0.06em;
  color: rgb(var(--warm-rgb));
}
.dp-head em { font-size: 10px; font-style: normal; color: var(--text-mute); }
.dp-rows { display: flex; flex-wrap: wrap; gap: 3px 8px; }
.dp-row {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; color: var(--text-dim); white-space: nowrap;
}
.dp-row i { display: flex; color: rgb(var(--warm-rgb)); opacity: 0.92; }
.dp-row b { color: var(--text); font-weight: 700; }

/* ============================ 弹层 ============================ */

.overlay {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: none;
  /* 内容比视口高时，align-items:center 会让顶部溢出到滚动区之外（够不着标题）。
     改成 flex-start + .sheet 的 margin:auto：内容矮时照常居中，内容高时从顶部开始且能滚。 */
  align-items: flex-start;
  justify-content: center;
  padding: calc(16px + var(--safe-t)) 16px calc(16px + var(--safe-b));
  /* 底色里掺入一点暖（2026-10-16）：改前是纯冷黑，弹层与游戏内画面
     在色温上完全一致 —— 于是"回到营地"与"在战场上"读起来是同一个地方。
     加一点暖之后，弹层读作**有光的室内**，战场读作**外面的荒地**。 */
  background:
    radial-gradient(ellipse at 50% 30%, rgba(58, 34, 34, 0.42), transparent 62%),
    radial-gradient(ellipse at 50% 38%, rgba(16, 26, 54, 0.9), rgba(3, 5, 13, 0.97));
  backdrop-filter: blur(7px) saturate(1.15);
  -webkit-backdrop-filter: blur(7px) saturate(1.15);
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
}
.overlay.show { display: flex; animation: fadeIn 0.22s var(--ez-out) both; }
/* 背景网格：给"空"的地方一点结构，不然整屏只有一块模糊 */
.overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(var(--line-rgb), 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--line-rgb), 0.055) 1px, transparent 1px);
  background-size: 42px 42px, 42px 42px;
  mask-image: radial-gradient(ellipse at 50% 34%, #000 12%, transparent 76%);
  -webkit-mask-image: radial-gradient(ellipse at 50% 34%, #000 12%, transparent 76%);
}
@keyframes fadeIn { from { opacity: 0; } }

.sheet {
  position: relative;
  width: min(720px, 100%);
  margin: auto;                     /* 配合 .overlay 的 flex-start：矮内容居中、高内容可滚 */
  background: linear-gradient(168deg, rgba(26, 38, 68, 0.96), rgba(10, 16, 32, 0.97));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow), var(--edge-hi);
  padding: 26px 24px 24px;
  text-align: center;
  animation: rise 0.26s var(--ez-pop) both;
  z-index: 1;
}
.sheet-narrow {
  width: min(520px, 100%);
  /* 窄面板（暂停 / 结算）比开始页紧凑，逐项落位会显得拖 ——
     步进加大一点，让"整块一起出来"的感觉还在，但节奏更利落。 */
  --stagger-base: 0.05s;
}
/* 开始页：**首要任务是在一屏内按到「开始生存」**。
   改前整张卡高 1156px，"开始生存"落在 y≈1100 —— 1280×720 的窗口（视口高 633）
   要往下滚 500 多像素，第一次进来的人根本不知道下面有按钮。
   预算：视口 633 − overlay 上下 padding 32 − 卡内 padding 38 ≈ 560px 给内容，
   所以标题横排、机库收紧、三个开关并一行，13 项特性折进 <details> 默认收起。 */
.sheet-start { padding: 20px 22px 18px; }
@keyframes rise { from { opacity: 0; transform: translateY(16px) scale(0.97); } }
/* 逐项入场（2026-10-16）：改前整张卡一起淡入，读起来是"一张图出现了"；
   现在卡内的区块**依次**落位，读起来是"一件件摆好"。这是"有分量"的来源，
   也是本次唯一会明显改变第一眼印象的动效。
   用 animation-delay + backwards：延迟期间保持 from 态，不会先闪一下正确位置。 */
.sheet > * {
  animation: riseIn 0.34s var(--ez-out) backwards;
  /* 步进量由 --stagger 给（开始页 0.035s / 窄面板 0.05s），
     序号由 nth-child 递推 —— 不用给每块挂行内变量。 */
  --stagger: var(--stagger-base, 0.035s);
}
/* 五档足够：超过第 5 块时再往后拖会让"最后一块"落得太晚，读成卡顿。
   第 6 块之后统一用第 5 档（同时落位）—— 一大簇一起出现比逐个慢半拍更好看。 */
.sheet > :nth-child(1) { animation-delay: calc(var(--stagger) * 0); }
.sheet > :nth-child(2) { animation-delay: calc(var(--stagger) * 1); }
.sheet > :nth-child(3) { animation-delay: calc(var(--stagger) * 2); }
.sheet > :nth-child(4) { animation-delay: calc(var(--stagger) * 3); }
.sheet > :nth-child(n+5) { animation-delay: calc(var(--stagger) * 4); }
@keyframes riseIn { from { opacity: 0; transform: translateY(10px); } }
/* 系统开了「减弱动态效果」时全部关掉：这些动效都是装饰性的，
   拿掉不影响任何信息的传达（与游戏内屏幕震动那条同一条体面）。 */
@media (prefers-reduced-motion: reduce) {
  .sheet, .sheet > *, .card, .chk, .fact {
    animation: none !important;
    transition: none !important;
  }
}

/* 标题横排（徽标与文字并排）：竖向那一版光标题就占 142px，横排只要 60 出头。
   开始页是全站唯一需要"塞进一屏"的弹层，所以这里比别处更需要省高度。 */
.logo { display: flex; align-items: center; justify-content: center; gap: var(--s6); margin-bottom: var(--s6); text-align: left; }
.logo-text { min-width: 0; }
/* 徽标：圆形 + 暖色渐变 + 弥散光晕（2026-10-16）。
   它是整屏第一个被看到的东西，而切角 + 冷色是"装备面板"的语言（锐、专业、
   有距离）。圆形与柔和的光晕是另一套语言（近、友好、有温度）。
   ★ 2026-10-16 二改：加一圈**呼吸** —— 静止的圆读起来还是个"图形"，
   缓慢的缩放 + 光晕脉动才读得出"这台机器是活的、上着弦"。
   幅度刻意小（±2.5%）：开始页要的是"有生命"不是"在跳"。 */
.logo-badge {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  flex: none;
  border-radius: 50%;
  font-weight: 700;
  color: #2a1006;
  background: var(--warm-grad);
  box-shadow: 0 10px 32px -8px rgba(var(--warm-rgb), 0.75),
              inset 0 1px 0 rgba(255, 255, 255, 0.42);
  animation: badgeBreath 3.4s var(--ez-soft) infinite;
}
@keyframes badgeBreath {
  0%, 100% { transform: scale(1);        box-shadow: 0 10px 32px -8px rgba(var(--warm-rgb), 0.72), inset 0 1px 0 rgba(255, 255, 255, 0.42); }
  50%      { transform: scale(1.025);    box-shadow: 0 12px 38px -8px rgba(var(--warm-rgb), 0.95), inset 0 1px 0 rgba(255, 255, 255, 0.5); }
}
/* 徽标里的齿轮自己转：`cog` 这个图标是发条的全部画面。
   转得慢（14 秒一圈）—— 快一点就变成"加载中"的转圈图标了。 */
.logo-badge svg { animation: badgeSpin 14s linear infinite; }
@keyframes badgeSpin { to { transform: rotate(360deg); } }
.logo h1 {
  font-size: clamp(23px, 5vw, 32px);
  /* 标题的**字距**从 4px 收到 1px（2026-10-16）：大写字距是"标牌/铭牌"的语言，
     收窄之后读起来是"一块招牌上的三个字"，而不是"设备编号"。
     这是本页唯一一处纯靠排版就能把军用味换成玩具味的改动。 */
  letter-spacing: 1px;
  /* 标题渐变原本是青 → 淡紫 → 粉（冷色三段）。改成**暖色三段**：
     与徽标同源，整块标题区读起来是一个暖的整体，而不是"冷标题配暖徽标"。 */
  background: linear-gradient(96deg, rgb(var(--warm-rgb)) 8%, rgb(var(--blush-rgb)) 52%, #ffd9c2);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* 极淡的暖色投影：字从底色里"浮"起来一点点，不是靠描边硬撑 */
  filter: drop-shadow(0 2px 12px rgba(var(--warm-rgb), 0.28));
}
.tagline { color: var(--text-dim); font-size: 12px; margin-top: 3px; letter-spacing: 0.6px; }

/* --- 特色速览（6 枚胶囊） ---
   13 项特性折进 <details> 之后，新用户打开只看到标题、机库和一个按钮 ——
   他会以为这就是个"点开始打机器人"的极简小游戏，看不出内容量。
   这 6 枚常驻，一行放下（内容宽 676px 里约 600px），只花 30px 高度；
   要详细的人仍然展开下面的折叠器。 */
.facts {
  display: flex; flex-wrap: wrap; gap: var(--s3);
  justify-content: center;
  margin-bottom: var(--s6);
}
.fact {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 11px;
  border-radius: 999px;
  /* 特色胶囊也走暖色（2026-10-16）：它是"这游戏有什么好东西"的清单，
     属于好消息那一侧；而且它们是开始页面积最大的一片内容，
     冷色铺满时整屏会显得很"技术文档"。只淡化边框、保留冷色文字，
     让暖只出现在轮廓上，不抢图标颜色。 */
  border: 1px solid rgba(var(--warm-rgb), 0.2);
  background: rgba(var(--warm-rgb), 0.055);
  color: var(--text-dim);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.fact i { display: flex; color: var(--cyan); opacity: 0.9; }

.sheet-title {
  font-family: var(--font-ui);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  color: var(--text);
}
.sheet-title.danger { color: var(--red); }
.sheet-title span {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-indent: 0;
  color: var(--cyan);
  background: rgba(var(--cyan-rgb), 0.12);
  border: 1px solid rgba(var(--cyan-rgb), 0.45);
  border-radius: 4px;
  padding: 3px 9px;
  vertical-align: 2px;
  margin-left: 4px;
}
.sheet-sub { color: var(--text-dim); font-size: 12.5px; margin: var(--s4) 0 var(--s7); }

/* --- 开始页的「特性墙」折叠器 ---
   13 项特性铺开是 491px，是开始页最大的一块，但它答的是"这游戏有什么"，
   不是"要不要开始"。默认收起，把高度让给主按钮；想看的人点一下就有。
   用原生 <details>：零 JS，且"展开状态"由浏览器管，不需要同步开关变量。 */
.more { margin-top: var(--s6); padding-top: var(--s6); border-top: 1px solid var(--line-soft); }
.more > summary {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  cursor: pointer;
  list-style: none;
  font-size: 11.5px; letter-spacing: 0.08em; color: var(--text-dim);
  transition: color 0.16s;
}
.more > summary::-webkit-details-marker { display: none; }
/* 展开 / 收起写在 ::after 上：文案跟着状态走，不必再挂一段 JS 去改 textContent */
.more > summary::after { content: ' · 展开'; color: var(--text-mute); }
.more[open] > summary::after { content: ' · 收起'; }
.more > summary:hover { color: var(--text); }
.more[open] > summary { color: var(--cyan); margin-bottom: 12px; }
/* 图标是 chevrons-right（▶▶），转 90° 得到"点击展开"的 ▼、转 -90° 得到"点击收起"的 ▲ ——
   方向必须跟着状态走：一个始终朝右的箭头读起来像"跳转"，不像"展开" */
.more-arrow { display: inline-grid; place-items: center; transform: rotate(90deg); transition: transform 0.18s; }
.more[open] .more-arrow { transform: rotate(-90deg); }

.feat {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s5);
  margin-bottom: 0;
  text-align: left;
}
.feat-item {
  display: grid;
  grid-template-columns: 32px 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 0 var(--s5);
  padding: var(--s5) var(--s6);
  border-radius: var(--r-sm);
  background: linear-gradient(180deg, var(--glassy), rgba(8, 13, 26, 0.34));
  border: 1px solid var(--line-soft);
  border-left: 2px solid rgba(var(--cyan-rgb), 0.34);
  transition: border-color 0.16s, background 0.16s;
}
.feat-item:hover { border-left-color: var(--cyan); background: linear-gradient(180deg, rgba(var(--cyan-rgb), 0.09), rgba(8, 13, 26, 0.4)); }
/* 图标座：给裸图标一块底，整片网格才不会散 */
.feat-item span {
  grid-row: span 2;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  color: var(--cyan);
  background: radial-gradient(circle at 50% 42%, rgba(var(--cyan-rgb), 0.2), transparent 72%);
  border: 1px solid rgba(var(--cyan-rgb), 0.28);
}
.feat-item b { font-size: 12.5px; letter-spacing: 0.04em; }
.feat-item em { font-style: normal; font-size: 10.5px; line-height: 1.45; color: var(--text-dim); }

.keys { display: flex; flex-wrap: wrap; gap: var(--s3) var(--s6); justify-content: center; margin-bottom: var(--s6); }
.key-row { display: flex; align-items: center; gap: var(--s2); font-size: 11.5px; color: var(--text-dim); }
.key-row.touch-only { display: none; }
body.is-touch .key-row.touch-only { display: flex; }
kbd {
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 3px 7px;
  min-width: 24px;
  text-align: center;
  border-radius: 4px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.04));
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  color: var(--text);
}
kbd.wide { padding: 3px 10px; }

/* 开始页的三个设置现在同一行；下边距交给 .more 的 margin-top，别再各留一份 */
.start-row {
  display: flex;
  gap: 14px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 0;
}
.switch { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-dim); cursor: pointer; }
.switch input { display: none; }
.switch span {
  width: 38px; height: 22px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  position: relative;
  transition: background 0.18s;
  flex: none;
}
.switch span::after {
  content: "";
  position: absolute;
  top: 3px; left: 3px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform 0.18s, background 0.18s;
}
.switch span { border: 1px solid var(--line-soft); }
.switch input:checked + span { background: rgba(var(--cyan-rgb), 0.45); box-shadow: 0 0 14px rgba(var(--cyan-rgb), 0.35); }
.switch input:checked + span::after { transform: translateX(16px); background: #ffffff; }

.big-btn {
  position: relative;
  display: block;
  width: 100%;
  padding: 16px 20px;
  border: none;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 3px;
  text-indent: 3px;
  color: #331306;
  /* 主按钮走**暖色通道**（2026-10-16）：它是整屏最该被按的东西，
     而"开始游戏"是个好消息 —— 用冷色 + 切角会读成"确认一项操作"。
     同时半径加大、切角去掉：形状与颜色一起换，才是真的换了一套语言。 */
  background: var(--warm-grad);
  cursor: pointer;
  border-radius: calc(var(--r-lg) + 4px);
  overflow: hidden;
  box-shadow: 0 12px 32px -10px rgba(var(--warm-rgb), 0.8),
              inset 0 1px 0 rgba(255, 255, 255, 0.4),
              inset 0 -2px 6px rgba(120, 40, 0, 0.18);
  transition: transform 0.12s, filter 0.12s, box-shadow 0.2s;
}
/* 扫光：只有主按钮有，暗示"这里可以按"。走 --ez-soft 与 3.6s 的长周期 ——
   改成"呼吸"而不是"闪烁"：闪烁是警告，呼吸才是邀请。 */
.big-btn::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: -40%;
  width: 32%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.5), transparent);
  transform: skewX(-16deg);
  animation: btnSweep 4.2s var(--ez-soft) infinite;
}
@keyframes btnSweep {
  0%, 62% { left: -40%; }
  88%, 100% { left: 122%; }
}
.big-btn:hover {
  filter: brightness(1.06);
  box-shadow: 0 16px 40px -10px rgba(var(--warm-rgb), 0.95),
              inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
.big-btn:active { transform: scale(0.975); }
/* 次级按钮：也跟着换形状（切角 → 圆角），否则主次两个按钮是两种语言，
   并排时读起来像"两个不同来源的控件"。颜色仍然留给主按钮独占。 */
.ghost-btn {
  display: block;
  width: 100%;
  padding: 13px 20px;
  border-radius: calc(var(--r-md) + 2px);
  font-family: inherit;
  font-size: 13.5px;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  color: var(--text-dim);
  background: var(--glassy);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.16s, border-color 0.16s;
}
.ghost-btn:hover { color: var(--text); border-color: var(--line-hot); }
.ghost-btn:active { transform: scale(0.975); }
.row-btns { display: flex; flex-direction: column; gap: var(--s6); }

.best { margin-top: var(--s5); font-size: 11.5px; color: var(--text-dim); }
.best b {
  color: var(--cyan);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   战区异常（每层的环境规则，数据表在 anomaly.js）
   ============================================================ */

/* HUD 层数旁的异常图标：挂在「层」字前面 —— 不新增布局空间，
   窄屏下 4 个统计格本来就挤，多一格一定会溢出 */
.anom-ico {
  display: none;
  font-style: normal;
  color: var(--anom-color, var(--cyan));
  margin-right: 4px;
  filter: drop-shadow(0 0 5px var(--anom-color, transparent));
}
.anom-ico.on { display: inline; }

/* ------------------------------------------------------------
   战区铭牌（HUD 的层数格）
   层数与战区代号**常驻**，整格跟着战区色走 —— 六层六色，画面上
   的地面与暗角用的是同一个颜色（见 world.js drawGround / render.js
   drawVignette），两边说同一句话，玩家才能把"层"和"地方"对上号。
   改前这一格只有一个裸数字 + 一个"层"字，进层横幅 2.8 秒收起之后
   再无任何常驻证据，玩家说不出自己在第几层、那层叫什么。
   有战区异常时由 .anom 覆盖成环境色 —— 环境是本金层更紧急的事。
   ------------------------------------------------------------ */
.stat-sector b {
  color: var(--zone-color, var(--text));
  text-shadow: 0 0 12px var(--zone-glow, transparent);
}
/* 里程单位。数字是主角，"km"只负责说明这是公里 —— 所以它必须小一号、
   淡一档，而且**不能**长在 JS 每帧重写的那个节点里（见 index.html 的嵌套）。
   用 em 缩放而不是固定 px：同一套令牌要同时服务于 HUD（18px）
   与结算面板（另一档字号），两处都得成比例。 */
.stat-u {
  font-style: normal;
  font-size: 0.6em;
  letter-spacing: 0.06em;
  margin-left: 2px;
  opacity: 0.62;
}
.stat-sector .zone-name {
  color: var(--zone-color, var(--text-mute));
  opacity: 0.85;
}
.stat-sector.anom .zone-name { color: var(--anom-color, var(--text-dim)); }

/* 进层脉冲：数字从 3 变成 4 本身太安静，给它一下"跳"，
   否则玩家只能靠事后发现"敌人怎么变硬了"反推自己进了层 */
.stat-sector.pop b { animation: zonePop 0.66s cubic-bezier(.2, .9, .3, 1); }
@keyframes zonePop {
  0% { transform: scale(1); }
  24% { transform: scale(1.5); }
  60% { transform: scale(0.94); }
  100% { transform: scale(1); }
}
/* 有环境的这一层：数值与下划线一起染上环境色，扫一眼就知道"这层不一样" */
.stat.anom { position: relative; }
.stat.anom b { color: var(--anom-color, var(--cyan)); }
.stat.anom em { color: var(--anom-color, var(--text-dim)); opacity: 0.85; }
.stat.anom::after {
  content: '';
  position: absolute; left: 3px; right: 3px; bottom: -4px; height: 1.5px;
  border-radius: 2px;
  background: var(--anom-color, transparent);
  opacity: 0.65;
}

/* 层奖励面板里的「下一层遭遇」预告：虚线 + 无按钮 = 不能选、只能受着 */
.anom-row {
  --corner-color: rgba(var(--danger-rgb), 0.4);
  position: relative;
  display: none;
  align-items: center; gap: 10px;
  margin-top: 13px;
  padding: 9px 12px;
  border: 1px dashed var(--panel-brd);
  border-left: 3px solid var(--anom-color, var(--cyan));
  border-radius: var(--r-md);
  background: rgba(10, 17, 34, 0.45);
  text-align: left;
}
.anom-row.show { display: flex; animation: rise 0.28s backwards; }
.anom-row-ico {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: var(--r-sm);
  color: var(--anom-color, var(--cyan));
  background: rgba(10, 17, 34, 0.6);
  border: 1px solid var(--line-soft);
}
.anom-row-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.anom-row-body b {
  font-size: 12.5px; letter-spacing: 0.8px;
  color: var(--anom-color, var(--text));
}
.anom-row-body em {
  font-style: normal; font-size: 10.5px; line-height: 1.45;
  color: var(--text-dim);
}

/* 暂停面板里的"本层环境"：复用 .pause-kit，但第 1 层没有环境时整条不出现 */
.pause-kit.pause-anom { display: none; }
.pause-kit.pause-anom.show { display: flex; animation: rise 0.24s backwards; }

/* 右上角的出击机体标记（点开 = 暂停面板里的机体详情） */
.chassis-btn { color: var(--ch-color, var(--cyan)); }
.chassis-btn span {
  font-size: 16px;
  filter: drop-shadow(0 0 6px var(--ch-color, var(--cyan)));
}

/* ============================================================
   机库（出击机体选择）
   卡片布局：桌面纵向（图标在上），窄屏横向（图标在左）—— 靠 .chk-body 统一
   ============================================================ */
.hangar {
  --corner-color: rgba(var(--line-rgb), 0.5);
  position: relative;
  margin-bottom: var(--s6);
  padding: var(--s5) var(--s5) var(--s6);
  border: 1px solid var(--panel-brd);
  border-radius: var(--r-md);
  background: rgba(10, 17, 34, 0.5);
  text-align: left;
}
.hangar-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s5); margin-bottom: var(--s4);
}
.hangar-head b { font-size: 12.5px; letter-spacing: 1px; }
.hangar-head em { font-style: normal; font-size: 10.5px; color: var(--text-dim); }
.hangar-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s5); }

.chk {
  --ch-color: var(--cyan);
  --corner-color: rgba(var(--line-rgb), 0.4);
  /* 每台机体自带一个**色相通道**（--ch-rgb），供选中态的柔光晕用。
     rgba() 要的是裸通道，所以定义里存通道、别处拼。 */
  --ch-rgb: var(--cyan-rgb);
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  padding: var(--s5) var(--s4);
  border: 1px solid var(--panel-brd);
  border-radius: var(--r-md);
  background: linear-gradient(172deg, rgba(26, 38, 66, 0.72), rgba(13, 20, 39, 0.84));
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.16s, transform 0.2s var(--ez-pop), box-shadow 0.24s var(--ez-out);
}
.chk:hover:not(:disabled) { transform: translateY(-3px); }
/* 选中态（2026-10-16 重做）：
   改前是 `box-shadow: 0 0 0 1px` 一圈**贴边硬描边** + 一块硬投影 ——
   那是"被框住"，读起来像表单里的选中项。
   现在换成**弥散的光晕**（大模糊、低透明、往外散）：机体卡片自己"亮起来"，
   而不是"被画了个框"。同时抬高一档（两层阴影：一层贴地、一层发光），
   卡片与底纸之间有了空气。 */
.chk.on {
  border-color: rgba(var(--ch-rgb), 0.75);
  background: linear-gradient(170deg, rgba(38, 53, 90, 0.92), rgba(13, 20, 39, 0.9));
  box-shadow:
    0 12px 30px -12px rgba(0, 0, 0, 0.72),
    0 0 0 1px rgba(var(--ch-rgb), 0.28),
    0 0 26px -4px rgba(var(--ch-rgb), 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
/* 选中时顶上一个"出击中"的切角标签。切角保留 —— 它是全站"装备面板"的
   签名记号，只在**主按钮**上让位给圆角（那里要的是友好，不是专业）。 */
.chk.on::after {
  content: '出击中';
  position: absolute; top: -1px; right: -1px;
  font-size: 9px; font-weight: 800; letter-spacing: 0.5px;
  padding: 3px 8px 3px 9px;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 8px 100%, 0 calc(100% - 8px));
  background: var(--ch-color);
  color: #06101f;
}
.chk.lock { opacity: 0.46; cursor: not-allowed; filter: grayscale(0.7); }
.chk-ico { color: var(--ch-color); line-height: 1; display: grid; place-items: center; }
.chk-body {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 100%; min-width: 0;
}
.chk-name { font-size: 13px; color: var(--ch-color); letter-spacing: 1px; }
.chk-tag { font-style: normal; font-size: 9.5px; color: var(--text-dim); letter-spacing: 0.4px; }
.chk-perk {
  margin-top: 4px;
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 11px; font-weight: 700; color: var(--text);
}
.chk-perk i { font-style: normal; font-size: 12px; }
.chk-desc {
  font-style: normal; font-size: 10px; line-height: 1.35;
  color: var(--text-dim); min-height: 27px;
}
.chk-stat, .chk-lock {
  margin-top: 5px; padding-top: 5px;
  width: 100%; text-align: center;
  border-top: 1px dashed var(--panel-brd);
  font-size: 10px;
}
.chk-stat { color: var(--text-dim); }
.chk-stat b { color: var(--ch-color); font-variant-numeric: tabular-nums; }
.chk-lock { color: var(--amber); }
/* 击杀轨（双轨解锁的保底路）：一行小字 + 一根细进度条。
   底色用琥珀的 15% 透明，走到 100% 时机体就已解锁、这行整个被 chk-stat 换掉。 */
.chk-locksub {
  display: block; margin-top: 3px;
  font-size: 10px; color: var(--text-dim);
}
.chk-locksub em { font-style: normal; font-variant-numeric: tabular-nums; color: var(--amber); }
.chk-bar {
  display: block; margin: 4px 0 1px; font-style: normal;
  width: 100%; height: 4px; border-radius: 2px;
  background: rgba(251, 191, 36, 0.16); overflow: hidden;
}
.chk-bar i { display: block; height: 100%; border-radius: 2px; background: var(--amber); }

/* 遗产芯片（跨局开局便利）：机库下方一行小胶囊。
   亮 = 已解锁并每局自动生效；灰 + 锁图标 = 未解锁（title 里给条件）。
   刻意只占一行高（~22px）：开始页的高度预算以"一屏内按到开始"为准，
   这里多一行就必须从别处省回来。 */
.legacy-strip {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 6px; margin-top: 9px;
}
.lgc-head {
  font-size: 10.5px; font-weight: 400; letter-spacing: 1px;
  color: var(--text-dim); margin-right: 2px; white-space: nowrap;
}
.lgc-head em { font-style: normal; font-variant-numeric: tabular-nums; }
.lgc {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2.5px 8px;
  border: 1px solid rgba(103, 232, 249, 0.28);
  border-radius: 999px;
  background: rgba(103, 232, 249, 0.07);
  color: var(--cyan);
  font-size: 10.5px; letter-spacing: 0.4px;
  cursor: help;                     /* title 提示是唯一的效果说明入口 */
}
.lgc i { font-style: normal; }
.lgc.lock {
  color: var(--text-dim);
  border-color: var(--panel-brd);
  background: rgba(148, 163, 184, 0.05);
  filter: grayscale(0.6);
}

/* 暂停面板里的"你现在开的是哪台" */
.pause-kit {
  --ch-color: var(--cyan);
  --corner-color: rgba(var(--line-rgb), 0.34);
  position: relative;
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 16px;
  padding: 10px 13px;
  border: 1px solid var(--panel-brd);
  border-left: 3px solid var(--ch-color);
  border-radius: var(--r-md);
  background: rgba(10, 17, 34, 0.55);
  text-align: left;
}
.pk-ico { color: var(--ch-color); display: grid; place-items: center; }
.pk-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pk-body b { font-size: 13px; letter-spacing: 1px; }
.pk-body em { font-style: normal; font-size: 10.5px; color: var(--text-dim); }

/* 结算页的新机体解锁提示 */
/* 解锁播报（结算页）：这一条**只报好消息**（新机体 / 新遗产芯片），
   所以它属于暖色通道 —— 改前用的是琥珀，而琥珀在全站是"警戒"（BOSS 预警、
   出界警告、契约代价都用它）。同一种颜色既喊"危险"又喊"解锁"，
   玩家就得读文字才知道这是好事。
   现在改用暖橘：与警戒金黄在色相上分得开，语义上也不再借用警告通道。 */
.over-unlock {
  display: none;
  margin-top: var(--s5);
  padding: var(--s5) var(--s6);
  border: 1px solid rgba(var(--warm-rgb), 0.45);
  border-radius: var(--r-md);
  background: linear-gradient(100deg, rgba(var(--warm-rgb), 0.14), rgba(var(--blush-rgb), 0.06));
  color: var(--warm);
  font-size: 11.5px; line-height: 1.5;
  box-shadow: var(--warm-halo);
}
.over-unlock.show { display: block; animation: rise 0.28s var(--ez-pop) backwards; }
/* 结算页"新纪录"那一句走暖色：它是一次事件（"这局破纪录了"），
   不是一项数据。 */
#overBest b { color: var(--warm); }
/* 破纪录分项胶囊：破了几项就亮几枚。用 flex 换行，手机上挤到第二行也不乱。 */
.rec-row {
  display: inline-flex; flex-wrap: wrap; gap: var(--s2);
  vertical-align: middle;
  margin-right: var(--s3);
}
.rec-chip {
  display: inline-flex; align-items: center; gap: 3px;
  font-style: normal;
  padding: 2.5px 9px;
  border-radius: 999px;
  background: linear-gradient(100deg, rgba(var(--warm-rgb), 0.22), rgba(var(--blush-rgb), 0.12));
  border: 1px solid rgba(var(--warm-rgb), 0.5);
  color: var(--warm);
  font-weight: 700;
  letter-spacing: 0.04em;
  animation: rise 0.3s var(--ez-pop) backwards;
}
.rec-chip:nth-child(2) { animation-delay: 0.08s; }
.rec-chip:nth-child(3) { animation-delay: 0.16s; }
.rec-chip:nth-child(n+4) { animation-delay: 0.22s; }
.hint { margin-top: var(--s6); font-size: 11.5px; color: var(--text-dim); }

/* ------------------------------------------------------------
   升级托盘（2026-09-23：升级不再暂停游戏）
   改前：升级 = 全屏遮罩 + 三张大卡，`state='levelup'`，**游戏完全停住**。
     被围的时候弹出来，玩家只有两个选择：站着看完图再挨打，或者乱点一张。
     打赢一场硬仗的节奏感被切断在一次"要读三行字"的中断上。
   改后：托盘挂在左侧边缘、垂直居中，不挡屏幕中心（玩家与主战场都在中心），
     半透明让底下的敌人仍然隐约可见，**游戏一直跑**。
   为什么是左侧而不是顶部/底部：顶部被血条 + 统计 + BOSS 血条占满，
     底部是武器 / 契约 / 无人机 / 两个技能环，只有左侧边缘是空的。
   托盘本体 `pointer-events: none`：只有卡片本身可点，鼠标划过空白处不拦截。
   **垂直居中用 auto 外边距，不用 `top:50% + translateY(-50%)`**：
     ① 百分比 transform 的居中是以"元素自己的高度"为基准的，而托盘的卡片会换一批、
        头部的"还有 N 次"也是随时出现的 —— 高度一变整条托盘就上下跳一下；
        auto 外边距是按当前高度现算的，换卡时纹丝不动。
     ② 带 transform 的元素会被提升成独立合成层，无头截图里那一层不参与合成：
        computed style 全是 opacity 1 / visible，截图里却整块不存在
        （与 canvas "只合成首帧"是同一类坑，见 tools/shoot.js 的说明）。
   ------------------------------------------------------------ */
.up-tray {
  position: absolute;
  left: 10px;
  top: 0;
  bottom: 0;
  height: fit-content;       /* 必须：不给的话 flex 容器里的绝对定位子元素会被拉满整屏 */
  margin: auto 0;            /* 垂直居中（配 top/bottom:0，不用 transform） */
  width: 274px;
  transform: translateX(-16px);   /* 入场只用横向位移，与居中解耦 */
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, transform 0.26s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
.up-tray.show { opacity: 1; visibility: visible; transform: translateX(0); }

/* 升级那一下整条托盘"闪一下"（2026-10-20）。
   为什么需要：托盘是**常驻**在边上的 —— 它出现、它换卡、它消失，
   三个时刻的长相差异很小。玩家原话「升级提示看不清楚」，一半原因就是这个：
   换了一批卡和"托盘一直在那儿"在余光里分不出来。
   这一闪只做一件事：让"刚刚发生了升级"与"托盘还挂着"在余光里可区分。
   0.45 秒、只闪一圈外发光，不位移（位移会与入场的 translateX 打架）。 */
@keyframes trayBump {
  0%   { filter: brightness(1); }
  22%  { filter: brightness(1.55) saturate(1.25); }
  100% { filter: brightness(1); }
}
.up-tray.bump { animation: trayBump 0.45s var(--ez-out) both; }

/* 玩家走到托盘底下时自动"让位"：留一层极淡的轮廓。
   不能整块隐藏 —— 玩家仍然要能看见有卡在等、仍然要能点；只是不能挡住机体。
   也不动 pointer-events：位置判断只改透视，不改命中区域，"手在卡片上、眼在被挡的
   机体上"的时候点下去仍然是选卡，不会突然变成"点空了"。
   让位时连外发光一起收掉：那一层是"看这里"，而让位恰恰是"别看我"。 */
.up-tray.ghost { opacity: 0.16; }
.up-tray.ghost .up-card { border-left-color: transparent; box-shadow: none; }
.up-tray.ghost .uc-ico, .up-tray.ghost .uc-keys { opacity: 0; }

.up-head {
  /* 两行网格：等级 / 待选计数占第一行，"怎么选"另起一行。
     挤成一行时 6 枚键帽会把它顶成两行，而且计数一变就换行 ——
     整条托盘的高度跟着跳（它是垂直居中的，跳一次就整条上下移动）。 */
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 7px;
  row-gap: 3px;
  padding: 6px 10px;
  border-radius: var(--r-sm);
  background: linear-gradient(100deg, rgba(18, 26, 50, 0.94), rgba(9, 14, 28, 0.9));
  border: 1px solid rgba(var(--violet-rgb), 0.5);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45), 0 0 18px -8px rgba(var(--violet-rgb), 0.7);
  font-size: 11px;
  color: var(--text);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.up-head b { font-family: var(--font-mono); font-size: 14px; color: var(--violet); letter-spacing: 0.04em; }
.up-head .up-hint { grid-column: 1 / -1; display: flex; align-items: center; flex-wrap: wrap; }
.up-head kbd {
  display: inline-block; margin: 0 2px; padding: 1px 5px;
  border-radius: 4px;
  border: 1px solid rgba(var(--violet-rgb), 0.5);
  background: rgba(var(--violet-rgb), 0.16);
  font-family: var(--font-mono); font-size: 10.5px; color: var(--text);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.45);
}
/* 攒了多次待选时的计数（比如一发 AOE 连升三级） */
.up-head em { grid-column: 2; justify-self: end; font-style: normal; color: var(--amber); font-weight: 700; }

/* 静置到点的自动选倒计时：**只在二级强催之后**才出现（class `on` 由 syncTrayAuto 加）。
   它是"再不理我就替你按了"的最后一句，所以跟着强催的琥珀色走。
   位置压在头部与卡片之间：视线扫过托盘时，先看到的是"还剩几秒"而不是"选什么"。 */
.up-auto {
  display: none;
  align-items: center; gap: 5px;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  background: rgba(251, 191, 36, 0.13);
  border: 1px solid rgba(251, 191, 36, 0.42);
  font-size: 11px; font-weight: 700;
  color: var(--amber);
  box-shadow: 0 0 18px -8px rgba(251, 191, 36, 0.8);
}
.up-auto.on { display: flex; }
.up-auto svg { flex: none; }

.up-cards { display: flex; flex-direction: column; gap: var(--s3); }

/* 卡片是"看这里"的主体。改前的问题不是小，而是**和地面是一伙的**：
   底色 rgba(16,24,46,.92) 压在深色地面上几乎同色，只剩一条细色条在提示稀有度。
   现在四件事一起上 —— 底色更实（.97）、边框带稀有度色、左侧色条加粗到 4px
   且用不透明色、外发光用稀有度色。让卡片在地面上"浮"出来。 */
.up-card {
  --rc: var(--cyan-rgb);
  pointer-events: auto;
  position: relative;
  overflow: hidden;               /* 入场扫光要裁在圆角里 */
  display: grid;
  grid-template-columns: 36px 1fr auto;
  align-items: center;
  gap: 9px;
  padding: var(--s4) var(--s5);
  border-radius: var(--r-sm);
  background: linear-gradient(100deg, rgba(20, 29, 54, 0.97), rgba(9, 14, 28, 0.95));
  border: 1px solid rgba(var(--rc), 0.30);
  border-left: 4px solid rgb(var(--rc));
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.5), 0 0 20px -8px rgba(var(--rc), 0.8);
  cursor: pointer;
  text-align: left;
  /* 逐张入场（2026-10-16）：改前三张**同时**滑入，读起来是"一条托盘出现了"；
     错开 70ms 之后是"三张牌一张张发过来"—— 同样的信息量，但有了节奏。
     顺序用 nth-child 递推，JS 不用管。 */
  animation: upIn 0.3s var(--ez-pop) both;
  transition: border-color 0.14s, transform 0.14s, background 0.14s;
}
.up-card:nth-child(1) { animation-delay: 0s; }
.up-card:nth-child(2) { animation-delay: 0.07s; }
.up-card:nth-child(3) { animation-delay: 0.14s; }
.up-card:nth-child(n+4) { animation-delay: 0.2s; }
@keyframes upIn { from { opacity: 0; transform: translateX(-18px); } }
/* 入场扫光：卡片滑入之后一道高光横掠而过 —— 那一下是"看这里"的一次性提示。
   游戏不暂停，弹出时玩家多半正盯着包围圈，静态出现很容易整条被忽略。 */
.up-card::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 34%, rgba(255, 255, 255, 0.16) 50%, transparent 66%);
  background-size: 250% 100%;
  animation: upSweep 0.9s var(--ez-out) 0.1s both;
}
@keyframes upSweep { from { background-position: 135% 0; } to { background-position: -35% 0; } }
.up-card:hover {
  transform: translateX(4px);
  border-color: rgba(var(--rc), 0.72);
  background: linear-gradient(100deg, rgba(28, 41, 72, 0.98), rgba(12, 19, 38, 0.96));
}
.up-card:active { transform: scale(0.98); }

/* 被吸收：选中的卡片朝机体（= 屏幕中心）收缩淡出。
   为什么是这一拍 —— 改前"按下 → 特效 → 卡消失"里，卡片自己没有任何过程，
   读起来像"卡被删掉了"而不是"卡变成了我的力量"。这一拍把因果补完整。
   位移量由 hud.js 的 absorbCard() 按卡片实际位置算好，写进 --abs-x/--abs-y；
   拿不到布局（无头测试桩）时两个变量都是 0，退化成"原地缩小"——
   这正是我们要的兜底：动效可以没有，逻辑不能没有。
   时长必须与 js 侧的 CARD_ABSORB_MS(160) 对齐，否则托盘收起了卡还在飞。 */
.up-card.absorb {
  pointer-events: none;
  animation: none;                 /* 掐掉入场的 upIn / 扫光，避免两套动画打架 */
  transition: transform 0.16s cubic-bezier(0.4, 0, 0.7, 1),
              opacity 0.16s linear;
  transform: translate(var(--abs-x, 0), var(--abs-y, 0)) scale(0.18);
  opacity: 0;
}
.up-card.absorb::after { animation: none; }
.up-card.rare-1 { --rc: var(--ok-rgb); }
.up-card.rare-2 { --rc: var(--violet-rgb); }
.up-card.rare-3 { --rc: var(--warn-rgb); }
.up-card.rare-4 { --rc: var(--pink-rgb); }
/* 精通（深层补位的无限卡）：青色 = 机体 HUD 的"自己的颜色"，
   与四档稀有度（绿/紫/琥珀/粉）都区分得开 */
.up-card.rare-5 { --rc: var(--cyan-rgb); }

/* 静置未选就**逐级催促**（计时在 hud.js，走帧驱动）。
   为什么必须有这一层：升级不暂停之后，玩家可以一直不选、一直打下去 ——
   托盘安静地挂在边缘，专心战斗的人是收不到的。但也不能一上来就大喊，
   所以分两级、按"多久没理我"递进。
   注意**两级的主要区别是静态样式**（边框与外发光），呼吸动画只是让它活起来：
   无头截图会把动画钉住，只靠 animation 的话验收图上看不出任何差别。 */
.up-tray.urge .up-card {
  border-color: rgba(var(--rc), 0.68);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.52), 0 0 26px -4px rgba(var(--rc), 0.85);
  animation: trayBreath 1.6s var(--ez-soft) infinite;
}
.up-tray.urge2 .up-card {
  border-color: rgb(var(--rc));
  animation: trayBreath 0.8s var(--ez-soft) infinite;
}
.up-tray.urge2 .up-head {
  border-color: rgba(251, 191, 36, 0.85);
  color: var(--amber);
}
.up-tray.urge2 .up-head b { color: var(--amber); }
@keyframes trayBreath { 50% { filter: brightness(1.28); } }

.uc-ico {
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border-radius: 9px;
  background: rgba(var(--rc), 0.20);
  border: 1px solid rgba(var(--rc), 0.34);
  color: rgb(var(--rc));
}
.uc-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.uc-top { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.uc-top b { font-size: 13px; font-weight: 700; color: var(--text); letter-spacing: 0.02em; white-space: nowrap; }
.uc-top i { font-style: normal; font-family: var(--font-mono); font-size: 9.5px; color: var(--text-mute); }
.uc-desc {
  font-style: normal; font-size: 11px; line-height: 1.35; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 键位做成**方键帽**，不是描边小字：升级不暂停时，"怎么选"比"选什么"更容易卡住人
   （手在 WASD 上、眼在包围圈上）。键帽是"这里可以按"的通用视觉语汇，不用读字。
   竖排两枚 = "这两个键都行"：上路是数字（左手够得到时用），下路是右手字母
   （WASD 玩家的手离不开移动键 —— 这正是这次要修的手感问题）。 */
.uc-keys { display: flex; flex-direction: column; gap: 2px; }
.uc-key {
  min-width: 21px;
  padding: 1px 0;
  text-align: center;
  font-style: normal;
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 700;
  color: rgb(var(--rc));
  background: rgba(var(--rc), 0.18);
  border: 1px solid rgba(var(--rc), 0.6);
  border-radius: 5px;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
/* 替代键压一档亮度：两张一样亮会让"每张一个键"读不出来（变成 6 枚键帽） */
.uc-key.alt { opacity: 0.6; }

/* --- 升级卡片 --- */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s5); }
.card {
  --rc: var(--cyan-rgb);
  position: relative;
  padding: var(--s7) var(--s5) var(--s6);
  border-radius: var(--r-md);
  background: linear-gradient(178deg, var(--surface-2), var(--surface) 55%);
  border: 1px solid var(--line);
  cursor: pointer;
  text-align: center;
  transition: transform 0.16s, border-color 0.16s, box-shadow 0.16s;
  animation: rise 0.24s var(--ez-out) backwards;
}
/* 卡面的一层稀有度辉光：从图标位置往外淡出 */
.card::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(circle at 50% 26%, rgba(var(--rc), 0.22), transparent 62%);
}
.card:hover {
  transform: translateY(-4px);
  border-color: rgba(var(--rc), 0.7);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5), 0 0 22px rgba(var(--rc), 0.28);
}
.card:active { transform: scale(0.97); }
/* 稀有度**顶部一条渐隐色带**（2026-10-16）：改前每张卡的稀有度只体现在
   边框色 + 图标光，而三张卡并排时边框色的差别要在心里比对才看得出来。
   加一条从上方往下淡出的色带之后，余光扫过去就能分辨"哪张更亮"——
   这是"形状 + 面积"上的差别，比"色相差别"强得多（与升级特效那一轮的结论同源）。 */
.card::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 46%;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(var(--rc), 0.26), transparent);
  opacity: 0.9;
}
/* 内容要压在色带之上（色带是 ::after，DOM 里的内容默认在它下面） */
.card > * { position: relative; z-index: 1; }

/* 图标座：芯片感的方托 + 内发光，比裸图标"重"得多 */
.card .ico {
  position: relative;
  width: 46px; height: 46px;
  margin: 0 auto 10px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  color: rgb(var(--rc));
  background: radial-gradient(circle at 50% 40%, rgba(var(--rc), 0.26), rgba(var(--rc), 0.05));
  border: 1px solid rgba(var(--rc), 0.42);
  box-shadow: inset 0 0 14px rgba(var(--rc), 0.22), 0 0 16px rgba(var(--rc), 0.16);
  filter: drop-shadow(0 0 8px rgba(var(--rc), 0.5));
}
.card b {
  display: block;
  font-size: 13.5px;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}
.card em {
  font-style: normal;
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.55;
  display: block;
}
.card .tag {
  position: absolute;
  top: 7px; right: 7px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  padding: 2px 7px;
  border-radius: 4px;
  background: rgba(var(--rc), 0.18);
  color: rgb(var(--rc));
  border: 1px solid rgba(var(--rc), 0.34);
}
.card .key-num {
  position: absolute;
  top: 7px; left: 7px;
  font-family: var(--font-mono);
  font-size: 10px;
  width: 18px; height: 18px;
  line-height: 17px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--line-soft);
  color: var(--text-dim);
}
.card.rare-1 { --rc: var(--ok-rgb); border-color: rgba(var(--ok-rgb), 0.45); }
.card.rare-1 .tag { background: rgba(var(--ok-rgb), 0.18); color: var(--green); }
.card.rare-2 { --rc: var(--violet-rgb); border-color: rgba(var(--violet-rgb), 0.5); }
.card.rare-2 .tag { background: rgba(var(--violet-rgb), 0.2); color: var(--violet); }
.card.rare-3 { --rc: var(--warn-rgb); border-color: rgba(var(--warn-rgb), 0.55); }
.card.rare-3 .tag { background: rgba(var(--warn-rgb), 0.2); color: var(--amber); }
/* 传说：更亮的描边 + 外发光，一眼分辨质变级词条 */
.card.rare-4 {
  --rc: var(--pink-rgb);
  border-color: rgba(var(--pink-rgb), 0.7);
  background: linear-gradient(170deg, rgba(var(--pink-rgb), 0.14), rgba(var(--warn-rgb), 0.06));
  box-shadow: 0 0 22px rgba(var(--pink-rgb), 0.22);
}
.card.rare-4 .tag { background: rgba(var(--pink-rgb), 0.24); color: #fbcfe8; }
.card.rare-4 b { color: #ffe4f3; }

/* 已拥有等级 / 上限 */
.card .lv {
  position: absolute;
  left: 7px; bottom: 7px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--line-soft);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* --- 结算 --- */
/* 改前六个格子全是同一个权重：**里程、积分、时间、击杀、等级、BOSS 并列**，
   玩家一眼看不出"我这一局最值得说的是什么"。
   现在分两级（2026-10-16）：
     · **头条**（里程 / 积分）：暖色 + 大一号 —— 那是"我打得多好"
     · 其余（时间/击杀/等级/BOSS）：冷色常规 —— 那是"过程数据"
   分级不等于删信息，只是让第一眼有落点。 */
.result {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s5);
  margin-bottom: var(--s4);
}
.result-item {
  padding: var(--s6) var(--s4);
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
}
.result-item {
  background: linear-gradient(180deg, var(--glassy), rgba(9, 15, 30, 0.42));
}
/* 头条两格：暖色发光数字 + 一圈很淡的暖边。
   注意**只加暖、不加亮** —— 结算页整屏在暗底上，再提亮会让数字糊成一团。 */
.result-item.head {
  background: linear-gradient(180deg, rgba(var(--warm-rgb), 0.10), rgba(9, 15, 30, 0.46));
  border-color: rgba(var(--warm-rgb), 0.28);
  box-shadow: inset 0 1px 0 rgba(var(--warm-rgb), 0.16);
}
.result-item b {
  display: block;
  font-family: var(--font-mono);
  font-size: 25px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--cyan);
  text-shadow: 0 0 18px rgba(var(--cyan-rgb), 0.4);
}
.result-item.head b {
  font-size: 29px;
  color: var(--warm);
  text-shadow: 0 0 22px rgba(var(--warm-rgb), 0.5);
}
.result-item em {
  font-style: normal;
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--text-mute);
}

/* --- BOSS 血条 --- */
.boss-bar {
  position: absolute;
  top: 66px; left: 50%;
  transform: translate(-50%, -14px);
  width: min(560px, 62vw);
  padding: 10px 15px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--boss-edge, rgba(var(--danger-rgb), 0.6));
  background: linear-gradient(180deg, rgba(12,16,32,0.88), rgba(8,10,22,0.74));
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 10px 34px rgba(0,0,0,0.5), 0 0 26px var(--boss-glow, rgba(var(--danger-rgb), 0.4));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s, transform 0.25s;
  z-index: 5;
}
.boss-bar.show { opacity: 1; transform: translate(-50%, 0); }
.boss-head { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.boss-skull {
  font-size: 13px;
  color: var(--boss-color, var(--red));
  filter: drop-shadow(0 0 6px var(--boss-color, var(--red)));
}
#bossName {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #fff;
  text-shadow: 0 1px 3px #000;
}
.boss-track {
  position: relative;
  height: 10px;
  border-radius: 4px;
  background: var(--sunk);
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--line-soft), inset 0 2px 6px rgba(0, 0, 0, 0.66);
}
.boss-track::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(90deg, rgba(2, 5, 12, 0.62) 0 1px, transparent 1px 5%);
}
.boss-track i {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, #fff 0%, var(--boss-color, var(--red)) 26%, var(--boss-color, var(--red)) 100%);
  box-shadow: 0 0 14px var(--boss-glow, rgba(var(--danger-rgb), 0.4));
  transition: width 0.16s linear;
}
/* 50% 狂暴阈值刻度：告诉玩家"再打掉一半它就变招" */
.boss-half {
  position: absolute;
  left: 50%; top: 0; bottom: 0;
  width: 2px; margin-left: -1px;
  background: rgba(255,255,255,0.6);
  box-shadow: 0 0 7px rgba(0,0,0,0.75);
}
/* 狂暴徽标（默认隐藏，进入狂暴形态后脉冲出现） */
.boss-rage {
  margin-left: auto;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.12em;
  padding: 1.5px 7px;
  border-radius: 999px;
  color: #fff;
  background: linear-gradient(90deg, #f97316, var(--red));
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
  opacity: 0;
  transform: scale(0.75);
  transition: opacity 0.22s, transform 0.22s;
  box-shadow: 0 0 10px rgba(249,115,22,0.55);
}
/* 狂暴态：血条整体转红脉冲，阈值刻度退场 */
.boss-bar.rage {
  border-color: rgba(249,115,22,0.85);
  animation: rageBarPulse 1.1s ease-in-out infinite;
}
.boss-bar.rage .boss-rage { opacity: 1; transform: scale(1); }
.boss-bar.rage .boss-half { opacity: 0; }
.boss-bar.rage .boss-track i { background: linear-gradient(90deg, #fff 0%, var(--amber) 20%, var(--red) 100%); }
@keyframes rageBarPulse {
  0%, 100% { box-shadow: 0 10px 34px rgba(0,0,0,0.55), 0 0 22px rgba(249,115,22,0.42); }
  50%      { box-shadow: 0 10px 34px rgba(0,0,0,0.55), 0 0 40px rgba(239,68,68,0.8); }
}

/* --- BOSS 预警态：血条先以"扫描条"亮相，提前告知下一位目标 --- */
.boss-bar.warn {
  border-style: dashed;
  animation: bossWarnPulse 1.35s ease-in-out infinite;
}
.boss-bar.warn .boss-track i {
  background: linear-gradient(90deg, transparent 0%, var(--boss-color, var(--red)) 34%, var(--boss-color, var(--red)) 100%);
  box-shadow: none;
}
.boss-bar.warn .boss-half { opacity: 0.22; }
@keyframes bossWarnPulse {
  0%, 100% { box-shadow: 0 10px 34px rgba(0,0,0,0.5), 0 0 12px var(--boss-glow, rgba(var(--danger-rgb), 0.3)); }
  50%      { box-shadow: 0 10px 34px rgba(0,0,0,0.5), 0 0 34px var(--boss-glow, rgba(var(--danger-rgb), 0.62)); }
}

/* --- BOSS 登场横幅 --- */
.boss-intro {
  position: absolute;
  top: 34%; left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 14px;
  pointer-events: none;
  opacity: 0;
  z-index: 7;
}
.boss-intro .bi-bar {
  width: clamp(34px, 11vw, 148px);
  height: 2px;
  transform-origin: right center;
  background: linear-gradient(90deg, transparent, var(--boss-color, var(--red)));
  box-shadow: 0 0 12px var(--boss-glow, rgba(var(--danger-rgb), 0.5));
}
.boss-intro .bi-bar:last-child {
  transform-origin: left center;
  background: linear-gradient(270deg, transparent, var(--boss-color, var(--red)));
}
.boss-intro .bi-body { display: flex; align-items: center; gap: 12px; }
.boss-intro .bi-skull {
  font-size: clamp(22px, 3vw, 30px);
  line-height: 1;
  color: var(--boss-color, var(--red));
  filter: drop-shadow(0 0 14px var(--boss-glow, rgba(var(--danger-rgb), 0.7)));
}
.boss-intro .bi-text { display: flex; flex-direction: column; gap: 3px; }
.boss-intro .bi-text b {
  font-size: clamp(20px, 3.4vw, 34px);
  letter-spacing: 0.14em;
  color: #fff;
  white-space: nowrap;
  text-shadow: 0 2px 10px #000, 0 0 26px var(--boss-glow, rgba(var(--danger-rgb), 0.6));
}
.boss-intro .bi-text em {
  font-style: normal;
  font-size: clamp(10px, 1.3vw, 13px);
  letter-spacing: 0.2em;
  color: var(--boss-color, var(--red));
  filter: brightness(1.35);
  white-space: nowrap;
}
.boss-intro.show { animation: bossIntro 2.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
.boss-intro.show .bi-bar { animation: biBar 2.6s ease-out forwards; }
@keyframes bossIntro {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(1.5); filter: blur(7px); }
  11%  { opacity: 1; transform: translate(-50%, -50%) scale(1); filter: blur(0); }
  74%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
}
@keyframes biBar {
  0%   { transform: scaleX(0.1); opacity: 0; }
  16%  { transform: scaleX(1); opacity: 1; }
  74%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(0.25); opacity: 0; }
}

/* --- 已觉醒的遗物武器：名字带一点金光 --- */
#wpnName.awake {
  color: var(--amber);
  text-shadow: 0 0 10px rgba(var(--warn-rgb), 0.7);
}

/* --- 同屏多 BOSS：主血条右上的剩余数量徽标 --- */
.boss-count {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  padding: 1.5px 7px;
  border-radius: 999px;
  color: var(--text);
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.22);
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.2s, transform 0.2s;
}
.boss-count.show { opacity: 1; transform: scale(1); }
/* 有额外目标时，狂暴徽标让位（两者都在头部右侧） */
.boss-bar.multi .boss-rage { margin-left: 8px; }

/* --- 战区横幅：复用登场横幅的动画，位置更低、配色更"地图" --- */
.sector-intro {
  top: 42%;
  --boss-glow: rgba(var(--cyan-rgb), 0.5);
}
.sector-intro .bi-skull.sec-icon {
  font-size: clamp(20px, 2.6vw, 26px);
  filter: drop-shadow(0 0 12px var(--boss-glow));
}
.sector-intro .bi-text b {
  letter-spacing: 0.22em;
}
.sector-intro .bi-text em { letter-spacing: 0.1em; }
.sector-intro.show { animation-duration: 2s; }
.sector-intro.show .bi-bar { animation-duration: 2s; }

/* --- 战区突破面板标题 --- */
/* 战区突破（里程结算）：这是一局的**中途小结**，属于"好消息"那一侧 ——
   推进了、有奖励可拿。标题改走暖色（2026-10-16），与结算页的头条同一套语言；
   下面的奖励卡与异常预告**不动**（异常是环境的坏消息，语义不能混）。 */
.sheet-title.sector-title { color: var(--warm); letter-spacing: 3px; }
.sheet-title.sector-title span { color: var(--warm); }
/* 里程数字单独发光：它是这一页唯一的"成绩" */
.sheet-title.sector-title span#secBadge {
  background: rgba(var(--warm-rgb), 0.14);
  border-color: rgba(var(--warm-rgb), 0.5);
  text-shadow: 0 0 16px rgba(var(--warm-rgb), 0.5);
}
#secZoneName { color: var(--cyan); }
/* 层奖励卡片：统一走"战区"配色，与普通升级的绿/紫/金区分开 */
.card.rare-sec { --rc: var(--cyan-rgb); border-color: rgba(var(--cyan-rgb), 0.5); }
.card.rare-sec .tag { background: rgba(var(--cyan-rgb), 0.2); color: var(--cyan); }
.card.rare-sec b { color: var(--text); }
.card.rare-relic { --rc: var(--warn-rgb); border-color: rgba(var(--warn-rgb), 0.6); box-shadow: 0 0 18px rgba(var(--warn-rgb), 0.16) inset; }
.card.rare-relic .tag { background: rgba(var(--warn-rgb), 0.22); color: var(--amber); }
.card.rare-relic b { color: #fff7d6; }

/* --- 精英契约栏：层奖励面板底部，独立于奖励卡（勾选后选卡即提交） --- */
.pact {
  display: none;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  border: 1px dashed var(--pact-color, rgba(var(--danger-rgb), 0.55));
  background: linear-gradient(180deg, rgba(var(--danger-rgb), 0.10), rgba(255,255,255,0.02));
  cursor: pointer;
  text-align: left;
  transition: transform 0.14s, border-color 0.14s, box-shadow 0.14s, background 0.14s;
}
.pact.show { display: flex; animation: rise 0.24s backwards; }
.pact:hover { transform: translateY(-2px); }
/* 已签署：实线描边 + 外发光，和"未签"一眼可分 */
.pact.on {
  border-style: solid;
  background: linear-gradient(180deg, rgba(var(--danger-rgb), 0.24), rgba(255,255,255,0.03));
  box-shadow: 0 0 22px var(--pact-glow, rgba(var(--danger-rgb), 0.4));
}
.pact .pact-ico {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 34px; height: 34px;
  border-radius: var(--r-sm);
  color: var(--pact-color, var(--red));
  background: rgba(var(--danger-rgb), 0.12);
  border: 1px solid rgba(var(--danger-rgb), 0.3);
}
.pact .pact-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pact .pact-body b { font-size: 13.5px; letter-spacing: 1px; color: var(--pact-color, rgb(var(--danger-rgb))); }
.pact .pact-body em { font-style: normal; font-size: 11px; line-height: 1.45; color: var(--text-dim); }
.pact .pact-flag {
  flex: 0 0 auto;
  font-size: 10.5px;
  padding: 4px 9px;
  border-radius: 999px;
  white-space: nowrap;
  border: 1px solid var(--pact-color, rgba(var(--danger-rgb), 0.5));
  color: var(--pact-color, rgb(var(--danger-rgb)));
}
.pact.on .pact-flag { background: var(--pact-color, var(--red)); color: var(--bg); font-weight: 700; }

/* --- HUD 左下角：当前生效的契约（整层有效） --- */
.pact-chip {
  display: none;
  align-items: center;
  gap: 5px;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--pact-color, rgba(var(--danger-rgb), 0.6));
  background: rgba(13, 20, 39, 0.74);
  color: var(--pact-color, rgb(var(--danger-rgb)));
  box-shadow: 0 0 14px var(--pact-glow, rgba(var(--danger-rgb), 0.35));
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.pact-chip.show { display: flex; animation: rise 0.24s backwards; }
.pact-chip span { display: grid; place-items: center; }
.pact-chip b { font-size: 11px; letter-spacing: 0.5px; }

/* 支援无人机：常驻标记（等级常变，所以带一枚等级胶囊） */
.pet-chip {
  display: none;
  align-items: center;
  gap: 5px;
  padding: 5px 10px 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--pet-color, rgba(var(--cyan-rgb), 0.6));
  background: rgba(13, 20, 39, 0.74);
  color: var(--pet-color, rgb(var(--cyan-rgb)));
  box-shadow: 0 0 14px var(--pet-glow, rgba(var(--cyan-rgb), 0.3));
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.pet-chip.show { display: flex; animation: rise 0.24s backwards; }
.pet-chip span { display: grid; place-items: center; }
.pet-chip b { font-size: 11px; letter-spacing: 0.5px; }
.pet-chip em {
  font-style: normal;
  font-size: 10px;
  font-weight: 800;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--pet-color, var(--cyan));
  color: #071018;
}

/* ============================================================
   HUD 精修：把三块常驻面板从"圆角卡片"掰成"仪表盘"
   —— 切角收小 + 左侧一条强调边。加了四角括号反而会在 34px 高
   的面板上糊成一片，所以这里只用最省空间的两招。
   ============================================================ */
.panel-life, .panel-stats, .panel-weapon {
  position: relative;
  border-radius: var(--r-sm);
}
/* 左侧强调边：HUD 上唯一一处"斜切"的视觉锚点。
   --anom-color 由 hud.js 在「统计面板」上就地写一份 —— 层数带环境时
   这条边跟着变环境色，和 .stat.anom 的下划线是同一套语言。
   没有环境时回落到 --zone-color（战区色，同样由 hud.js 写在面板上）：
   于是这条边在整层里一直替玩家回答"我这一层是什么地方"。
   （变量只向下继承，所以必须挂在面板本身，不能只挂 .stat） */
.panel-life::before, .panel-stats::before, .panel-weapon::before {
  content: '';
  position: absolute;
  left: 0; top: 7px; bottom: 7px;
  width: 2px;
  border-radius: 0 2px 2px 0;
  background: var(--anom-color, var(--zone-color, var(--cyan)));
  box-shadow: 0 0 9px var(--anom-glow, var(--zone-glow, rgba(var(--cyan-rgb), 0.6)));
  opacity: 0.7;
}

/* --- 分段开关：三档设置项（0/1/2 用 checkbox 表达不了） --- */
.seg-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.6px;
  color: var(--text-dim);
}
.seg {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: var(--r-sm);
  background: var(--sunk);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}
.seg button {
  font: 800 11px var(--font-ui);
  letter-spacing: 0.5px;
  color: var(--text-dim);
  padding: 6px 13px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  transition: color 0.14s, background 0.14s, box-shadow 0.14s;
}
.seg button:hover { color: var(--text); }
/* 选中态用实色填充 + 外发光：仪表盘上"开关拨到哪一档"必须一眼可读 */
.seg button.on {
  color: #04101f;
  background: linear-gradient(180deg, #d9f8ff, var(--cyan));
  box-shadow: 0 0 10px rgba(var(--cyan-rgb), 0.35);
}
/* 上一版的 `.row-motion`（设置行单独成行）已随开始页重排删除：
   三个开关现在同一行，窄屏靠 .start-row 的 flex-wrap 自己换行即可。 */
.pause-set {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding: 9px 12px;
  border-radius: var(--r-md);
  background: var(--glassy);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}
.pause-note {
  margin: 7px 0 12px;
  font-size: 11px;
  color: var(--text-mute);
  text-align: center;
}

/* ==================== 触屏：升级托盘贴顶，给操作区让位 ====================

   手机上没有键盘，手指必须落在屏幕上 —— "托盘挂哪儿"于是不再是无关紧要的选择：

     · 竖屏（≤640px）托盘在**底部横排**（bottom 98px），正好横跨右下两个技能键
       （冲刺 bottom 96 / 冲击波 bottom 102）；
     · 横屏（>640px 走桌面布局）托盘在**左侧竖排**，正好压住左手摇杆的起手区。

   实测（`tools/shoot.js` + `mode=mobile` 的命中测试）：改前两个技能键的**中心**
   命中的都是 `.up-card` 里的文字 —— 玩家想按冲刺/冲击波，按下去变成选卡，
   一次操作被另一次吃掉。`.hud` 的 z-index(10) 高于触屏层(6)，托盘本来就压在
   触屏层之上，所以这不是"点不到"，而是"点错了东西"。

   桌面端不存在这个问题（操作全在键盘上，托盘最多挡住机体，已由 `.ghost` 让位
   那套处理），所以整套只在 `body.is-touch` 下生效。

   分区原则：**上 = 信息与选择，下 = 操作**。托盘贴顶、横排；摇杆与两个技能键
   留在下半屏，两者不再争同一块像素。`top` 取在 BOSS 血条（视口 76~126px /
   窄屏 102~152px）之下，避免升级时正好 BOSS 在场、两块信息叠在一起。
   托盘空白处是 pointer-events:none，会穿到触屏层 —— 摇杆照旧"点哪起哪"。 */
body.is-touch .up-tray {
  left: 8px;
  right: 8px;
  width: auto;
  top: 130px;
  bottom: auto;
  margin: 0;                    /* 基类的 auto 外边距在 top 定死后失去意义 */
  transform: translateY(-14px);
}
body.is-touch .up-tray.show { transform: translateY(0); }

/* 横排：横屏的基线是桌面端那份竖排，这里必须一并改成横排 */
body.is-touch .up-cards { flex-direction: row; gap: 5px; }
body.is-touch .up-card {
  flex: 1 1 0;
  min-width: 0;
  grid-template-columns: 1fr;
  justify-items: center;
  gap: 4px;
  padding: 6px 5px 7px;
  text-align: center;
  border-left-width: 1px;
  border-top: 3px solid rgba(var(--rc), 0.85);
  /* 点按选卡：去掉 iOS 的 300ms 点击延迟；viewport 已锁缩放，这层是保险 */
  touch-action: manipulation;
}
body.is-touch .uc-ico { width: 28px; height: 28px; }
body.is-touch .uc-top { flex-direction: column; align-items: center; gap: 1px; }
body.is-touch .uc-top b { font-size: 11.5px; white-space: normal; }
/* 键盘键帽在触屏上没有意义（hud.js 会把整行文案换成"点按卡片选择"，这里兜底） */
body.is-touch .uc-key { display: none; }
body.is-touch .up-head { justify-content: center; }
body.is-touch .up-head kbd { display: none; }

/* 触屏有实体按钮，键盘用的那两个冷却环不再显示 */
body.is-touch .dash-wrap { display: none; }

/* 触屏上"让位"不能让到看不见。
   桌面端 0.16 是为了把机体让出来做微操 —— 托盘在左、机体在屏幕正中，两者本来
   分居，只有走位贴过去时才短暂让位。手机屏幕小，而相机把机体**永远**钉在正中：
   矮屏（横屏 390、小屏竖屏 650）上托盘伸进让位区之后就不会再出来，0.16 读起来
   就是"托盘一直是灰的、卡名看不清"，等于没有托盘。
   0.5 仍能看见机体轮廓，卡片文字也读得出；图标一并保留（认卡主要靠它，
   描述在矮屏已经被压掉了）。主流竖屏 844 高根本不进让位区，这一条碰不到。 */
body.is-touch .up-tray.ghost { opacity: 0.5; }
body.is-touch .up-tray.ghost .uc-ico { opacity: 1; }

/* 宽屏触屏（横屏手机 / 平板）：托盘靠左限宽。
   横屏时屏幕矮，托盘横铺整宽会一路顶到右下两个技能键。垂直方向已经让不开
   （顶部依次被 HUD 顶部条和 BOSS 血条占住），那就从**横向**避让：
   右边留出按钮带（冲刺键左边沿约 220px + 安全边距）。
   顺带解决一个美观问题 —— 不限宽时 3 张卡会被拉成一张 276px 宽，
   而卡片内容其实只有图标 + 名字。 */
@media (min-width: 641px) {
  body.is-touch .up-tray {
    right: auto;
    width: min(720px, calc(100% - 248px));
  }
}

/* ============================ 响应式 ============================ */

@media (max-width: 640px) {
  .feat { grid-template-columns: 1fr; }
  .cards { grid-template-columns: 1fr; }
  .card { padding: 12px; text-align: left; display: grid; grid-template-columns: 42px 1fr; gap: 0 10px; align-items: center; }
  .card .ico { width: 42px; height: 42px; margin: 0; grid-row: span 2; }
  .card b { margin-bottom: 3px; }
  .card .key-num { display: none; }
  .sheet { padding: 20px 16px; }
  .result { grid-template-columns: repeat(2, 1fr); }
  .dash-wrap { display: none; }

  /* 窄屏：三块 HUD 按比例分配宽度，保证右上按钮不被挤出屏幕 */
  .hud-top { gap: 7px; }
  .panel-life { flex: 1 1 36%; width: auto; min-width: 0; padding: 7px 9px 8px; }
  .panel-stats {
    flex: 1 1 42%;
    min-width: 0;
    margin: 0;
    padding: 6px 9px;
    /* 4 个统计项排成 2×2，避免竖排 4 行把 BOSS 血条顶下去 */
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px 10px;
    align-content: center;
  }
  .hud-btns { flex: 0 0 auto; gap: 6px; }
  .icon-btn { width: 34px; height: 34px; }
  .stat { display: flex; justify-content: flex-start; align-items: baseline; gap: 5px; min-width: 0; }
  .stat b { font-size: 12.5px; }
  .stat em { font-size: 9.5px; }
  .bar span { font-size: 9px; }
  .panel-weapon { max-width: 58vw; padding: 7px 11px 7px 8px; }
  .wpn-icon { width: 28px; height: 28px; }
  .wpn-meta b { font-size: 12px; }
  .buff { width: 32px; height: 32px; }
  /* 窄屏：契约栏收窄，说明文字压到一行能读 */
  .pact { margin-top: 9px; padding: 9px 10px; gap: 8px; }
  .pact .pact-ico { width: 30px; height: 30px; }
  .pact .pact-body b { font-size: 12.5px; }
  .pact .pact-body em { font-size: 10.5px; }
  .pact .pact-flag { font-size: 9.5px; padding: 3px 7px; }
  .pact-chip { padding: 4px 9px; }
  .pact-chip b { font-size: 10px; }
  /* 窄屏左下角要塞三块（武器 / 契约 / 无人机），全部收窄 */
  .pet-chip { padding: 4px 8px 4px 9px; gap: 4px; }
  .pet-chip b { font-size: 10px; }
  .pet-chip em { font-size: 9px; padding: 1px 5px; }
}

@media (max-width: 640px) {
  /* 机库：窄屏一列一个，卡片转成横排（图标在左），三列并排的话每列只剩约 100px */
  .hangar-row { grid-template-columns: 1fr; gap: 7px; }
  .chk { flex-direction: row; gap: 10px; padding: 9px 12px; text-align: left; }
  .chk-ico { flex: 0 0 auto; }
  .chk-body { align-items: flex-start; }
  .chk-stat, .chk-lock { text-align: left; }

  /* 窄屏下统计栏是竖排两行，血条要下移到它下面才不挡数值 */
  .boss-bar { top: 92px; width: 80vw; padding: 7px 11px 9px; }
  #bossName { font-size: 11.5px; }
  .boss-track { height: 8px; }

  /* 升级托盘：窄屏左侧那条 268px 会吃掉 70% 屏宽，改成贴在技能环上方的横排。
     横排每张只剩约 100px，描述留两行截断——比"只给个名字"强，
     但仍然比桌面端短，所以窄屏下更要靠图标 + 名称先认出来。 */
  /* 升级托盘 · 窄屏重做（2026-10-20）
     ------------------------------------------------------------
     改前是「底部横排三张，每张 1/3 宽」：375 屏上每张只剩约 110px，
     名字折成两行、描述被 -webkit-line-clamp 截掉一半，而且整条压在
     摇杆与两个技能键的上沿（bottom:98px）——
     玩家原话：「放在下面看不清楚」。

     为什么不是"把它做得更大"：横排的宽度是**除出来的**，屏幕越窄每张越小，
     越努力越小。所以窄屏改成**竖排全宽**：一张卡一整行，图标 / 名字 / 描述
     横着排开，每行高度约 46px，三行合计 150px —— 比原来还矮，但每个字都放得下。

     位置也从底部挪到**屏幕上三分之一**（top 由 JS 量出的 HUD 底决定，见
     measureTray）。理由：底部那块永远和拇指、摇杆、技能键抢像素；
     而升级是一个"现在就要处理"的提示，它该出现在视线自然落点偏上的位置。 */
  .up-tray {
    left: 8px; right: 8px; width: auto;
    top: 156px; bottom: auto;
    margin: 0;                  /* 归位：基类的 auto 外边距在 top 定死后无意义 */
    transform: translateY(-10px);
    gap: 6px;
  }
  .up-tray.show { transform: translateY(0); }
  /* ★ 竖排：flex-direction 在窄屏下保持 column（基类就是 column），
     这里显式写一遍是为了压掉下方 body.is-touch 的横排规则 ——
     那条规则的选择器权重是 (0,2,0)，本条是 (0,1,0)，所以必须带上 body 前缀。 */
  body .up-cards { flex-direction: column; gap: 6px; }
  body .up-card {
    grid-template-columns: 34px 1fr auto;
    justify-items: stretch;
    text-align: left;
    gap: 10px;
    padding: 8px 10px;
    border-left-width: 4px;
    border-top-width: 0;
  }
  body .up-card:hover { transform: none; }
  body .uc-ico { width: 34px; height: 34px; }
  /* 名字与描述回到"横着读"：两个字的名字 + 一行描述，是手机上最快的读法 */
  body .uc-top { flex-direction: row; align-items: baseline; gap: 6px; }
  body .uc-top b { font-size: 14px; white-space: nowrap; }
  body .uc-desc {
    font-size: 12px;
    -webkit-line-clamp: 2;
    line-height: 1.4;
  }
  /* 键帽在窄屏不显示（触屏本来就没有键盘），但**右端要留出选择落点**：
     数字改成一枚圆形序号，告诉玩家"这是第几张"，同时给整行一个视觉重心。 */
  body .uc-keys { display: flex; }
  body .uc-key { display: grid; width: 22px; height: 22px; font-size: 11px; }
  body .uc-key.alt { display: none; }
  .up-head { justify-content: flex-start; }

  /* 成就横幅在窄屏要落在 BOSS 血条（top 92~152）之下 ——
     15% 在 640 宽的屏上只有约 110px，正好被血条压住。
     这一条只挪位置，其他（徽章大小、字号）不动：小屏本来就该少改。 */
  .ach-toast { top: calc(164px + var(--safe-t)); }

  /* 触屏 + 竖屏：窄屏的 BOSS 血条在 top 92（视口 102~152），托盘要落在它下面 */
  body.is-touch .up-tray { top: 158px; }
}

/* 矮窗口（1280×600 这类被压扁的浏览器窗口，视口高 510 上下）：
   按钮此时已经可见，但收尾的「展开特性」那一行会掉到折线以下 —— 折叠器只露半截
   比完全不露更像没做完。这里只动间距，不动字号与内容。 */
@media (max-height: 620px) {
  .sheet-start { padding: 16px 20px 14px; }
  .logo { margin-bottom: 11px; }
  .facts { margin-bottom: 10px; gap: 6px; }
  .fact { padding: 4px 9px; font-size: 11px; }
  .hangar { margin-bottom: 11px; padding: 9px 10px 10px; }
  .hangar-head { margin-bottom: 6px; }
  .chk { padding: 8px; }
  .best { margin-top: 7px; }
  .keys { margin-bottom: 10px; }
  .start-row { gap: 12px; }
  .more { margin-top: 10px; padding-top: 9px; }
}

/* 高度不够时的两档压缩。
   判据只有一条：**托盘下缘 + 46px 余量 < 屏幕中心**。
   相机把机体钉在屏幕正中，托盘一旦伸进中心附近，已有的 `.ghost` 让位就会被
   持续触发 —— 玩家看到的是一条"一直很淡、一直读不清"的托盘。桌面端不会这样
   （托盘在左、机体在中心，两者本来就分居），手机竖屏的 844 高也不会（余量够）。
   压缩只砍描述文字：图标 + 名称足以认出卡，而"能看清卡片"比"读得到描述"优先。 */
@media (max-height: 700px) {
  body.is-touch .up-head { padding: 3px 8px; }
  body.is-touch .uc-desc { display: none; }
}

/* 横屏手机（真机视口高约 390，被浏览器 UI 一挤只剩 300 上下）：
   抬到玩家视野之上，并且**连头部一起收掉**（只留卡片行）—— 这一档高度只剩
   390，任何多余的一行都会把托盘推回屏幕中心。代价是读不到 Lv 与"还有几次"，
   换来的是"卡片真的看得清"。 */
@media (max-height: 520px) {
  body.is-touch .up-tray { top: 76px; }
  body.is-touch .up-head { display: none; }
  body.is-touch .up-card { padding: 5px 6px 6px; }
  body.is-touch .uc-desc { display: none; }
}

/* 极矮视口（横屏手机、被压扁的窗口）：开始页只保留"标题 + 机库 + 开始"这条主链，
   键位与特性墙一起收掉。改前这里写的是 `.feat`（那时特性墙是常驻的），
   现在特性墙已经折进 <details>，要收就该收整个折叠器，否则会留下一个点了没反应的"展开" */
@media (max-height: 480px) {
  .keys, .more { display: none; }
  .logo-badge { width: 40px; height: 40px; }
  .logo h1 { font-size: 22px; }
  .logo { margin-bottom: 10px; }
  .sheet-start { padding: 14px 16px 14px; }
  .best { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* ★ 必须是 `animation: none`，不能只把 duration 压到 0.01ms。
     本表里有 **14 处 `infinite`** 循环动画（血条危急脉冲 / 出界警示 /
     冲击波就绪 / 徽标呼吸 / 齿轮自转 / 托盘呼吸 / BOSS 狂暴条 …）。
     压 duration 对它们等于"以十万倍速继续循环" —— 那是**更**剧烈的动，
     比不做这条兜底还糟。归零只对一次性动画（进场 / 扫光）才是正确的关法。
     所以直接整体关掉：这条兜底是给"晕动敏感"的人用的，安静比动效重要。 */
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
  }
}
