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

/* ⚠️⚠️ 第 44 轮：`.hidden` 的全局兜底。
 * 这个项目原来**没有**全局的 .hidden 规则 —— 每个元素靠自己那条 `#id.hidden` 生效。
 *   漏写一条，JS 里 `classList.add('hidden')` 就只是加了个没人认的类名，
 *   元素照样显示（而且控制台干干净净，查不到）。第 44 轮用户报的两个 bug 都是这么来的：
 *     · #itemtip.hidden 漏了 → 「鼠标移开后详细卡片不消失」
 *     · #bag-codex.hidden 漏了 → 「点过一次典录，背包/装备页也一直挂着
 *       '本页 0/1321 件…' 那行字」
 * 所以这里补一条全局规则兜住所有情况，以后再加面板也不会重蹈覆辙。 */
.hidden { display: none !important; }

:root {
  --glass: rgba(14, 22, 30, 0.62);
  --line: rgba(255, 255, 255, 0.14);
  --txt: #eaf2f6;
  --dim: #9fb4c0;
  --gold: #ffd08a;
  /* r144：顶部公告条让位量。
     用户原话：「上端组件离屏幕上顶边边缘距离太近，如果发布公告管理，
     那么公告管理会压着前端的组件」。
     #annbar 是 net.js 运行时注入的（position:fixed;top:0;height:26px;z-index:60），
     有公告时才出现。这里用一个变量把它换算成"顶部组件要下移多少"：
       没有公告 → 0px（组件贴到正常上边距）
       有公告   → 34px（26 高的条 + 8 的呼吸缝）
     ⚠️ 变量只加在**各组件自己的 top 声明里**（写成 calc(基准 + var(--annoff))），
        不另写 `html.ann-on #minimap{...}` 这种覆盖 —— 那条会被
        `html.touch.t-h-lo #hud`（特异度更高）压回去，专治不彻底。
     ⚠️ 默认值必须写 0px：老浏览器 / 变量没生效时行为和不加变量一致。 */
  --annoff: 0px;
}
/* 公告条真的有内容时才下移（net.js 的 renderBar 负责加/摘这个类） */
html.ann-on { --annoff: 34px; }
html, body { height: 100%; overflow: hidden; background: #071018; }
body {
  font-family: -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
  color: var(--txt); user-select: none; -webkit-font-smoothing: antialiased;
}
#app { position: fixed; inset: 0; }
#view { display: block; width: 100%; height: 100%; cursor: grab; }
#view:active { cursor: grabbing; }

/* 暗角 + 轻色调，纯 CSS 的“后期处理” */
#vignette {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(120% 90% at 50% 45%, rgba(0,0,0,0) 42%, rgba(0,0,0,0.42) 100%),
    linear-gradient(180deg, rgba(80,150,200,0.06), rgba(255,180,120,0.05));
  mix-blend-mode: multiply;
}

/* 闪电：整屏白一下。opacity 由 JS 直接写，不加 transition（闪电必须是瞬间的） */
#flash {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(70% 55% at 50% 35%, rgba(255,255,255,.95), rgba(200,220,255,.5) 70%, rgba(180,200,255,0) 100%);
  z-index: 12;
}

/* 进入新区域：屏幕中央横一条字，上下各一条细线，像原神那样 */
#region {
  position: absolute; left: 0; right: 0; top: 27%;
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  pointer-events: none; z-index: 14; opacity: 0;
}
#region.show { animation: regionIn 3.6s ease-out forwards; }
#region.hidden { display: none; }
.rg-line {
  width: 0; height: 1px;
  background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,236,196,.85), rgba(255,255,255,0));
}
.rg-name {
  font-size: 30px; font-weight: 300; letter-spacing: 10px; color: #fff6e2;
  text-shadow: 0 2px 18px rgba(0,0,0,.7), 0 0 40px rgba(255,214,150,.35);
}
.rg-sub { font-size: 12px; letter-spacing: 4px; color: rgba(255,240,214,.72); text-shadow: 0 1px 8px rgba(0,0,0,.7); }
@keyframes regionIn {
  0%   { opacity: 0; transform: translateY(14px); }
  8%   { opacity: 1; transform: translateY(0); }
  14%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-8px); }
}
#region.show .rg-line { animation: rgLine 3.6s ease-out forwards; }
@keyframes rgLine {
  0% { width: 0; opacity: 0; }
  12% { width: 260px; opacity: 1; }
  80% { width: 260px; opacity: 1; }
  100% { width: 180px; opacity: 0; }
}

/* 加载 */
#loading {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px;
  background: radial-gradient(60% 60% at 50% 40%, #123243, #050b12 80%);
  z-index: 40; transition: opacity .6s;
}
#loading.gone { opacity: 0; pointer-events: none; }
/* ⚠️⚠️ r157：加载遮罩**盖不住比它层级高的常驻层**（用户报的："PC 端在 loading 的时候，
   右上角还会显示之前的残留：admin 管理员 邮箱已验证；右下角也还会显示之前的聊天框，
   loading 结束后就消失了"）。
   **实测（probe_boot_leak.py 打在线上，两个阶段、两个不同的泄漏）：**
     · 0~11 秒：`html.class` **还是空的**（touch.js / main.js 都还没执行完），于是
       `html.touch #accbar{display:none}` 这条根本没生效 → #accbar(z=60) 露出，
       里面那颗 `button.acc-chip.acc-mail`「邮箱未验证 · 点此解锁」就是用户看到的"残留"。
     · 82 秒（世界还在加载）：#accbar 已被 touch 规则关掉，但 **#mulayer 漏着** ——
       它 z=70 且挂在 `<body>` 上，比较对象是 `#app(z=65)`，遮罩在它下面，
       **抬 #loading 的 z 一辈子也没用**（那是两个堆叠上下文之间的比较）。
   正解：`booting` **静态写在 index.html 的 <html> 上**（0 秒就生效，不依赖 JS 跑起来），
   进世界时在 main.js 里跟 `loading.gone` **同一行**摘掉。
   ⚠️ 为什么不能只靠"把 #loading 的 z 调高"：#accbar / #pcbtns 在 #app 内部还压得住，
      #mulayer / #annbar 在 body 上，压根不在同一个堆叠上下文里。
   ⚠️ #annbar（公告条，net.js 注入、fixed z=60）一起藏：它同样会浮在加载页上，而加载页
      应该只有遮罩 + 进度条。它跟 --annoff 无关（那个由 html.ann-on 类控制）。 */
html.booting #accbar, html.booting #pcbtns,
html.booting #mulayer, html.booting #annbar { display: none !important; }
.lg-title { font-size: 44px; letter-spacing: 12px; font-weight: 300; color: #eaf6ff; }
.lg-sub { color: var(--dim); font-size: 13px; letter-spacing: 2px; }
/* ⚠️⚠️ r130：加载条改成「零布局」写法 —— 这是"别让进度条拖慢加载"的核心。
   以前：`.lg-bar i` 的进度用 `style.width` 推，而 width 是**布局属性** ——
        全流程 490 个文件，就是 490 次强制布局，而这 490 次全都发生在
        「主线程正在解 GLB / 建世界」的那段时间里，纯属抢资源。
   现在：进度只用 `transform: scaleX()` —— 不触发布局、不进主线程，
        交给合成线程，代价接近 0。
   · `contain` 把这条 3px 高的条子从父级布局里摘出去，改它绝不牵动 #loading。
   · 扫光挪到 ::after：它只动 transform，可以独立合成；
     ⚠️ 并且 `.gone` 之后**必须停掉** —— 以前 `#loading.gone { opacity: 0 }` 只是
     让它看不见，动画一直在跑，进游戏后还在为一个透明元素做合成。 */
.lg-bar {
  position: relative; width: 240px; height: 3px;
  background: rgba(255,255,255,.12); border-radius: 2px; overflow: hidden;
  contain: layout style paint;
}
.lg-bar i {
  display: block; height: 100%; width: 100%;
  background: var(--gold);
  transform: scaleX(.12); transform-origin: left center;
  will-change: transform;
}
.lg-bar::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 30%;
  background: rgba(255,255,255,.4); pointer-events: none;
  animation: slide 1.4s infinite ease-in-out;
}
@keyframes slide { 0% { transform: translateX(-120%); } 100% { transform: translateX(420%); } }
#loading.gone .lg-bar::after { animation: none; opacity: 0; }
/* r126：实时下载速度。min-height 占位 —— 没有它，文本一出现/消失会把下面整块顶得一跳。
   tabular-nums 让数字等宽，否则 4.2 → 12.8 这种位数变化会让整行左右抖。 */
.lg-spd {
  min-height: 16px; font-size: 12px; letter-spacing: 1px;
  color: var(--dim); opacity: .78; font-variant-numeric: tabular-nums;
}
/* r193：素材走加速源时的提示。
   ⚠️⚠️ 只说「加速中」—— **不许**提节点 / 源 / 机房的任何名字。
     玩家只关心"快不快"，不关心字节从哪来；那种信息也不该出现在玩家界面上
     （它既没有可操作的意义，又等于把基础设施摆到了台面上）。
   ⚠️ 默认 `display:none`：走同源时**连空白都不留**，完全保持原样。
     只有真的在加速时才插进来（此时顶一下版面是可以接受的 —— 那是好消息）。 */
.lg-boost {
  display: none; margin-top: 4px; font-size: 12px; letter-spacing: 1px;
  color: var(--gold);
}
.lg-boost.on { display: block; }

/* 常驻小地图：左上角。以前地图只在按 M 时弹出来，
   走着走着就不知道自己在岛的哪一边了 —— 现在它一直在那儿。 */
/* ⚠️ 宽度必须是「画布 176 + 左右 padding 8×2 + 左右边框 1×2 = 194」：
   box-sizing 是 border-box，写 192 的话内容区只剩 174，176 的画布横向溢出 2px
   （用户报的"左上角小地图也有滚动条"就是这个 2px —— 差一点点最难发现）。 */
#minimap {
  position: absolute; left: 14px; top: calc(12px + var(--annoff, 0px)); width: 194px; z-index: 18;
  padding: 8px; background: var(--glass); border: 1px solid var(--line);
  border-radius: 12px; backdrop-filter: blur(8px);
  /* ⚠️ 第 45 轮：桌面端原来是 `pointer-events: none`（纯显示），
     于是**左键点小地图完全没反应**（用户报的 bug）—— 手机上能点开大地图，
     桌面反而不能。现在桌面也让它可以点（cursor 跟着变手型，给到可点的暗示）。 */
  pointer-events: auto; cursor: pointer;
  overflow: hidden;
}
#minimap:hover { border-color: rgba(255, 208, 138, .42); }
#mmcv { width: 176px; height: 176px; display: block; border-radius: 8px; background: #0b1a24; }
.mm-cap {
  display: flex; justify-content: space-between; gap: 8px;
  font-size: 11.5px; margin-top: 6px; color: var(--dim); font-variant-numeric: tabular-nums;
}
.mm-cap b { color: var(--gold); font-weight: 600; }
/* 群系名和坐标都可能变长（"雾岭冷杉林" / "-12345, 6789"），不许把卡片撑宽 */
.mm-cap > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mm-buff { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.mm-buff:empty { margin-top: 0; }
.buff {
  display: flex; align-items: center; gap: 4px; font-size: 11px;
  padding: 2px 7px; border-radius: 20px; color: var(--txt);
  background: rgba(255,255,255,.07); border: 1px solid var(--line);
}
.buff i { font-style: normal; }
.buff b { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--gold); }

/* ------------------------------------------------------------------
   第 32 轮：常驻状态栏（小地图下方一列）。
   位置从"左下角一张小卡片"改成"左上角小地图正下方一列"——
   用户的原话是"页游屏幕小，状态栏越干净越好"，所以：
     · 只放一眼要看的东西：等级 / 经验 / 生命 / 能量 / 体力 / 护盾 / Buff
     · **攻击力、防御力这些一律不常驻**（在 C 面板里查）
     · 时间天气坐标延迟 FPS 压成一行小字
   ------------------------------------------------------------------ */
#hud {
  position: absolute; left: 14px; top: calc(246px + var(--annoff, 0px)); width: 194px; z-index: 17;
  padding: 9px 10px 8px;
  background: var(--glass); border: 1px solid var(--line); border-radius: 12px;
  backdrop-filter: blur(8px); font-size: 11.5px; line-height: 1.4;
  pointer-events: none;
}
.hud-top { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.hud-face {
  width: 30px; height: 30px; flex: 0 0 30px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; color: #0d1620; font-weight: 700;
  background: linear-gradient(140deg, var(--gold), #e0a95c);
}
.hud-id { flex: 1; min-width: 0; }
/* 第 33 轮 QA B-06：11px → 12px。「Lv.1 旅人」这行是玩家自我认知的核心信息，
   11px 在 1080p 上真的看不清。 */
.hud-lv { font-size: 12px; color: var(--dim); display: flex; align-items: baseline; gap: 4px; }
.hud-lv b { color: var(--gold); font-size: 13px; font-variant-numeric: tabular-nums; }
.hud-lv span {
  color: var(--txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ── 第 48 轮：这一行改成**账号入口**（点它弹出账号菜单 / 退出登录） ──
   ⚠️ #hud 整体是 `pointer-events:none`（它只是信息条，不该挡住鼠标看世界），
      所以这个入口必须**单独开 auto**，否则点了完全没反应（且没有任何报错）。
   名称最长 96px（手机窄栏 52px），超出省略 —— 名字再长也不会把等级挤出框。 */
.hud-lv.acc {
  pointer-events: auto; cursor: pointer; user-select: none;
  padding: 1px 4px; margin: -1px -4px; border-radius: 6px;
  transition: background .15s, color .15s;
}
.hud-lv.acc:hover { background: rgba(255, 255, 255, .10); }
.hud-lv.acc.on { background: rgba(255, 255, 255, .16); }
.hud-lv.acc .acc-arrow {
  font-style: normal; font-size: 9px; opacity: .5; margin-left: auto; flex: 0 0 auto;
  align-self: center; transition: opacity .15s;
}
.hud-lv.acc:hover .acc-arrow, .hud-lv.acc.on .acc-arrow { opacity: 1; }
#hud-name { max-width: 96px; }
html.touch #hud-name { max-width: 52px; }
/* 四条资源条共用一套骨架 */
.hud-bar {
  position: relative; display: flex; align-items: center; gap: 5px;
  height: 15px; margin: 3px 0;
}
/* 图标列（❤🔮🫁）：固定 12px 宽的槽，emoji 天然画得比文字小，
   10px 在 Windows 上会糊成一团 → 提到 12px（槽宽同步放到 14px，不然会被挤）。 */
.hud-bar > span { width: 14px; text-align: center; font-size: 12px; flex: 0 0 14px; }
.hud-bar > i {
  flex: 1; height: 7px; border-radius: 4px; overflow: hidden;
  background: rgba(255,255,255,.10); position: relative;
}
.hud-bar > i::after {
  content: ""; position: absolute; inset: 0; width: var(--w, 100%);
  border-radius: 4px; transition: width .16s linear;
}
/* 第 33 轮 QA B-06：10px → 12px。这是**唯一**显示血/魔/体力/经验数的地方，
   却是全场最小的字。它是 position:absolute 浮在进度条右端的，不占布局。
   ⚠️ 但 12px 的行高是 17px，而 .hud-bar 只有 15px 高 —— 数字会**下沿溢出 2px**。
   所以：字号给 12px，同时把行高压到 1（=12px）并用 transform 上提1px 居中，
   这样既看得清又不会溢出容器。 */
.hud-bar > em {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  font-style: normal; font-size: 12px; line-height: 1;
  color: var(--dim); font-variant-numeric: tabular-nums;
  background: linear-gradient(90deg, transparent, var(--glass) 30%);
  padding-left: 8px;
}
/* XP 条原来是 9px 高 —— 塞不下任何可读的字号（12px 的行高就有 12px）。
   第 33 轮 QA B-06 直接加高到 13px：经验条比血条细一点是正常视觉层级，
   但「0 / 100」这个数字必须能读。13px 配line-height:1刚好装下 12px。 */
.hud-bar.xp { height: 13px; }
.hud-bar.xp > em { font-size: 12px; }
.hud-bar.xp > i { height: 4px; }
.hud-bar.xp > i::after { background: var(--gold); }
.hud-bar.hp > i::after { background: linear-gradient(90deg, #d8564e, #ff8a6a); }
.hud-bar.ep > i::after { background: linear-gradient(90deg, #4f7fd8, #7fc9f0); }
.hud-bar.sta > i::after { background: linear-gradient(90deg, #4f9d78, #8fd06a); }
.hud-bar.sh > i::after { background: linear-gradient(90deg, #8a8fd8, #c8b0ff); }
/* ⚠️ 低血量变色警告：整条变红 + 轻微呼吸，别用 animation（软渲染下会抖） */
.hud-bar.hp.low > i::after { background: linear-gradient(90deg, #ff2d2d, #ff6a4a); }
.hud-bar.hp.low { animation: hudPulse 1.1s ease-in-out infinite; }
@keyframes hudPulse { 50% { opacity: .58; } }
.hud-bar.hidden { display: none; }

/* Buff / Debuff 图标行：中毒、燃烧、减速、眩晕 */
.hud-buffs { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; min-height: 0; }
.hud-buffs:empty { margin-top: 0; }
.hud-buffs .buff { padding: 1px 5px; font-size: 10px; gap: 2px; }
.hud-buffs .buff.debuff { color: #ff9a8a; border-color: rgba(255,120,100,.42); }
.hud-buffs .buff.debuff b { color: #ff9a8a; }

.hud-foot {
  display: flex; flex-wrap: wrap; gap: 3px 8px; margin-top: 7px; padding-top: 6px;
  border-top: 1px solid var(--line);
  /* 第 33 轮 QA：B-06 实测这行是 10.5px，1920×1080 下读数吃力 →提到 12px。
     顺带把下面几处一并提上来（都是持续显示的读数，不是装饰）。 */
  font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums;
}
/* 第 48 轮：版本号。发版后一眼看出「这台设备跑的是不是最新版」——
   不同设备缓存不一致是长期痛点（用户报的 bug 5）。
   落后时变琥珀色 + 可点（点了带 cache-bust 重新载入）。
   ⚠️ #hud 是 pointer-events:none，所以 .stale 必须显式开 auto，否则点了没反应。 */
#hud-ver { opacity: .8; letter-spacing: .2px; }
#hud-ver.stale { color: var(--gold); opacity: 1; font-weight: 600; pointer-events: auto; cursor: pointer; }
#hud-ver.err { opacity: .55; }
/* 第 39 轮：移动状态读数。奔跑时点亮成金色 —— 一眼确认"我正在跑" */
#hud-spd { min-width: 52px; display: inline-block; transition: color .12s; }
#hud-spd.on { color: var(--gold); font-weight: 600; }
/* 第 54 轮：力竭状态要看得见 —— 否则「体力满却跑不动」在玩家眼里就是 Shift 坏了 */
#hud-spd.exh { color: #ff9a9a; font-weight: 600; opacity: 1; }
/* r144：原「喇叭」#hud-snd 三行 + @keyframes sndBlink 已删。
   删除原因：#hud 整块 pointer-events:none，#hud-set 写回了 auto 所以齿轮点得动，
   而 #hud-snd **没写** → PC 上从来点不动（用户反馈「好像没用」属实）。
   静音走快捷键 N 即可，不再留一个假按钮。 */
.hud-gpu { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line); }
.hud-gpu.hidden, .hud-dbg.hidden { display: none; }
.hud-gpu span { color: var(--dim); font-size: 10px; max-width: 172px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.hud-gpu.ok span { color: #8fe08f; }
.hud-gpu.warn span { color: #ffbe5c; }
.hud-dbg { margin-top: 4px; }
.hud-dbg span { color: #9ae89a; font-size: 12px; font-variant-numeric: tabular-nums; }

/* r103：画质设置面板本体（独立浮层）。
   ⚠️ r144：**入口**从 HUD 底行那颗 ⚙（#hud-set，连带它的 CSS 一起删了）
      搬到右上角功能菜单（touch.js 的 BTNS 的 gfx 项，键盘 T）—— 用户要求
      「画质调整的功能也放在菜单里，pc端和手机端一样」。
      面板本身没动，只是少了一个入口、多了一个入口。 */
#setpanel {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 80; pointer-events: auto;
}
#setpanel.hidden { display: none; }
#setpanel .sp-card {
  min-width: 236px; padding: 10px 10px 8px;
  background: rgba(16, 20, 26, .94); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .5);
  backdrop-filter: blur(6px);
}
#setpanel .sp-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 13px; color: var(--txt, #e8e2d2); font-weight: 600;
  padding-bottom: 7px; margin-bottom: 5px; border-bottom: 1px solid var(--line);
}
#setpanel .sp-head b { cursor: pointer; color: var(--dim); font-weight: 400; padding: 0 3px; }
#setpanel .sp-head b:hover { color: #ff9a6b; }
#setpanel .sp-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 7px 6px; border: 0; border-radius: 6px;
  background: transparent; color: var(--dim); font-size: 12px; text-align: left;
  cursor: pointer; font-family: inherit;
}
#setpanel .sp-row:hover { background: rgba(255, 255, 255, .07); color: var(--txt, #e8e2d2); }
#setpanel .sp-row em {
  font-style: normal; color: var(--gold, #e8c98a); font-weight: 600;
  min-width: 42px; text-align: right; font-variant-numeric: tabular-nums;
}
#setpanel .sp-row.off em { color: #8b8b8b; }
/* r116：每项下面那行小字（说明它到底影响什么）。用 em 是为了不额外加标签名，
   所以要把 em 的“值样式”圈到直接子元素上，避免和小字抢。 */
#setpanel .sp-row > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
#setpanel .sp-row em.sp-sub {
  display: block; font-style: normal; font-weight: 400; min-width: 0; text-align: left;
  font-size: 10.5px; line-height: 1.35; color: var(--dim); opacity: .62;
}
#setpanel .sp-tip {
  margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line);
  font-size: 10.5px; line-height: 1.5; color: var(--dim); opacity: .82;
}

/* 目标信息条：指着怪或可交互物时出现 */
/* 目标信息条（r138 第 5 步扩写）
 * 原来只有 名字/等级/血条/状态 三行；现在加了头像圆 + 蓝/体力两条。
 * 桌面仍是**顶部居中**（准星上方，不挡视野）；手机端挪到头像盘右侧，
 * 兑现用户那句「在头像右侧还会出现对方的头像以及对方的血量、蓝、体力、等级」。
 * ⚠️ 怪没有蓝/体力这两项资源 —— 两条先按 0 长度画着（.tg-ep / .tg-sta 的 --w=0%），
 *   将来接上「锁定玩家」时直接就有数据，不用再改结构。 */
#target {
  position: absolute; left: 50%; top: 16px; transform: translateX(-50%);
  width: 210px; padding: 7px 11px 8px; z-index: 17;
  display: flex; align-items: center; gap: 8px;
  background: var(--glass); border: 1px solid var(--line); border-radius: 10px;
  backdrop-filter: blur(8px); pointer-events: none;
}
#target.hidden { display: none; }
/* 锁定态：描边变金 + 微微发光，让玩家一眼分清"路过看到的"和"我盯上的" */
#target.locked {
  border-color: rgba(255, 208, 138, .62);
  box-shadow: 0 0 0 1px rgba(255, 208, 138, .18), 0 4px 16px rgba(0, 0, 0, .42);
}
.tg-face {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .16);
}
.tg-main { flex: 1 1 auto; min-width: 0; }
/* ⚠️ r138 实测：不加 flex-wrap + nowrap 的话，中文会在**字与字之间**断开 ——
   截图里「滩涂野猪」被拆成「滩涂野 / 猪」、「Lv.2 凶悍」拆成「Lv.2 凶 / 悍」，
   比换行难看得多。现在：名字整块不折，等级整块换到第二行。 */
.tg-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 7px; margin-bottom: 4px; }
.tg-head b { color: var(--txt); font-size: 12px; font-weight: 600; white-space: nowrap; }
.tg-head span { font-size: 10.5px; color: var(--gold); white-space: nowrap; }
.tg-hp { height: 7px; margin: 0; }
.tg-hp > i::after { background: linear-gradient(90deg, #c0403a, #e8705a); }
/* 蓝 / 体力：细一档，跟血条区分开（血是判断"还要打几下"的主信息） */
.tg-ep, .tg-sta { height: 4px; margin: 3px 0 0; opacity: .85; }
.tg-ep > i::after { background: linear-gradient(90deg, #4f7fd8, #7fc9f0); }
.tg-sta > i::after { background: linear-gradient(90deg, #4f9d68, #8fd06a); }
/* ⚠️ .mono = 这个目标没有蓝/体力（怪、道具箱、村民都是）。
   不藏起来的话，两条永远 0 长度的灰槽只会让人以为界面画坏了 —— 实测截图就是这样。
   将来接「锁定玩家」时去掉 .mono，三条自然都出来，**不用改结构**。 */
#target.mono .tg-ep, #target.mono .tg-sta { display: none; }
.tg-st { font-size: 11px; color: var(--dim); margin-top: 4px; min-height: 0; }
.tg-st:empty { margin-top: 0; }
/* ── 手机端：挪到左上头像盘右侧 ──
 * ⚠️ 这里必须写死数字，不能写 calc(var(--hw) + …)：--hw 定义在 #hud 上，
 *   #target 是 #hud 的**兄弟**，继承不到。横屏 88 / 竖屏 104 各一档，
 *   和 style.css 里 .tzone-sys 的做法一致。 */
/* ⚠️⚠️ r138 F 方案：状态卡从「88px 圆盘」变成了「78 头像 + 6 缝 + 104 条列 = 188」的横块，
   目标条原来的 left（= 8+88+10 = 106）会**正好压在三条资源条上** —— 必须整体右移。
   横屏：8 + 78 + 6 + 104 + 10 = 206。
   竖屏横着放不下（条列右沿 216、右上 ☰ 左沿 244，中间只剩 28px），
   所以竖屏改成**挂在小地图正下方**（right 对齐），见下面的 portrait 段。 */
html.touch #target {
  left: calc(8px + 78px + 6px + 104px + 10px); top: calc(env(safe-area-inset-top, 0px) + 8px + var(--annoff, 0px));
  transform: none;
  /* 150px：内宽 = 150−16(padding)−28(头像)−6(gap) = 100，
     刚好放下「滩涂野猪」+「Lv.2 凶悍」一行（44+5+45 ≈ 94）。
     132px 时内宽只有 82 → 中文会在字间断开（实机截图踩到）。 */
  width: 150px; padding: 5px 8px 6px; gap: 6px;
  border-radius: 9px;
  z-index: 26;                 /* 压过 #hud(25)，免得被圆环的边缘蹭到 */
}
html.touch .tg-face { flex: 0 0 28px; width: 28px; height: 28px; font-size: 15px; }
html.touch .tg-head { gap: 0 5px; margin-bottom: 3px; }
html.touch .tg-head b { font-size: 11px; }
html.touch .tg-head span { font-size: 9px; }
html.touch .tg-hp { height: 6px; }
html.touch .tg-ep, html.touch .tg-sta { height: 3px; margin-top: 2px; }
html.touch .tg-st { font-size: 9.5px; margin-top: 3px; }
/* 竖屏：头像盘 104px → 目标条从 122 起；但右上功能键（☰）在 242~288，
   所以宽度只能给到 112（= 122..234，留 8px 缝）。
   112 装不下「名字 + Lv.x 等级」一行 → .tg-head 本来就会把它们分成两行
   （见上面 flex-wrap + nowrap 的注释），这里只需再夹一下名字宽度。 */
/* 竖屏：条列右沿 216（8+86+6+116）、右上 ☰ 左沿 244 —— 中间只有 28px，横着放不下。
   改挂**小地图正下方**：小地图 84 宽 + top 8 + 高 ≈85 → 底 93，目标条从 102 起，
   x 256..380。这块区域在竖屏是空的（右侧抽屉拉手在垂直中线 y≈422，战斗键在底部）。 */
@media (orientation: portrait) {
  html.touch #target {
    left: auto;
    right: max(10px, env(safe-area-inset-right));
    top: calc(env(safe-area-inset-top, 0px) + 102px + var(--annoff, 0px));
    width: 124px;
  }
  html.touch .tg-head b { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
}

/* 第 33 轮 QA B-06：11px → 12px。已确认有 flex-wrap，1080p 下换行也不挤，
   反而是 11px 在高分屏上真的看不清。 */
#keys {
  position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
  display: flex; flex-wrap: wrap; gap: 6px 10px; justify-content: center;
  max-width: 94vw; font-size: 12px; color: var(--dim);
  background: var(--glass); border: 1px solid var(--line); border-radius: 10px;
  padding: 7px 14px; backdrop-filter: blur(8px);
}
#keys span::before { content: "· "; opacity: .5; }
#keys span:first-child::before { content: ""; }

/* 发现卡片 */
#card {
  position: absolute; left: 50%; bottom: 76px; transform: translate(-50%, 20px);
  width: min(420px, 88vw); padding: 14px 18px; text-align: center;
  background: linear-gradient(180deg, rgba(20,34,46,.92), rgba(10,18,26,.92));
  border: 1px solid rgba(255, 208, 138, .38); border-radius: 14px;
  box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.04) inset;
  opacity: 0; transition: opacity .35s, transform .35s; z-index: 20;
}
#card.show { opacity: 1; transform: translate(-50%, 0); }
#card.hidden { display: none; }
.card-tag {
  display: inline-block; font-size: 11px; letter-spacing: 3px; color: #0d1620;
  background: var(--gold); border-radius: 20px; padding: 2px 12px; margin-bottom: 8px;
}
.card-name { font-size: 21px; font-weight: 600; letter-spacing: 1px; }
.card-desc { font-size: 12.5px; color: var(--dim); margin-top: 6px; line-height: 1.6; }

/* 面板
 * ⚠️ 第 45 轮：z-index 从 25 提到 **70**（原来只有移动端 html.touch 覆盖成 70）。
 *   右上角账号条 #accbar 是 z-index:60，两者同在 #app 这个堆叠上下文里，
 *   于是**桌面端一开背包 / 装备，顶上那三枚标签（角色名 / 邮箱已验证 / 在线 n）
 *   就压在面板上**，正好盖住面板右上角的关闭 × 和第一排页签（用户报「被挡住」）。
 *   ⚠️ 为什么不去压 #accbar：账号条是常驻信息条，被面板盖住没损失；
 *     面板被它盖住则点不到关闭键。和 #annbar（公告条，也是 60）同一个道理。
 *   ⚠️ 70 仍低于 #itemtip(140) / #chose(130) / #gate(200)，且 #map / #atlas
 *     与面板互斥（closeAll 会先关掉对方），所以抬高不会有副作用。
 */
.panel {
  position: absolute; right: 14px; top: 12px; width: 300px; max-height: calc(100vh - 40px);
  overflow: auto; padding: 14px 16px; z-index: 70;
  background: var(--glass); border: 1px solid var(--line); border-radius: 14px;
  backdrop-filter: blur(10px);
}
.panel.hidden { display: none; }
.panel.wide { width: 372px; }          /* 地图 / 背包：340 的画布 + 边距 */

/* ══════════════════════════════════════════════════════════════════════
   第 45 轮 · 滚动条游戏化
   ──────────────────────────────────────────────────────────────────────
   装备栏 / 典录 / 属性面板里滚起来是一条**浅灰方块 + 白底凹槽**，
   一眼就是浏览器默认样式，和暗色玻璃 + 描金的界面完全不是一套东西
   （用户原话「像是网页的滚动条风格」）。

   做法：细条 + 深色内凹轨道 + 金色滑块（和 --gold 同一色系），
   hover 时提亮，圆角收口。
   ⚠️⚠️ Chromium 121+ 一旦认到标准属性 `scrollbar-width`，就会**忽略整套
      ::-webkit-scrollbar**、改用系统 overlay 滚动条（悬停才出现，平时根本看不见）。
      所以标准属性必须**只给 Firefox**（@-moz-document 是 Firefox 专属块），
      Chromium / Edge / Safari 走下面的 ::-webkit-scrollbar 自绘 ——
      自绘滚动条是**常驻**的，滚动条永远看得见，手感也更"游戏"。
   ⚠️ 用 `*` 而不是逐个容器列名单：漏一个容器就会回到白条，
      而列名单这种事在这个项目里已经漏过不止一次（见 .hidden 的教训）。
      `.cx-tabs` 自己写了 `scrollbar-width:none`（选择器优先级更高），不受影响。
   ══════════════════════════════════════════════════════════════════════ */
@-moz-document url-prefix() {
  * {
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 208, 138, .40) rgba(9, 18, 26, .55);
  }
}
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track {
  background: rgba(9, 18, 26, .55);
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px rgba(140, 190, 215, .10);
}
::-webkit-scrollbar-thumb {
  /* 2px 透明边框 + padding-box 裁剪 = 视觉上更细的滑块，但热区仍有 9px 好抓 */
  background: linear-gradient(180deg, rgba(255, 208, 138, .60), rgba(214, 160, 92, .50));
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, rgba(255, 216, 152, .92), rgba(226, 172, 100, .82));
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:active {
  background: linear-gradient(180deg, #ffd08a, #d6a05c);
  background-clip: padding-box;
}
::-webkit-scrollbar-corner { background: transparent; }
/* 面板里的滚动条再细一点：372px 宽的面板被 9px 吃掉 2.4% 的可视宽度 */
.panel::-webkit-scrollbar { width: 7px; height: 7px; }
.panel h2 { font-size: 14px; letter-spacing: 4px; font-weight: 500; margin-bottom: 10px; color: var(--gold); }
.tabs { display: flex; gap: 6px; margin-bottom: 10px; }
.tabs button {
  flex: 1; padding: 6px 0; font-size: 12px; color: var(--dim); cursor: pointer;
  background: rgba(255,255,255,.05); border: 1px solid transparent; border-radius: 8px;
}
.tabs button.on { color: #0d1620; background: var(--gold); font-weight: 600; }
.item {
  padding: 8px 10px; border-radius: 9px; margin-bottom: 6px; font-size: 12.5px;
  background: rgba(255,255,255,.04); border-left: 2px solid rgba(255,255,255,.12);
}
.item.got { border-left-color: var(--gold); background: rgba(255,208,138,.10); }
.item .n { font-weight: 600; }
.item .d { color: var(--dim); font-size: 11.5px; margin-top: 3px; line-height: 1.5; }
.item.lock { color: #6d8290; }
.p-foot { font-size: 11px; color: #6d8290; text-align: center; margin-top: 8px; }

/* ------------------------------------------------------------------
   第 32 轮：角色属性面板（六页六十项）。
   一行 = 图标 + 名称 + 说明 + 当前值 + 装备加成。
   "说明"用 title 挂在行上（鼠标悬停才显示）—— 常驻六十行字会把面板塞满。
   ------------------------------------------------------------------ */
.sp-tabs { flex-wrap: wrap; }
.sp-tabs button { flex: 1 1 30%; font-size: 11px; padding: 5px 2px; }
#sp-body { max-height: 52vh; overflow-y: auto; padding-right: 2px; }
.sp-row {
  display: flex; align-items: center; gap: 7px; padding: 4px 7px;
  border-radius: 7px; font-size: 12px; line-height: 1.35;
}
.sp-row:nth-child(odd) { background: rgba(255,255,255,.035); }
.sp-row > i { font-style: normal; width: 15px; text-align: center; flex: 0 0 15px; }
.sp-row > span { flex: 1; min-width: 0; color: var(--txt); }
.sp-row > b {
  color: var(--gold); font-weight: 600; font-variant-numeric: tabular-nums;
  min-width: 46px; text-align: right; flex: 0 0 auto;
}
.sp-row > em {
  font-style: normal; font-size: 10.5px; color: #8fd06a; min-width: 44px;
  text-align: right; font-variant-numeric: tabular-nums;
}
/* 顶到上限的那几条：标出来，玩家就不会一直以为还能堆 */
.sp-row.capped > b { color: #ffbe5c; }
.sp-row.capped > b::after { content: " 满"; font-size: 9px; opacity: .8; }
.sp-row.zero { opacity: .48; }
.sp-group {
  font-size: 10.5px; color: var(--dim); letter-spacing: 1px;
  margin: 7px 0 3px; padding-left: 7px; border-left: 2px solid var(--line);
}
/* r199 · 技能树（#statpanel 第 7 页「本事」）。复用 .sp-row 范式，只多一个升阶按钮。
   ⚠️ 置灰不只靠颜色：aria-disabled + 按钮自身 disabled，玩家能明确知道"点不了"。 */
.sp-row > .sk-up {
  flex: 0 0 auto; margin-left: 4px; padding: 3px 9px;
  font-size: 11px; border-radius: 7px; cursor: pointer;
  color: #0d1620; background: var(--gold); border: 0; font-weight: 600;
}
.sp-row > .sk-up[disabled] { background: rgba(255,255,255,.12); color: #8b8b8b; cursor: default; }
.sp-group:first-child { margin-top: 0; }

/* 不用 d-none 之外的 display 切换：两边共用一个 hidden 类，别再加一套 */
#bag-inv.hidden, #bag-gear.hidden { display: none; }
.bag-ic.gear {
  border: 1px solid var(--line); border-radius: 7px; font-size: 16px;
  background: rgba(255, 255, 255, .05); line-height: 1.15; height: 28px;
}
.bag-n b.eqbtn {
  font-weight: 600; font-size: 11px; color: #0d1620; background: var(--gold);
  border-radius: 20px; padding: 1px 7px; margin-right: 6px;
}

/* 装备页：八个部位 + 属性总览 */
/* 装备格：10 个槽。
   ⚠️ 第 36 轮：原来固定 `repeat(2, 1fr)`，在手机横屏的面板里 2 列太窄，
   槽内的「图标 + 名字 + 词条」被压到换行重叠（用户报「装备10个区域显示错版」）。
   正解：用 `auto-fill` + `minmax` 让**宽度决定列数**，
   宽屏 4~5 列、窄屏 2~3 列自动切换，任何宽度下都不会挤坏。 */
#gear-grid {
  display: grid;
  /* 第 39 轮改方格：minmax(56px)。上面那段 96px 的注释是第 36 轮为"文字横条"写的，
     现在格子只有图标，用不着那么宽。宽屏 6 列、竖屏 5 列，10 个槽两行就排完。 */
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
  gap: 6px;
}
/* 第 36 轮的横条排版已被第 39 轮方格取代（.slot 的 flex/字号/省略号规则见文件末尾「物品详情浮层」一节） */
.slot i {
  font-style: normal; font-size: 18px; width: 26px; height: 26px; line-height: 24px; text-align: center;
  border: 1px solid var(--line); border-radius: 8px; background: rgba(255, 255, 255, .05); color: #6d8290;
}
#gear-stat { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.st-row {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 12px; color: var(--dim); line-height: 1.9;
}
.st-row.has { color: var(--txt); }
.st-row b { color: var(--gold); font-variant-numeric: tabular-nums; }
/* 攻击那一行要能从五条探索属性里跳出来：它是"我这一下能打到多远"，不是加成 */
.st-row.atk {
  margin: 2px 0 2px; padding: 5px 8px; border-radius: 6px; color: var(--txt);
  background: rgba(255, 208, 138, .10); border: 1px solid rgba(255, 208, 138, .30);
}
.st-row em { font-style: normal; font-size: 10.5px; color: #8fd06a; }
.st-sets { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.st-sets span {
  font-size: 11px; color: var(--dim); padding: 2px 8px; border-radius: 20px;
  background: rgba(255, 255, 255, .05); border: 1px solid var(--line);
}
.st-sets span.on { color: #0d1620; background: var(--gold); font-weight: 600; border-color: var(--gold); }
.st-note { font-size: 11px; color: #8fbfa8; margin-top: 8px; line-height: 1.6; }

/* 第 39 轮：完整图例（地形色带 7 档 + 符号 4 类）。
   ⚠️ 色块色值必须和 main.js 的 mapColor() 完全一致 —— 图例和底图对不上，
      玩家就没法用地形色去判断"那边是林地还是岩地"，图例反而成了误导。 */
.map-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 3px 4px; margin-top: 6px;
  font-size: 10.5px; color: var(--dim); line-height: 1.6;
}
.map-legend b {
  width: 100%; font-size: 10px; font-weight: 600; color: #9fb2bd;
  letter-spacing: 1px; margin: 3px 0 1px;
}
.map-legend i {
  width: 13px; height: 9px; border-radius: 2px; margin-left: 6px;
  border: 1px solid rgba(0, 0, 0, .35);
}
.map-legend i:first-child { margin-left: 0; }
.map-legend i.k-deep { background: rgb(14, 44, 82); }
.map-legend i.k-shallow { background: rgb(110, 168, 160); }
.map-legend i.k-sand { background: rgb(228, 208, 162); }
.map-legend i.k-grass { background: rgb(134, 162, 94); }
.map-legend i.k-forest { background: rgb(84, 114, 64); }
.map-legend i.k-rock { background: rgb(146, 132, 104); }
.map-legend i.k-snow { background: rgb(240, 244, 250); }
.map-legend i.k-road { height: 3px; border: 0; background: rgba(226, 210, 172, .9); }
.map-legend i.k-town { width: 8px; height: 8px; background: #9fe3c0; }
.map-legend i.k-lm { height: 3px; border: 0; background: #ffd08a; }
.map-legend i.k-you { width: 8px; height: 8px; border-radius: 50%; background: #8fd0ff; border: 1px solid #fff; }

/* ---------- 世界地图（M）：全屏 ----------
   ⚠️ 第 24 轮：#map 原来是 .panel（300/372px 宽 + overflow:auto），
      内容一超高就冒出竖向滚动条，滚动条再吃掉 15px 宽度 → 340px 的画布
      放不进 325px 的内容区 → **横竖两条滚动条都出来了**。
      全屏版改成 flex 布局：画布那格自己缩放（绝不溢出），
      只有右侧地点列表允许竖向滚动，且滚动条做成细条不抢宽度。

   ⚠️ 第 48 轮：z-index 40 → **68**。
      右上角那三个功能键 #pcbtns 是 z-index:62，和 #map 同在 #app 这个堆叠
      上下文里 → 40 < 62 → 地图一打开，三键**浮在地图上面**（用户报的 bug 4）。
      全屏地图是打断式界面，必须盖住一切常驻 HUD。
      68 这个数是有讲究的：> #pcbtns(62) 和 #accbar(60)，
      但 < .panel(70)（按 I 开背包时面板仍要在地图之上）、
      < #chose(130) / #itemtip(140) / #accmenu(150) / #gate(200)。 */
#map.wm {
  position: fixed; inset: 0; z-index: 68; display: flex; flex-direction: column;
  padding: 18px 22px 14px; overflow: hidden;
  background: rgba(6, 12, 18, .82); backdrop-filter: blur(14px);
}
#map.wm.hidden { display: none; }
.wm-head { display: flex; align-items: baseline; gap: 14px; flex: 0 0 auto; }
.wm-head h2 { font-size: 15px; letter-spacing: 5px; font-weight: 500; color: var(--gold); }
.wm-hint { font-size: 11.5px; color: var(--dim); }
.wm-body { flex: 1 1 auto; min-height: 0; display: flex; gap: 16px; margin-top: 12px; }
.wm-stage {
  position: relative; flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* ⚠️ 第 27 轮：画布改成**铺满舞台的长方形**（不再锁 1:1 的正方形）。
   内部像素尺寸由 main.js 的 wmResize() 按舞台实测大小设定，数据分辨率跟着宽高比走，
   所以横竖缩放一致、大陆不会被拉扁，而整块画布是贴满屏幕的。
   ⚠️ 别再加 aspect-ratio / max-width —— 那正是"大地图只有一个正方形"的原因。 */
#mapcv {
  display: block; width: 100%; height: 100%;
  border-radius: 12px; border: 1px solid var(--line); background: #0b1a24;
  cursor: crosshair;
  /* ⚠️ 第 38 轮：手机上要能**拖动 + 双指捏合**。不写 touch-action:none 的话，
     浏览器会把单指拖当成滚页面、双指当成整页缩放，游戏只收到零星几个
     pointermove（甚至直接 pointercancel），手势根本做不出来。 */
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.wm-side {
  flex: 0 0 268px; min-height: 0; display: flex; flex-direction: column;
  overflow: hidden; padding-right: 2px;
}
#tp-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; margin-top: 8px; }
#tp-list::-webkit-scrollbar-track { background: transparent; }
.wm-foot { flex: 0 0 auto; margin-top: 10px; font-size: 11px; color: var(--dim); }

/* 悬停提示：跟着鼠标走的一张小卡片，绝不允许把 stage 撑大 */
.wm-tip {
  position: absolute; z-index: 3; max-width: 260px; padding: 8px 11px;
  pointer-events: none; border-radius: 10px; font-size: 12px; line-height: 1.55;
  background: rgba(10, 18, 26, .94); border: 1px solid var(--line);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .5);
}
.wm-tip.hidden { display: none; }
.wm-tip b { color: var(--gold); font-weight: 600; }
.wm-tip .tp-kind { font-size: 10.5px; color: var(--dim); }
.wm-tip .tp-pos { font-variant-numeric: tabular-nums; font-size: 10.5px; color: #9fd8c4; }
.wm-tip p { margin-top: 5px; color: #cfdde4; }
.wm-scale {
  position: absolute; left: 12px; bottom: 10px; font-size: 10.5px; color: #cfe0ea;
  padding: 2px 7px; border-radius: 6px; background: rgba(8, 14, 20, .62);
  font-variant-numeric: tabular-nums;
}

/* 背包 */
/* 第 39 轮：背包改成方格网格（.inv-grid，规则见文件末尾「物品详情浮层」一节）。
   这里必须把旧的 flex 列布局**删掉** —— 留着的话 `#bag-list` 的 ID 选择器
   优先级高于 `.inv-grid`，display:flex 会压掉 grid，格子被拉成整行 338px 的长条。 */
.bag-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; cursor: pointer;
  border-radius: 9px; background: rgba(255,255,255,.04);
  border-left: 2px solid rgba(255,255,255,.12); text-align: left; width: 100%;
}
.bag-item:hover { background: rgba(255,208,138,.14); border-left-color: var(--gold); }
.bag-item.out { opacity: .38; cursor: default; }
.bag-item.out:hover { background: rgba(255,255,255,.04); border-left-color: rgba(255,255,255,.12); }
.bag-ic { font-size: 18px; width: 22px; text-align: center; }
.bag-txt { flex: 1; min-width: 0; }
.bag-txt .n { font-size: 12.5px; font-weight: 600; }
.bag-txt .n em { font-style: normal; color: var(--gold); font-size: 11px; margin-left: 6px; }
.bag-txt .d { font-size: 11px; color: var(--dim); margin-top: 2px; line-height: 1.45; }
.bag-n { font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums; }
/* 典录（第 38 轮：1100+ 件的总目录） */
#bag-codex { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.panel.wide.cxmode { width: 468px; }
.cx-bar { display: flex; flex-direction: column; gap: 6px; }
/* ⚠️ 页签必须**单行横向滚**：换行的话 12 个页签在竖屏手机上会堆成 344px 高的一列，
   把下面的列表整个挤出屏幕（用户根本看不到任何一行）。 */
.cx-tabs {
  display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 4px;
  width: 100%; padding-bottom: 2px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.cx-tabs::-webkit-scrollbar { display: none; }
.cx-tabs button {
  flex: 0 0 auto; white-space: nowrap;
  font-size: 11.5px; padding: 4px 9px; border-radius: 7px; cursor: pointer; color: var(--dim);
  background: rgba(255,255,255,.05); border: 1px solid var(--line);
}
.cx-tabs button.on { color: #0d1620; background: var(--gold); border-color: var(--gold); font-weight: 600; }
.cx-row2 { display: flex; gap: 8px; align-items: center; }
.cx-only { font-size: 11.5px; color: var(--dim); display: flex; align-items: center; gap: 4px; cursor: pointer; flex: 0 0 auto; }
.cx-q {
  font-size: 12px; padding: 5px 9px; flex: 1 1 auto; min-width: 0;
  border-radius: 8px; color: var(--txt); background: rgba(255,255,255,.06);
  border: 1px solid var(--line); outline: none;
}
.cx-q:focus { border-color: rgba(255,208,138,.5); }
.cx-stat { font-size: 11px; color: var(--dim); line-height: 1.5; }
.cx-stat b { color: var(--gold); font-variant-numeric: tabular-nums; }
#cx-list {
  display: flex; flex-direction: column; gap: 5px; overflow-y: auto;
  max-height: min(52vh, 470px); padding-right: 2px;
}
.cx-row {
  display: flex; align-items: center; gap: 9px; padding: 6px 9px; cursor: pointer; text-align: left;
  border-radius: 8px; background: rgba(255,255,255,.04); border-left: 2px solid rgba(255,255,255,.12);
}
.cx-row:hover { background: rgba(255,208,138,.14); border-left-color: var(--gold); }
.cx-row.off { opacity: .45; }
.cx-row.off:hover { background: rgba(255,255,255,.04); border-left-color: rgba(255,255,255,.12); }
/* 1321 件道具，图标是唯一的身份标识 —— 典录里必须比背包大一圈 */
.cx-row .bag-ic { width: 32px; flex: 0 0 32px; }
.cx-row .bag-ic.gear { height: 32px; border-radius: 8px; }
.cx-row .bag-ic img.ic { width: 27px; height: 27px; vertical-align: 0; }
.cx-row .bag-txt .n { font-size: 12px; }
/* ⚠️ `.d` 是 <span>（inline）—— inline 元素上的 overflow:hidden 不生效，
   于是省略号不出现、文字直接压在右侧「未获得」上面。必须 display:block。 */
.cx-row .bag-txt .d {
  display: block; font-size: 10.5px; color: var(--dim); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cx-row .bag-n { flex: 0 0 auto; }
.cx-set {
  font-style: normal; font-size: 10px; margin-left: 6px; padding: 1px 5px; border-radius: 5px;
  color: #bcd2f0; background: rgba(120,170,240,.16); border: 1px solid rgba(120,170,240,.3);
}
/* ══════════════ 物品详情浮层 + 方格网格（第 39 轮） ══════════════
 * 为什么改成方格 + 悬浮详情：
 *   1321 件道具如果还用横条列表，一屏放不下 8 件，越堆越长；
 *   而横条里塞了"名字 + 部位 + 词条 + 描述"，真正在对比装备时只关心图标。
 *   方格是业界（暗黑/DNF/原神/塞尔达背包）的通用解法：一眼扫到 rarity 颜色，
 *   想知道细节再悬停。手机没有 hover，所以那边走"点一下出详情面板"。
 */
#itemtip {
  position: fixed; z-index: 140; width: 272px; max-width: min(300px, 82vw);
  padding: 11px 13px 12px; border-radius: 12px;
  background: rgba(9, 16, 23, .97); border: 1px solid var(--line);
  box-shadow: 0 12px 34px rgba(0, 0, 0, .6); backdrop-filter: blur(8px);
  font-size: 12px; color: var(--txt); pointer-events: none;
}
#itemtip.pin { pointer-events: auto; border-color: rgba(255, 208, 138, .45); }
#itemtip .tip-h { display: flex; gap: 10px; align-items: flex-start; }
#itemtip .bag-ic { width: 40px; height: 40px; flex: 0 0 40px; border-radius: 9px; }
#itemtip .bag-ic img.ic { width: 33px; height: 33px; vertical-align: 0; }
#itemtip .tip-h-t { min-width: 0; flex: 1; }
#itemtip .tip-n { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
#itemtip .tip-k { font-size: 10.5px; color: var(--dim); margin-top: 3px; letter-spacing: .4px; }
#itemtip .tip-set {
  display: inline-block; margin-top: 5px; font-size: 10px; padding: 1px 6px; border-radius: 5px;
  color: #bcd2f0; background: rgba(120, 170, 240, .16); border: 1px solid rgba(120, 170, 240, .3);
}
#itemtip .tip-st {
  margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--line);
  display: grid; grid-template-columns: 1fr auto; gap: 3px 10px; font-size: 11.5px;
}
#itemtip .tip-st .k { color: var(--dim); }
#itemtip .tip-st b { color: var(--gold); font-variant-numeric: tabular-nums; text-align: right; }
#itemtip .tip-st b.neg { color: #e08a7a; }
#itemtip .tip-d { margin-top: 8px; font-size: 11px; color: var(--dim); line-height: 1.55; }
/* ══════════════ 第 53 轮 · 套装小栏 ══════════════
   规矩（用户定的）：穿在身上的部件 = 亮；已达成件数的套装属性 = 亮。
   没穿的部件、没达成的档位一律暗色（暗 = "还没生效"，不能让玩家误以为有加成）。
   凑齐一整套 → 部件全亮 + 所有档全亮 + 卡片描金边（.full）。 */
#itemtip .tip-setbox {
  margin-top: 9px; padding: 8px 9px 9px;
  border: 1px solid rgba(120, 170, 240, .28); border-radius: 9px;
  background: rgba(20, 32, 46, .55);
}
#itemtip .tip-setbox.full {
  border-color: rgba(255, 208, 138, .48); background: rgba(48, 40, 20, .38);
}
#itemtip .tsb-h {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 11.5px;
}
#itemtip .tsb-h b { color: #bcd2f0; font-weight: 600; letter-spacing: .5px; }
#itemtip .tsb-cnt { font-size: 10.5px; color: #6f8496; }
#itemtip .tsb-cnt.on { color: #7ee0a8; }
#itemtip .tsb-list { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; }
#itemtip .tsb-p {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 5px; border-radius: 6px;
  font-size: 11px; color: #61758a; background: rgba(255, 255, 255, .03);
}
#itemtip .tsb-p i {
  flex: 0 0 15px; width: 15px; height: 15px; font-style: normal;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; opacity: .5;
}
#itemtip .tsb-p .tsb-pn {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#itemtip .tsb-p em { font-style: normal; font-size: 10px; opacity: .85; flex: 0 0 auto; }
/* 已穿戴：亮 */
#itemtip .tsb-p.on { color: var(--txt); background: rgba(255, 208, 138, .14); }
#itemtip .tsb-p.on i { opacity: 1; }
#itemtip .tsb-p.on .tsb-pn { color: var(--gold); font-weight: 600; }
#itemtip .tsb-p.on em { color: var(--gold); opacity: .95; }
#itemtip .tsb-tiers { margin-top: 7px; display: flex; flex-direction: column; gap: 5px; }
#itemtip .tsb-t { padding-top: 5px; border-top: 1px dashed rgba(255, 255, 255, .10); }
#itemtip .tsb-th { display: flex; align-items: center; gap: 6px; font-size: 10.5px; color: #61758a; }
#itemtip .tsb-tag {
  margin-left: auto; font-size: 9.5px; padding: 0 5px; border-radius: 5px;
  background: rgba(255, 255, 255, .06);
}
#itemtip .tsb-ts {
  margin-top: 3px; display: grid; grid-template-columns: 1fr auto; gap: 2px 8px;
  font-size: 11px; color: #5f7385;
}
#itemtip .tsb-ts .k { color: #5f7385; }
#itemtip .tsb-ts b { color: #5f7385; font-variant-numeric: tabular-nums; text-align: right; }
/* 该档已生效：亮 */
#itemtip .tsb-t.on .tsb-th { color: #cfe6f8; }
#itemtip .tsb-t.on .tsb-tn { color: var(--gold); font-weight: 600; }
#itemtip .tsb-t.on .tsb-tag { color: #0d1620; background: #7ee0a8; }
#itemtip .tsb-t.on .tsb-ts { color: var(--txt); }
#itemtip .tsb-t.on .tsb-ts .k { color: var(--dim); }
#itemtip .tsb-t.on .tsb-ts b { color: var(--gold); }
#itemtip .tip-c { margin-top: 9px; display: flex; gap: 6px; }
#itemtip .tip-c button {
  flex: 1; font-size: 11.5px; padding: 6px 0; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--gold); color: var(--gold); background: rgba(255, 208, 138, .12);
}
#itemtip .tip-c button:hover, #itemtip .tip-c button:active { background: var(--gold); color: #0d1620; }
#itemtip .tip-c button.ghost { border-color: var(--line); color: var(--dim); background: rgba(255,255,255,.05); }
/* ══════════════ 第 48 轮：账号菜单（左下角名称框点出来的小弹窗） ══════════════
   层级 150：盖过 #map/#atlas(68) / #pcbtns(62) / .panel(70) / #accbar(60)，
             低于 #gate(200)（门禁永远最上层）。
   定位由 gate.js 按锚点实测矩形算 + clamp 进屏幕，不写死 top/left ——
   手机横屏被地址栏压到 300px 高时，写死的位置一定出屏。 */
#accmenu {
  position: fixed; z-index: 150; width: 232px; max-width: 84vw;
  padding: 10px 12px 11px; border-radius: 12px;
  background: rgba(9, 16, 23, .97); border: 1px solid var(--line);
  box-shadow: 0 12px 34px rgba(0, 0, 0, .62); backdrop-filter: blur(10px);
  font-size: 12px; color: var(--txt);
}
#accmenu.hidden { display: none; }
.acm-head { display: flex; align-items: center; gap: 9px; }
.acm-head .acm-face {
  width: 32px; height: 32px; flex: 0 0 32px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; font-weight: 700; color: #0d1620;
  background: linear-gradient(140deg, var(--gold), #e0a95c);
}
.acm-id { min-width: 0; flex: 1; display: flex; flex-direction: column; line-height: 1.35; }
.acm-id b { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acm-id i { font-style: normal; font-size: 10.5px; color: var(--dim); }
.acm-rows { margin: 9px 0 4px; display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; font-size: 11px; }
.acm-rows .k { color: var(--dim); }
.acm-rows .v { color: var(--txt); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acm-rows .v.warn { color: #e0b06a; }
.acm-rows .v.ok { color: #8fd0a0; }
.acm-foot { margin-top: 9px; display: flex; gap: 7px; }
.acm-foot .acm-b {
  flex: 1; padding: 7px 0; border-radius: 8px; cursor: pointer; font-size: 11.5px;
  border: 1px solid var(--line); color: var(--dim); background: rgba(255, 255, 255, .06);
}
.acm-foot .acm-b:hover { background: rgba(255, 255, 255, .12); color: var(--txt); }
.acm-foot .acm-b.danger {
  border-color: rgba(224, 138, 122, .55); color: #e08a7a; background: rgba(224, 138, 122, .12);
}
.acm-foot .acm-b.danger:hover { background: rgba(224, 138, 122, .22); color: #f2a898; }
/* 二次确认：第一下只是"举起手"，4 秒内不再点就自动放下 —— 防止手滑退出。 */
.acm-foot .acm-b.danger.armed {
  border-color: #e08a7a; color: #0d1620; background: #e08a7a; font-weight: 600;
}

/* ══════════════ 选角面板（第 40 轮 · 角色重做） ══════════════
   整屏卡片 + 独立遮罩：选角是打断式流程，世界要完全停住。 */
#chose {
  position: fixed; inset: 0; z-index: 120;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 9, 14, .78); backdrop-filter: blur(10px);
}
#chose.hidden { display: none; }
.cs-card {
  width: min(640px, 94vw); max-height: 92vh; overflow: auto;
  padding: 20px 22px 16px; border-radius: 18px;
  background: linear-gradient(180deg, rgba(17,29,40,.97), rgba(10,18,26,.97));
  border: 1px solid var(--line); box-shadow: 0 24px 70px rgba(0,0,0,.7);
}
.cs-card h2 { font-size: 16px; letter-spacing: 4px; color: var(--gold); margin: 0 0 4px; }
.cs-sub { font-size: 11.5px; color: var(--dim); margin: 0 0 14px; }
.cs-sub b { color: var(--gold); }
.cs-body { display: flex; gap: 16px; align-items: stretch; }
.cs-left { flex: 0 0 190px; display: flex; flex-direction: column; gap: 10px; }
.cs-right { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.cs-sexs, .cs-bodies, .cs-pals { display: flex; flex-wrap: wrap; gap: 6px; }
.cs-sexs button, .cs-bodies button, .cs-pals button {
  padding: 6px 10px; border-radius: 9px; cursor: pointer; font-size: 12px;
  color: var(--txt); background: rgba(255,255,255,.05); border: 1px solid var(--line);
}
.cs-sexs button.on, .cs-bodies button.on { color: #0d1620; background: var(--gold); border-color: var(--gold); font-weight: 600; }
.cs-pals button { display: flex; align-items: center; gap: 6px; }
.cs-pals .sw { width: 12px; height: 12px; border-radius: 3px; border: 1px solid rgba(0,0,0,.4); }

/* ── r159：还没下载的那几套 ──
 * ⚠️ 用户报的「要等一整套载完才显示名字」的正面修法：名字**立刻全列出来**，
 *   没载过的那几套做成"虚线 + 箭头"的灰态；点一下才开始下（见 chose.js 的 csShow）。
 *   千万别再把它们从列表里藏掉 —— 那就是原来那个 bug。 */
.cs-bodies button.dim { opacity: .5; border-style: dashed; }
.cs-bodies button.dim::after { content: '↓'; margin-left: 4px; font-size: 10px; opacity: .85; }
/* 正在下载某一套时，预览台压暗一点，配合 note 里那行"正在载入…" */
#chose.cs-busy #cs-cv { opacity: .35; }
#cs-cv {
  width: 100%; height: 210px; border-radius: 12px; display: block;
  background: linear-gradient(180deg, #22384a, #16242f);
  border: 1px solid var(--line);
}
.cs-note { font-size: 11px; color: var(--dim); line-height: 1.55; min-height: 2.6em; }
.cs-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }
.cs-foot button { padding: 8px 18px; border-radius: 10px; font-size: 12.5px; cursor: pointer; }
.cs-b { color: var(--dim); background: transparent; border: 1px solid var(--line); }
.cs-go { color: #0d1620; background: var(--gold); border: 1px solid var(--gold); font-weight: 600; }
/* 手机：左右改上下，预览台缩小 —— 竖屏横向塞不下 */
html.touch #chose .cs-body { flex-direction: column; }
html.touch .cs-left { flex: 0 0 auto; }
html.touch #cs-cv { height: 168px; }

/* ══════════════ 第 51 轮：选角期间，一切游戏 HUD 退场 ══════════════
   用户原话：「按理说这一步不应该有右上角三个按键和右下角三个按键，因为角色都还没创建呢」。
   根因：移动端右下战斗键簇(.tzone-btns)/右上功能键簇(.tzone-sys)挂在 <body> 下(z=22)，
   #chose 在 #app(html.touch 下 z=2) 堆叠上下文内部 —— z=120 只在 #app 内部排序，
   永远压不过 body 层的 22（第 36 轮 #minimap 同款坑）。实测它们盖住「就这个」确认键、
   手机上点「机偶」无反应。正解 = 选角期间让 HUD 整个退场（tzone 系由 touch.js 的
   setTouchIdle 处理，这里补 PC/常驻四件）。 */
html.cs-open #pcbtns, html.cs-open #hud, html.cs-open #accbar,
html.cs-open #minimap { display: none !important; }

#itemtip .tip-c button.danger { border-color: #e08a7a; color: #ffb9a8; background: rgba(224, 138, 122, .16); }
#itemtip .tip-c button.danger:hover, #itemtip .tip-c button.danger:active { background: #e08a7a; color: #1a0d0a; }

/* ══════════════ r120：背包工具条（整理 / 批量卖出）══════════════
   两条硬约束：
   1) 手机上手指按不到 16px 的复选框 —— 整行 `.bulk-row` 都是 <label>，
      点行内任意位置都能勾，触控目标 ≥ 44px 高。
   2) 「卖出」是**不可逆**操作，按钮必须够大、够远，不能和列表格子贴在一起，
      否则拇指滑一下就误触。所以面板内单独一行、手机上满宽。 */
.inv-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 8px;
}
.inv-tool {
  -webkit-appearance: none; appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
  min-height: 30px; padding: 0 11px; border-radius: 8px;
  font: inherit; font-size: 12px; line-height: 1; white-space: nowrap;
  color: var(--txt); background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .13);
  transition: background .12s, border-color .12s, color .12s;
}
.inv-tool:hover { background: rgba(255, 208, 138, .14); border-color: rgba(255, 208, 138, .45); }
.inv-tool:active { background: rgba(255, 208, 138, .24); }
.inv-tool.on { background: rgba(255, 208, 138, .20); border-color: rgba(255, 208, 138, .65); color: #ffe9c4; }
/* r195 · T-P03-14（发现 P）：**背包工具条的「寄给人」也要挂待领取红点** ——
   PC 上 `#pcb-send` 不存在（buildPcButtons 非触摸直接 return）、`tb-send` 又在收起的抽屉里，
   于是 PC 玩家唯一常走的 `#inv-send` 连红点都没有 → 核心承诺"东西留着等他领"在 PC 上不可达。
   ⚠️ 红点**内缩**（`inset` 而非 `top/right` 正值）：`.inv-bar` 有 flex-wrap、背包面板有
      overflow:auto，外扩会被裁掉。所以用 `inset:0` + `top:2px;right:2px` 贴内缘。 */
#inv-send { position: relative; }
#inv-send.has-new::after {
  content: ''; position: absolute; top: 2px; right: 2px;
  width: 8px; height: 8px; border-radius: 50%;
  background: #ff5a5a; box-shadow: 0 0 0 2px rgba(10, 18, 26, .8);
}
.inv-hint { font-size: 11px; color: var(--dim); line-height: 1.4; }
.inv-hint:empty { display: none; }

.bulk-box {
  display: flex; flex-direction: column; gap: 5px;
  margin-bottom: 9px; padding: 9px 10px 10px;
  border-radius: 10px; background: rgba(255, 208, 138, .055);
  border: 1px solid rgba(255, 208, 138, .26);
}
.bulk-head { font-size: 11px; color: var(--dim); line-height: 1.55; margin-bottom: 1px; }
/* r122：品级五档 / 类型两组 → 复选小胶囊（比 7 行 checkbox 紧凑得多，手机上也好按） */
.bulk-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 3px; }
.bulk-line > .lb {
  flex: none; min-width: 24px; font-size: 11px; color: var(--dim);
}
.bulk-chip {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  padding: 5px 9px; border-radius: 9px; cursor: pointer; font: inherit;
  color: var(--txt); text-align: left;
  background: rgba(255, 255, 255, .045); border: 1px solid rgba(255, 255, 255, .14);
  transition: background .14s, border-color .14s, transform .12s;
}
.bulk-chip:hover { background: rgba(255, 255, 255, .09); transform: translateY(-1px); }
.bulk-chip:active { transform: translateY(0); }
.bulk-chip b {
  font-size: 12.5px; font-weight: 600; line-height: 1.25;
  color: var(--rc, var(--txt));
}
.bulk-chip em {
  font-style: normal; font-size: 10.5px; line-height: 1.25; color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.bulk-chip.on {
  background: rgba(255, 208, 138, .15); border-color: rgba(255, 208, 138, .60);
  box-shadow: 0 0 0 1px rgba(255, 208, 138, .22) inset;
}
.bulk-chip.on em { color: #d6c5a4; }
.bulk-chip.empty { opacity: .45; }
/* 整行可点：label 是天然的"点哪都行"元素，不用 JS 兜 */
.bulk-row {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 6px 8px; border-radius: 8px; font-size: 12.5px;
  border: 1px solid transparent; transition: background .12s, border-color .12s;
}
.bulk-row:hover { background: rgba(255, 255, 255, .045); }
.bulk-row.on { background: rgba(255, 208, 138, .11); border-color: rgba(255, 208, 138, .38); }
/* 勾上高价值品类时整行转暖红，视觉上和低等级组分开 */
.bulk-row.hi.on { background: rgba(224, 138, 122, .12); border-color: rgba(224, 138, 122, .42); }
.bulk-row.empty { opacity: .42; cursor: default; }
.bulk-row.empty:hover { background: none; }
.bulk-row input[type="checkbox"] {
  flex: none; width: 15px; height: 15px; margin: 0; cursor: inherit;
  accent-color: #ffd08a;
}
.bulk-row.hi input[type="checkbox"] { accent-color: #e08a7a; }
.bulk-row b { flex: 1 1 auto; min-width: 0; font-weight: 500; color: var(--txt); }
.bulk-row em {
  flex: none; font-style: normal; font-size: 11px; color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.bulk-row i {
  flex: none; font-style: normal; font-size: 11.5px; color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.bulk-warn {
  font-size: 11.5px; line-height: 1.6; color: #ffcdbe;
  background: rgba(224, 138, 122, .14); border: 1px solid rgba(224, 138, 122, .38);
  border-radius: 8px; padding: 7px 9px; margin-top: 2px;
}
.bulk-warn b { color: #ffb9a8; }
/* "穿着中"那条是提示不是警告，换成中性一点的琥珀色，别和"高价值误操作"混为一谈 */
.bulk-warn.keep {
  color: #ffe2b8; background: rgba(255, 208, 138, .12);
  border-color: rgba(255, 208, 138, .34);
}
.bulk-warn.keep b { color: #ffd08a; }
.bulk-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 5px; }
.bulk-sum { font-size: 12px; color: var(--dim); }
.bulk-sum b { color: var(--txt); font-weight: 600; }
.bulk-sum b.c { color: var(--gold); }
.bulk-go {
  -webkit-appearance: none; appearance: none; cursor: pointer; margin-left: auto;
  min-height: 30px; padding: 0 15px; border-radius: 8px; border: 0;
  font: inherit; font-size: 12.5px; font-weight: 600; line-height: 1;
  color: #241708; background: var(--gold);
}
.bulk-go:hover { background: #ffdca6; }
.bulk-go:active { background: #f5c477; }
.bulk-go.hi { color: #2a0f0a; background: #e08a7a; }
.bulk-go.hi:hover { background: #eda093; }
.bulk-go:disabled { cursor: default; opacity: .38; background: #8ea0ab; color: #101a21; }

/* 触控端：目标放大、卖出键满宽（拇指扫不到别处） */
html.touch .inv-tool { min-height: 38px; padding: 0 14px; font-size: 13px; }
html.touch .inv-bar { gap: 8px; margin-bottom: 10px; }
html.touch .bulk-row { padding: 11px 10px; font-size: 13px; }
html.touch .bulk-row input[type="checkbox"] { width: 19px; height: 19px; }
html.touch .bulk-go { min-height: 42px; width: 100%; margin-left: 0; font-size: 14px; }
html.touch .bulk-foot { gap: 6px; }
/* 手机上胶囊要够大（手指按得住），横向排不下自动换行 */
html.touch .bulk-chip { padding: 8px 12px; min-height: 44px; justify-content: center; }
html.touch .bulk-chip b { font-size: 13.5px; }
html.touch .bulk-line { gap: 7px; margin-top: 5px; }

/* 方格网格：背包与装备页共用 */
.inv-grid, #gear-grid { display: grid; gap: 6px; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); }
.inv-cell, .slot {
  position: relative; aspect-ratio: 1 / 1; min-width: 0; padding: 0; cursor: pointer;
  border-radius: 10px; background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .10);
  display: flex; flex-direction: row; align-items: center; justify-content: center;
  text-align: center;
}
.inv-cell:hover, .slot:hover { background: rgba(255, 208, 138, .16); border-color: rgba(255, 208, 138, .5); }
.inv-cell img.ic, .slot img.ic { width: 38px; height: 38px; vertical-align: 0; }
.inv-cell .em { font-size: 27px; line-height: 1; }
.inv-cell .q {
  position: absolute; right: 3px; bottom: 1px; font-size: 10.5px; font-weight: 600; color: #fff;
  text-shadow: 0 1px 3px #000, 0 0 6px #000; font-variant-numeric: tabular-nums;
}
.inv-cell .eq { position: absolute; left: 4px; top: 2px; font-size: 9px; color: var(--gold); text-shadow: 0 1px 2px #000; }
/* 第 44 轮：穿着中的装备在背包里也列出来（原来整件跳过，开局那四件根本看不到），
   用一个角标 + 金色描边区分"身上这件"和"包里这件"，点一下就是卸下。 */
.inv-cell.worn { border-color: rgba(255, 208, 138, .75); box-shadow: 0 0 7px rgba(255, 208, 138, .20) inset; }
.inv-cell .wb {
  position: absolute; right: 3px; top: 2px; font-size: 9px; line-height: 1;
  color: #0d1620; background: var(--gold); border-radius: 3px; padding: 1px 3px; font-weight: 600;
}
/* 稀有度：只描边，不铺底色 —— 1321 个格子铺色就是一片花，描边才扫得清 */
.inv-cell.r1, .slot.r1 { border-color: rgba(159, 178, 189, .35); }
.inv-cell.r2, .slot.r2 { border-color: rgba(127, 201, 160, .55); }
.inv-cell.r3, .slot.r3 { border-color: rgba(127, 179, 240, .65); }
.inv-cell.r4, .slot.r4 { border-color: rgba(209, 155, 240, .70); }
.inv-cell.r5, .slot.r5 { border-color: rgba(255, 207, 107, .80); box-shadow: 0 0 8px rgba(255, 207, 107, .18) inset; }
.inv-cell.r5::after {
  content: ''; position: absolute; inset: 0; border-radius: 9px; pointer-events: none;
  background: radial-gradient(circle at 50% 115%, rgba(255, 207, 107, .22), transparent 62%);
}
.inv-cell.sel, .slot.on { background: rgba(255, 208, 138, .12); }
/* ══════════ r122：批量卖出的「选中框」══════════
   被勾选范围框住 = 这次会被卖掉。要求是"明显的动态特效"，所以做成
   **呼吸式双层描边**（内圈实线 + 外圈光晕，一起明/暗一个周期 1.15s），
   静态边框在一百多格子里根本扫不出来。
   ⚠️ 用 animation 而不是普通声明：animation 的优先级高于 .inv-cell.r5 那组
   稀有度 box-shadow，不会互相打架。 */
.inv-cell.bsel { animation: bsel 1.15s ease-in-out infinite; }
@keyframes bsel {
  0%, 100% { box-shadow: 0 0 0 2px rgba(255, 208, 138, .85), 0 0  9px 1px rgba(255, 208, 138, .30);
             background: rgba(255, 208, 138, .12); }
  50%      { box-shadow: 0 0 0 3px rgba(255, 240, 200, 1),   0 0 16px 5px rgba(255, 208, 138, .62);
             background: rgba(255, 208, 138, .26); }
}
/* 穿着中的那件：勾到了但**卖不掉**（要先卸下）。换暖红框 + 慢一拍的呼吸区分开，
   别让玩家以为金框那堆里少了它。 */
.inv-cell.bsel.worn { animation: bsel-worn 1.7s ease-in-out infinite; }
@keyframes bsel-worn {
  0%, 100% { box-shadow: 0 0 0 2px rgba(224, 138, 122, .70), 0 0  8px 0 rgba(224, 138, 122, .22);
             background: rgba(224, 138, 122, .10); }
  50%      { box-shadow: 0 0 0 3px rgba(243, 176, 158, .95), 0 0 14px 4px rgba(224, 138, 122, .45);
             background: rgba(224, 138, 122, .20); }
}
/* 左上角打勾，穿在身上的那件打红勾 —— 和右上角「穿」角标一左一右不打架 */
.inv-cell.bsel::before {
  content: '✓'; position: absolute; left: 3px; top: 2px; z-index: 3;
  font-size: 9.5px; font-weight: 700; line-height: 1.3; color: #0d1620;
  background: var(--gold); border-radius: 3px; padding: 0 3px;
}
.inv-cell.bsel.worn::before { background: #e08a7a; }
/* 系统开了「减少动态效果」就改成静态高亮，别把人晃晕 */
@media (prefers-reduced-motion: reduce) {
  .inv-cell.bsel { animation: none; box-shadow: 0 0 0 2px rgba(255, 240, 200, .95), 0 0 12px 3px rgba(255, 208, 138, .5);
                   background: rgba(255, 208, 138, .20); }
  .inv-cell.bsel.worn { animation: none; box-shadow: 0 0 0 2px rgba(243, 176, 158, .9), 0 0 10px 2px rgba(224, 138, 122, .4);
                        background: rgba(224, 138, 122, .16); }
}
/* 第 56 轮：空格子要**一眼看出是个空栏位** —— 图标压暗 + 去色，格子整体再淡一档。
   原来只把文字调灰，结果它和"穿了件低稀有度装备"长得几乎一样，玩家会去点。
   （手机端点空格子本来就不绑任何事件 = 什么都不发生，这里只是让它"看得出不该点"。） */
.slot.empty { color: #4b5a66; opacity: .55; cursor: default; }
.slot.empty .em { opacity: .7; filter: grayscale(.9); }
.slot.empty:hover { background: rgba(255, 255, 255, .03); border-color: rgba(255, 255, 255, .09); }
.gear-sub { margin-top: 9px; font-size: 11px; color: var(--dim); line-height: 1.5; }
.gear-sub b { color: var(--gold); }

#tp-list { margin-top: 10px; display: flex; flex-direction: column; gap: 5px; }
.tp {
  text-align: left; padding: 7px 10px; font-size: 12px; cursor: pointer; color: var(--txt);
  background: rgba(255,255,255,.05); border: 1px solid var(--line); border-radius: 8px;
}
.tp:hover { background: rgba(255,208,138,.16); border-color: rgba(255,208,138,.4); }
.tp.none { color: #6d8290; cursor: default; }

#toast {
  position: absolute; left: 50%; top: 70px; transform: translateX(-50%);
  padding: 8px 16px; font-size: 12.5px; border-radius: 20px; z-index: 30;
  background: rgba(10,18,26,.9); border: 1px solid var(--line);
  /* r138：分段提示条。段与段之间用 gap 隔开（不再靠全角空格凑间距），
     段内 nowrap 保证「+27币」这种不会在数字中间折行。 */
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 9px;
  max-width: min(78vw, 560px);
  justify-content: center;
}
#toast > span { white-space: nowrap; }
#toast.hidden { display: none; }

/* 交互提示：常驻在按键条上方，走近才出现 */
#prompt {
  position: absolute; left: 50%; bottom: 58px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px; z-index: 22;
  padding: 6px 14px; font-size: 12.5px; border-radius: 20px;
  background: rgba(10,18,26,.86); border: 1px solid rgba(255,208,138,.42);
  color: var(--txt); white-space: nowrap;
}
#prompt.hidden { display: none; }
#prompt b {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 5px; border-radius: 5px;
  font-size: 11px; color: #0d1620; background: var(--gold); font-weight: 700;
}

/* 对话气泡：NPC 说话 / 互动回应 */
#talk {
  position: absolute; left: 50%; bottom: 108px; transform: translate(-50%, 14px);
  width: min(460px, 88vw); padding: 12px 16px; z-index: 23;
  background: linear-gradient(180deg, rgba(20,34,46,.94), rgba(10,18,26,.94));
  border: 1px solid rgba(255,208,138,.32); border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
  opacity: 0; transition: opacity .3s, transform .3s;
}
#talk.show { opacity: 1; transform: translate(-50%, 0); }
#talk.hidden { display: none; }
#talk b { display: block; font-size: 13px; color: var(--gold); letter-spacing: 1px; margin-bottom: 4px; }
#talk b:empty { display: none; }
#talk span { display: block; font-size: 13.5px; line-height: 1.7; color: var(--txt); }

/* 道具图标：CC0 32×32 像素图，放大别插值糊掉（pixelated 保住像素味） */
img.ic {
  width: 20px; height: 20px; object-fit: contain;
  image-rendering: pixelated; image-rendering: crisp-edges;
  vertical-align: -5px; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6));
}
.bag-ic.gear img.ic { width: 19px; height: 19px; vertical-align: -3px; }
.slot i img.ic { width: 17px; height: 17px; vertical-align: -3px; }
#buff-list i img.ic { width: 15px; height: 15px; vertical-align: -3px; }

/* ================= 世界志（J 键）：羊皮纸全屏地图 ================= */
/* ⚠️ 第 48 轮：z-index 45 → 68，和 #map.wm 同步（同一个 bug：全屏界面被 #pcbtns 压住）。 */
#atlas.atl {
  position: fixed; inset: 0; z-index: 68; display: flex; flex-direction: column;
  padding: 14px 18px 12px; overflow: hidden;
  background: rgba(8, 12, 16, .86); backdrop-filter: blur(12px);
}
#atlas.atl.hidden { display: none; }
.atl-head { flex: 0 0 auto; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.atl-head h2 { font-size: 15px; letter-spacing: 5px; font-weight: 500; color: var(--gold); }
.atl-head-btns { display: flex; align-items: center; gap: 7px; }
.atl-in {
  background: rgba(255, 255, 255, .07); color: var(--txt);
  border: 1px solid var(--line); border-radius: 7px; padding: 5px 8px; font-size: 12px;
}
.atl-in[type=number] { width: 90px; }
.atl-btn {
  background: rgba(255, 208, 138, .13); color: var(--gold); cursor: pointer;
  border: 1px solid rgba(255, 208, 138, .34); border-radius: 7px; padding: 5px 11px; font-size: 12px;
}
.atl-btn:hover { background: rgba(255, 208, 138, .24); }
.atl-btn-x { color: var(--dim); border-color: var(--line); background: rgba(255, 255, 255, .05); }
.atl-prog { flex: 1 1 auto; font-size: 11.5px; color: var(--dim); text-align: right; }
.atl-body { flex: 1 1 auto; min-height: 0; display: flex; gap: 14px; margin-top: 10px; }
.atl-stage {
  position: relative; flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.atl-cv {
  display: block; width: auto; height: auto; aspect-ratio: 1 / 1;
  max-width: 100%; max-height: 100%; border-radius: 10px; cursor: crosshair;
  border: 1px solid var(--line); background: #e8dcc0;
}
.atl-tip {
  position: absolute; z-index: 4; max-width: 240px; padding: 7px 10px; pointer-events: none;
  border-radius: 9px; font-size: 12px; line-height: 1.6;
  background: rgba(16, 22, 28, .94); border: 1px solid var(--line); color: var(--txt);
}
.atl-tip.hidden { display: none; }
.atl-tip .tp-pos { color: var(--gold); }
.atl-side {
  flex: 0 0 264px; min-height: 0; display: flex; flex-direction: column; overflow: hidden;
}
.atl-sec { margin: 10px 0 6px; font-size: 12px; color: var(--dim); letter-spacing: 1px; }
.atl-sec:first-child { margin-top: 0; }
.atl-search { width: 100%; }
.atl-list { max-height: 148px; overflow-y: auto; margin-top: 6px; }
.atl-layers { display: flex; flex-direction: column; gap: 4px; }
.atl-layer { display: flex; align-items: center; gap: 7px; font-size: 12px; }
.atl-layer-nm { flex: 0 0 54px; color: var(--txt); }
.atl-layer input[type=range] { flex: 1 1 auto; min-width: 0; }
.atl-detail, .atl-stats { font-size: 12px; line-height: 1.7; color: var(--txt); }
.atl-kv { display: flex; justify-content: space-between; gap: 8px; }
.atl-kv span { color: var(--dim); flex: 0 0 auto; }
.atl-kv b { font-weight: 500; text-align: right; }
.atl-good { color: #7fd4a0; }
.atl-bad { color: #e08a6a; }
.atl-sub { margin-top: 8px; color: var(--gold); font-size: 11.5px; letter-spacing: 1px; }
.atl-row {
  padding: 3px 6px; border-radius: 5px; cursor: pointer; color: var(--txt);
  display: flex; align-items: center; gap: 6px;
}
.atl-row:hover { background: rgba(255, 255, 255, .08); }
.atl-tag {
  flex: 0 0 auto; font-size: 10.5px; color: var(--gold);
  border: 1px solid rgba(255, 208, 138, .3); border-radius: 4px; padding: 0 4px;
}
.atl-dim { color: var(--dim); }

/* ================= 账号门禁（第 31 轮） ================= */
#gate {
  position: fixed; inset: 0; z-index: 200; display: flex;
  align-items: center; justify-content: center;
  font: 14px/1.6 system-ui, "PingFang SC", "Microsoft YaHei", sans-serif;
  background: radial-gradient(900px 620px at 22% 18%, #17415a 0%, #0b1c28 55%, #06111a 100%);
  overflow: hidden;
}
#gate.hidden { display: none; }
/* 背景漂浮的小蝴蝶：纯装饰，不接事件 */
#gate::before, #gate::after {
  content: ""; position: absolute; width: 320px; height: 320px; opacity: 0.07;
  background: url("./brand-butterfly.png") center/contain no-repeat;
  animation: gtFloat 17s ease-in-out infinite;
}
#gate::before { left: -70px; bottom: -60px; }
#gate::after { right: -80px; top: -70px; animation-delay: -8s; transform: rotate(24deg); }
@keyframes gtFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-26px) rotate(8deg); }
}
.gt-card {
  position: relative; width: 380px; max-width: 92vw; padding: 28px 28px 22px;
  border-radius: 18px; background: rgba(11, 28, 40, 0.86);
  border: 1px solid rgba(127, 214, 176, 0.18);
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255,255,255,0.05);
  backdrop-filter: blur(10px); color: #e8f3ff;
}
.gt-brand { display: flex; align-items: center; gap: 14px; margin-bottom: 20px; }
.gt-logo { width: 56px; height: 56px; filter: drop-shadow(0 4px 12px rgba(0,0,0,.5)); }
.gt-name { font-size: 22px; font-weight: 700; letter-spacing: 1px; }
.gt-name sup { font-size: 11px; opacity: 0.7; }
.gt-slogan { font-size: 12px; color: #7fa6bd; letter-spacing: 1px; }
.gt-tabs { display: flex; gap: 6px; background: rgba(255,255,255,0.05); padding: 4px; border-radius: 10px; }
.gt-tabs button {
  flex: 1; padding: 8px 0; border: 0; border-radius: 7px; cursor: pointer;
  background: transparent; color: #9dc0d4; font-size: 13px; transition: .18s;
}
.gt-tabs button.on { background: linear-gradient(120deg, #2eb08a, #3cbecd); color: #05131c; font-weight: 600; }
.gt-form { margin: 16px 0 6px; display: flex; flex-direction: column; gap: 10px; }
.gt-pane { display: flex; flex-direction: column; gap: 10px; }
.gt-pane.hidden { display: none; }
.gt-pane label { display: flex; align-items: center; gap: 10px; }
.gt-pane label span { width: 46px; font-size: 13px; color: #8fb3c8; flex: none; }
.gt-pane input {
  flex: 1; min-width: 0; padding: 9px 11px; border-radius: 8px; font-size: 13px;
  background: rgba(255,255,255,0.06); border: 1px solid rgba(140,190,215,0.2);
  color: #eaf4ff; outline: none; transition: border-color .18s, background .18s;
}
.gt-pane input::placeholder { color: #5b7c90; }
.gt-pane input:focus { border-color: #3cbecd; background: rgba(60,190,205,0.10); }
/* 图形验证码（第 32 轮）。input 固定 76px，图 132×46，点图换一张。 */
.gt-pane label.cap input,
#gt-l-cap, #gt-r-cap {
  flex: none; width: 82px; text-align: center; letter-spacing: 3px; font-weight: 700;
}
/* r192：验证码放大到约 2.4x（图**独占一行**，占满卡片内容宽）。
   为什么必须换行：标签 46 + 间距 10 + 输入 82 + 间距 10 + 图 132 = 280，
     卡片内容宽只有 324（.gt-card 380 − 左右 padding 28）—— 直接把图改成 264 会变成 412，
     顶出卡片；手机上（max-width: 92vw）更糟。所以让图 flex-basis:100% 自己另起一行。
   为什么敢直接放大：服务端出的是**矢量 SVG**（viewBox 0 0 132 46），
     等比放大时字形和描边一起放大、比例完全不变 —— 数字视觉高度从 ≈20px 变成 ≈48px。
   ⚠️⚠️ 同一处删掉了 `image-rendering: pixelated`。那条注释（"图本身有噪点，别被插值糊掉"）
     是对**位图**说的；用在矢量图上，浏览器会先按 132×46 栅格化、再用最近邻拉大，
     字边全是锯齿 —— 等于放大了个寂寞，比不放还难看。 */
.gt-pane label.cap,
#gt-l-cap-wrap { flex-wrap: wrap; }
.gt-pane img.cap, #gt-l-cap-img, #gt-r-cap-img {
  flex: 1 0 100%; width: 100%; height: auto; aspect-ratio: 132 / 46;
  border-radius: 8px; cursor: pointer;
  border: 1px solid rgba(140,190,215,0.25); background: #eef4f8; user-select: none;
}
#gt-l-cap-img.busy, #gt-r-cap-img.busy { opacity: .45; cursor: progress; }
.gt-msg { min-height: 18px; font-size: 12px; color: #7fa6bd; margin: 4px 0 8px; }
.gt-msg.bad { color: #ff8a8a; }
.gt-msg.ok { color: #7fd6b0; }
.gt-go {
  width: 100%; padding: 12px 0; border: 0; border-radius: 10px; cursor: pointer;
  font-size: 15px; font-weight: 600; letter-spacing: 3px; color: #05131c;
  background: linear-gradient(120deg, #2eb08a, #3cbecd 55%, #7a5cf0);
  box-shadow: 0 8px 22px rgba(46,176,138,0.28); transition: .18s;
}
.gt-go:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
.gt-go:disabled { opacity: 0.6; cursor: default; }
.gt-tip { margin-top: 14px; font-size: 11.5px; line-height: 1.7; color: #6e93a8; }
.gt-tip b { color: #7fd6b0; font-weight: 600; }

/* ---- 邮箱验证面板 ---- */
#verify { width: 330px; }
.vf-hint { font-size: 12.5px; color: #8fb3c8; margin: 2px 0 10px; word-break: break-all; }
.vf-msg { min-height: 18px; font-size: 12px; color: #7fa6bd; margin-bottom: 8px; }
.vf-msg.bad { color: #ff8a8a; }
.vf-msg.ok { color: #7fd6b0; }
.vf-row { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.vf-row span { font-size: 13px; color: #8fb3c8; flex: none; }
.vf-row input {
  flex: 1; min-width: 0; padding: 8px 10px; border-radius: 8px; font-size: 14px;
  letter-spacing: 3px; text-align: center;
  background: rgba(255,255,255,0.06); border: 1px solid rgba(140,190,215,0.2);
  color: #eaf4ff; outline: none;
}
.vf-btns { display: flex; gap: 8px; }
.vf-btn {
  flex: 1; padding: 9px 0; border: 1px solid rgba(140,190,215,0.28); border-radius: 8px;
  background: rgba(255,255,255,0.05); color: #cfe6f5; font-size: 13px; cursor: pointer;
}
.vf-btn.on { background: linear-gradient(120deg, #2eb08a, #3cbecd); color: #05131c; border-color: transparent; font-weight: 600; }

/* ---- 右上角账号条 ---- */
#accbar {
  position: fixed; top: calc(34px + var(--annoff, 0px)); right: 10px; z-index: 60;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
  pointer-events: none;
}
.acc-chip {
  pointer-events: auto; padding: 3px 10px; border-radius: 11px;
  background: rgba(12,28,40,.62); color: #cfe8ff; backdrop-filter: blur(4px);
  font: 12px/1.6 system-ui, "PingFang SC", sans-serif; border: 1px solid transparent;
}
.acc-net i {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: #41b883; margin-right: 6px; vertical-align: middle;
}
.acc-mail { cursor: pointer; }
.acc-mail.warn { color: #ffd08a; border-color: rgba(255,208,138,.35); }
.acc-mail.ok { color: #7fd6b0; border-color: rgba(127,214,176,.3); cursor: default; }

/* ══════════════════════════════════════════════════════════════════════
   第 45 轮 · PC 端右上角功能三键（背包 / 属性 / 视角）
   ──────────────────────────────────────────────────────────────────────
   移动端一直有这三个键（touch.js 的 tb-bag / tb-stat / tb-first，竖排在右上），
   桌面端却只有底部一行小字提示「I 背包装备 · C 属性 · V 视角」——
   用户要的是「像移动端那样，右上角显示三个功能按钮」。

   为什么是 62 而不是更高：要**高于账号条 #accbar(60)**（否则被它盖住），
   又要**低于面板 .panel(70)** —— 面板一开就占住右上角，三键此时正好被面板
   盖住是**期望行为**（同一个功能不该同时出现两个入口）。
   同理，#annbar 公告条(60) 也在它下面。

   ⚠️ 账号条必须下移让位：它原来 top:34px，和三键同一格 → 会叠在一起。
      这里只在**非触屏**下移（移动端布局另有 .tzone-sys，不受影响）。
   ══════════════════════════════════════════════════════════════════════ */
#pcbtns {
  position: fixed; top: calc(32px + var(--annoff, 0px)); right: 14px; z-index: 62;
  display: flex; gap: 8px;
}
html.touch #pcbtns { display: none !important; }      /* 手机上走 .tzone-sys */
#pcbtns button {
  position: relative;                    /* 快捷键小字 i 要相对按钮定位 */
  width: 38px; height: 38px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; line-height: 1;
  color: #eaf4ff; cursor: pointer;
  background: rgba(12, 28, 40, .62);
  border: 1px solid rgba(140, 190, 215, .22);
  border-radius: 11px;
  backdrop-filter: blur(6px);
  transition: background .16s, border-color .16s, transform .16s;
}
#pcbtns button:hover {
  background: rgba(46, 176, 138, .30);
  border-color: rgba(255, 208, 138, .45);
  transform: translateY(-1px);
}
#pcbtns button:active { transform: translateY(0); }
/* 视角键：开着第一人称时常亮（和状态同步，别让玩家猜当前是第几人称） */
#pcbtns button.on {
  background: rgba(255, 208, 138, .22);
  border-color: rgba(255, 208, 138, .55);
  color: #ffd08a;
}
/* 三键下面的快捷键小字，只在 hover 时浮出，平时不占地方 */
#pcbtns button i {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  margin-top: 3px;
  font-style: normal; font-size: 10.5px; white-space: nowrap;
  color: var(--dim); opacity: 0; transition: opacity .16s;
  pointer-events: none;
}
#pcbtns button:hover i { opacity: .9; }
/* 第 120 轮：表情键的菜单开着时常亮（和移动端 .tbtn.tb-emote.fan-on 一致）。
   ⚠️ 用 .fan-on 而不是 .on —— .on 由 pointerdown/up 控制，抬手就灭了，
      菜单却还开着，玩家会以为没生效。 */
#pcbtns button.fan-on {
  background: rgba(255, 208, 138, .22);
  border-color: rgba(255, 208, 138, .60);
  color: #ffd08a;
}
/* r141：社交键的「有人邀你组队」高亮（和移动端 .tb-social.has-inv 同一套语义）。
   青色 —— 名牌/队伍那一族的颜色，玩家一看就知道是"跟人有关"。 */
#pcb-social.has-inv {
  background: rgba(120, 210, 245, .24);
  border-color: rgba(140, 235, 255, .9);
  color: #d8f4ff;
  box-shadow: 0 0 14px rgba(110, 210, 245, .5);
}
html:not(.touch) #accbar { top: calc(78px + var(--annoff, 0px)); }

/* ⚠️ 第 45 轮：桌面端也得给 #app 提一个 **body 层**的 z-index。
 *   #app 是 position:fixed → 它自己就是一个堆叠上下文（第 36 轮用 elementsFromPoint
 *   实测过，见下面的移动端块）。面板的 z-index:70 只在 #app **内部**排序，
 *   而公告条 #annbar 是 net.js 挂在 <body> 下的 z-index:60 ——
 *   桌面端面板无论调多高都压不过它（跨栈比较没意义），顶上一条 26px 的横幅
 *   会永远糊在面板标题上。移动端当年已经修过（html.touch #app z-index:2 + 面板 70），
 *   桌面端一直漏着。取 65：> 公告条(60)，< 拖拽幽灵(150)。
 *   ⚠️ 内部的相对次序完全不变 —— 抬的是整个 #app，不是里面哪一个。 */
html:not(.touch) #app { z-index: 65; }
/* ══════════════════════════════════════════════════════════════════════
   第 36 轮 · 移动端布局（按需求重排为「原神式」）
   ══════════════════════════════════════════════════════════════════════
   布局分区：
     ┌─────────────────────────────────────────┐
     │ ① 公告条（吸顶滚动，全宽，最高层）        │  ← 独立条，不与下面三者重叠
     │ ② 小地图（圆形，左上角）      ⑦ 功能键   │  ← 右上竖排：背包/属性/视角
     │                              （三键）    │
     │                              ⑥ 玩家名    │  ← 左下（公告栏下方）
     │                    ⑧ 状态卡             │
     │ ⑤ 视角区（整屏）                        │
     │ ③ 摇杆区        ④ 战斗键簇（右下弧形）  │  ← 滑上浮现跑键
     └─────────────────────────────────────────┘

   键盘映射（原神同类手游的通行做法）：
     · 摇杆 = WASD
     · 摇杆上方浮现跑键 = Shift（滑动锁定）
     · 右下弧形三键     = 攻击 / 交互 / 跳跃
     · 右上竖排三键     = 背包 / 属性 / 第一人称
     · 点左上圆形小地图 = 全屏地图（取消了独立地图键）

   ⚠️ 桌面端完全不受影响：所有规则都包在 html.touch / .tzone 内，
     非触屏设备不会命中任何一条。
   ══════════════════════════════════════════════════════════════════════ */

/* ---------- 触摸遮罩：挡掉双击缩放 / 长按菜单 / 橡皮筋 ---------- */
/*⚠️ 第 36 轮：这个遮罩是 `inset:0` 的全屏层，z 一旦压过 UI（小地图等）就点不到它们
   （实测 elementFromPoint 栈顶永远是它）。而它**必须存在**（否则 iOS 双击缩放、
   长按菜单、橡皮筋全都回来）。
   解法：**保持最低的 z（1）**，UI 全部高于它 —— UI 都设pointer-events:auto，
   照样能正常接收触摸；遮罩只在「UI 没覆盖的地方」起作用，那正是它唯一要干的活。 */
.touch-blocker {
  position: fixed; inset: 0; z-index: 1;
  touch-action: none;                /* 关键：禁止浏览器手势，交给游戏 */
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
}
html.touch body, html.touch #view { touch-action: none; }
html.touch #view { -webkit-user-drag: none; }

/* 三个热区的公共底座：默认不吃触摸，只有具体子元素开 pointer-events。
   ⚠️ 热区本身必须**透明且无背景**——它铺在屏幕上是「死区」，
   但在它上面再摆按钮/小地图，靠z-index 压住（见各区块）。 */
.tzone {
  position: fixed;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

/* ══════════════ 面板打开时：虚拟控制层整体让开 ══════════════ */
/* ⚠️ 第 36 轮修 BUG：手机上打开属性面板，六个标签「看得见、点不动」。
   根因**不是 z-index 不够**（面板已经 70，比所有层都高），
   而是触摸层自己在 hit-test 上赢了：
     .touch-blocker(z=5)  position:fixed  inset:0   pointer-events:auto
     .tzone-look(z=18)   position:fixed  占大半屏   pointer-events:auto
   面板里的标签是 z-index:auto 的普通流内容 → 命中测试排在它们之后。
   元素栈实测栈顶是 DIV.tzone / DIV.touch-blocker，标签只在第三层。
   正解：面板一开就给这些层加 .tzone-off（彻底不吃触摸），
        面板一关（closeAll）去掉。不能靠调高面板 z-index —— 那是无效的。 */
.tzone-off, .touch-blocker.tzone-off {
  pointer-events: none !important;
  visibility: hidden;
}
/* 虚拟按键本体也藏起来：面板盖着时它们不该透出来（视觉上露半个按钮很难看，
   而且玩家会误触到看不见的键） */
.tzone-hide { display: none !important; }

/* ══════════════ ① 公告条：独立吸顶，抬高z-index ══════════════ */
/* ⚠️ 第 36 轮修 BUG：公告条 #annbar 是 z-index:60（net.js 动态注入的 CSS），
   而面板 .panel 只有 25 / 移动端覆盖成 40 → **公告条压住面板**，
   手机上打开属性面板时顶部的六个标签正好被这条 26px 高的横条盖住，
   点不到（用户报「点击后无反应」）。
   正解：把面板提到**比公告条更高**，而不是去压公告条 ——
   公告条是内容，横幅盖住它没有意义，被面板盖住才叫问题。 */
html.touch .panel { z-index: 70 !important; }
/* 公告条本身在触屏上收窄一点，给小地图让位；pointer-events 已由 net.js 关掉 */
html.touch #annbar { font-size: 11px !important; opacity: .92; }

/* ══════════════ ② 小地图：圆形、左上、可点 ══════════════ */
/* ⚠️ 第 36 轮踩了三次的坑：光调 z-index **完全无效**。
     根因（elementsFromPoint 栈实测）：
        [touch-blocker(子BODY), mmcv(子minimap), minimap(子**#app**), view, #app]
     `#app` 是 `position:fixed` + `z-index:auto` → **它是一个堆叠上下文**，
     于是 `#minimap` 的 z-index:7 只在 `#app` 内部排序，永远压不过挂在
     `<body>` 下的 `.touch-blocker`（z:1，但层级更高）。
     z 从 30 改到 6 再改到 7 都没用 —— 跟它不在同一个栈里比。
     正解：给祖先链提权。这里给 `#app` 一个 z-index，让它整体参与 body 层的排序，
     再让小地图在 #app 内拿最高的那个值。 */
html.touch #app {
  position: fixed;
  /* ⚠️ 第 36 轮踩了四次的坑，根因值得写清楚：
     小地图 `#minimap` 是 `#app` 的子元素，而 `#app` 是 `position:fixed`
     → **它是一个堆叠上下文**。于是小地图的 z-index 只在 #app 内部排序，
     永远压不过挂在 <body> 下的 `.touch-blocker`。z 从 30→6→7 全都无效。
     正解：给 #app 一个 **body 层的** z-index，让它整体参与外层排序。
     这个值必须 **< 70**（面板 z）—— #app 抬到比面板高会把面板一起压住，
     标签就变成「被 #app 挡」（实测命中栈里出现 BODY 就是这个症状）。
     取 2：小地图(内部 40)压过 blocker(1)，#app(2)远低于面板(70)。
     ⚠️ 不要给 #app 加 pointer-events:none —— 面板/背包都在它内部，
     一关就把它们的点击也关了（#app 是堆叠上下文的创建者，
     pointer-events 会被子元素各自的设置覆盖，但设 none 本身会让
     整个子树默认不可点，得逐个子元素放开，得不偿失）。 */
  z-index: 2;
}
html.touch #minimap {
  position: fixed;
  z-index: 40;                /* #app 内部最高（面板是 70，仍在它之上） */
  /* ⚠️ r138：左上 → **右上**。O&C2 的布局是「左上头像 / 右上罗盘」，
     而原来我们正好反着（小地图占左上），左上就腾不出地方放圆头像。
     换过来之后横屏的 .tzone-stick 也不必再从 122px 往下让（见 landscape 段）。 */
  left: auto;
  right: max(10px, env(safe-area-inset-right));
  top: calc(env(safe-area-inset-top, 0px) + 8px + var(--annoff, 0px));
  width: 84px; padding: 5px;
  border-radius: 50%;                    /* 圆形 */
  background: rgba(10, 22, 32, .72);
  border: 1.5px solid rgba(255, 255, 255, .18);
  overflow: hidden;
  pointer-events: auto;                  /* 手机上要能点开大地图 */
  cursor: pointer;
}
html.touch #mmcv {
  width: 72px; height: 72px;
  border-radius: 50%;
  border: none;                          /* 方形边框会在圆角外露出来 */
}
html.touch .mm-cap {
  display: none !important;              /* 不显示坐标（用户要求） */
}
html.touch .mm-buff { display: none !important; }

/* ══════════════ ③ 摇杆区（**左半屏任意位置**）══════════════ */
/* 第 36 轮（用户需求）：「左手拇指按住屏幕左侧任意位置，出现浮动虚拟摇杆」——
   所以热区是整个左半屏，落点即摇杆中心，不用去瞄固定的小圆点。
   宽度必须 < 50vw，否则会和右侧的按钮热区交叠（实测 46vw+56vw 会重叠 8px，手指会误触）。 */
.tzone-stick {
  position: fixed; z-index: 20;
  left: 0; bottom: 0;
  width: 48vw; max-width: 340px; height: 100vh; max-height: none;
  touch-action: none;
}

/* 摇杆底盘：按下才出现（落点即中心） */
.tstick {
  position: fixed; z-index: 21;
  width: 124px; height: 124px;
  margin-left: -62px; margin-top: -62px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.10) 0%, rgba(255,255,255,.04) 62%, transparent 72%);
  border: 2px solid rgba(255,255,255,.22);
  opacity: 0; transition: opacity .13s;
  pointer-events: none;
}
.tstick.on { opacity: 1; }
.tstick-knob {
  position: absolute; left: 50%; top: 50%;
  width: 52px; height: 52px;
  transform: translate(-50%,-50%);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #e8f2ff, #9fc0e0);
  box-shadow: 0 2px 10px rgba(0,0,0,.45);
}

/* ══════════════ ④ 战斗键簇（右下，弧形三键） ══════════════ */
/* 不排成一行 —— 三个键围成弧，拇指自然扫过就能按到（原神/原神类手游通行做法）

   ⚠️ 第 37 轮：这里的 `bottom:0` 锚的是**大视口**，Chrome 地址栏一出现就会被
      推到可视区外。所以 transform 由 JS（touch.js 的 autoFitTouchLayout）驱动：
        --tbs   整体缩放（原点钉在 100% 100% = 右下角，缩放只会往角上收）
        --cbtdy 整体上移补偿（把掉出可视下沿的部分拉回来）
      CSS 只负责声明钩子，真正的数字全部由实测收敛得出。 */
.tzone-btns {
  position: fixed; z-index: 22;
  right: 0; bottom: 0; left: auto;
  width: 50vw; max-width: 280px; height: 42vh; max-height: 260px;
  touch-action: none;
  pointer-events: none;
  transform: translate3d(0, var(--cbtdy, 0px), 0) scale(var(--tbs, 1));
  transform-origin: 100% 100%;
}
.tzone-btns .tbtn { pointer-events: auto; position: absolute; }
/* 攻击（大，右下主位）/ 交互（中上）/ 跳跃（左上）—— 构成一条斜弧 */
.tbtn.tb-atk {
  right: 18px; bottom: 26px; width: 82px; height: 82px;
  border-color: rgba(255, 158, 116, .58);
  background: linear-gradient(180deg, rgba(72, 38, 30, .84), rgba(38, 20, 16, .90));
}
.tbtn.tb-atk .tb-ico { font-size: 30px; }
/* ⚠️ r138：「互动 ✋」这个独立键已取消，和「跳 ⤒」合并成上下文主键（.tb-jump）。
   原来的 .tb-use 几何（base / landscape / t-h-hi / t-h-mid / t-h-lo 五处）随之删除 ——
   留着是死规则，而且会让人以为还有第五个键要摆位置。 */
.tbtn.tb-jump {
  right: 30px; bottom: 118px; width: 62px; height: 62px;
  border-color: rgba(140, 190, 255, .52);
  background: linear-gradient(180deg, rgba(28, 48, 74, .84), rgba(14, 26, 40, .90));
}
/* r138：上下文主键进入「互动态」—— 附近有可交互物，此时点按 = 互动而不是跳。
   换成原来 ✋ 那套青绿配色，让玩家一眼看出"这个键现在干的是另一件事"；
   冰蓝（跳）↔ 青绿（互动）在色相上相隔足够远，不靠文字也分得清。 */
.tbtn.tb-jump.ctx {
  border-color: rgba(126, 214, 176, .68);
  background: linear-gradient(180deg, rgba(30, 66, 54, .88), rgba(16, 34, 28, .94));
}
/* 互动态的呼吸光：纯装饰，取不到也不影响任何判定（CSS 动画在无头环境会冻在 t=0） */
.tbtn.tb-jump.ctx { animation: ctxBreath 1.6s ease-in-out infinite; }
@keyframes ctxBreath {
  0%, 100% { box-shadow: 0 3px 12px rgba(0, 0, 0, .42), 0 0 0 0 rgba(126, 214, 176, 0); }
  50%      { box-shadow: 0 3px 12px rgba(0, 0, 0, .42), 0 0 0 5px rgba(126, 214, 176, .22); }
}

/* ⑤ 视角区：整屏（热区在按钮之下，指哪拖哪） */
.tzone-look {
  position: fixed; z-index: 18;
  right: 0; top: 0;
  width: 56vw; height: 100vh;
  touch-action: none;
}

/* ══════════════ ⑥ 跑步键：摇杆上方浮现，可锁定 ══════════════ */
.tsprint {
  position: fixed; z-index: 24;
  width: 60px; height: 60px;
  margin-left: -30px; margin-top: -30px;    /* JS 用 left/top 定位，中心对齐拇指 */
  border-color: rgba(255, 205, 120, .78) !important;
  background: linear-gradient(180deg, rgba(92, 66, 28, .92), rgba(48, 34, 14, .94)) !important;
  pointer-events: none;                       /* 命中判定走摇杆的 hitTest，这个只作视觉 */
  transition: transform .16s cubic-bezier(.34, 1.56, .64, 1), box-shadow .16s, background .16s;
  opacity: .55;                               /* 未激活时半透明，提示"可以拖过来" */
}
.tsprint.show { opacity: 1; }
/* 需求：锁定时「»」平滑变为「🔒」，伴随缩放 + 发光反馈 */
.tsprint.locked {
  background: linear-gradient(180deg, rgba(255, 196, 96, .98), rgba(196, 118, 28, .98)) !important;
  box-shadow: 0 0 22px rgba(255, 186, 80, .85), 0 0 44px rgba(255, 160, 50, .45);
  transform: scale(1.14);
  animation: lockPulse .42s ease-out;
}
@keyframes lockPulse {
  0% { transform: scale(0.72); }
  55% { transform: scale(1.26); }
  100% { transform: scale(1.14); }
}
.tsprint .tb-ico { transition: none; }

/* ══════════════ ⑦ 功能键簇（右上，竖排三键） ══════════════ */
/* ⚠️ 第 37 轮：和战斗簇一样，几何由 CSS 定、位置补偿由 JS 实测给：
     --tss    整体缩放（原点 100% 0 = 右上角）
     --sysdx  横向错开（缩到最小仍撞车 / 右出屏时的最后手段）
     --sysdy  往下推（顶出可视上沿时，比如刘海横屏 + 地址栏）
   这三档保证「右上三键永远不压右下三键」，跟设备高度、地址栏状态都无关。 */
.tzone-sys {
  position: fixed; z-index: 22;
  /* r138：小地图搬到右上后，功能键抽屉的开关要让到**小地图左侧**（O&C2 的位置）。
     84(小地图) + 10(右边距) + 8(间隙) = 102。竖屏在下面各自再校准一次。 */
  right: 102px;
  top: calc(env(safe-area-inset-top, 0px) + 34px + var(--annoff, 0px));
  display: flex; flex-direction: column; gap: 8px;
  padding: 0;
  touch-action: none;
  pointer-events: none;
  transform: translate3d(var(--sysdx, 0px), var(--sysdy, 0px), 0) scale(var(--tss, 1));
  transform-origin: 100% 0;
}
.tzone-sys .tbtn { pointer-events: auto; width: 46px; height: 46px; }
.tzone-sys .tb-ico { font-size: 18px; }
.tzone-sys .tb-txt { font-size: 8.5px; }

/* ⚠️ 第 37 轮：右上三键之前**没写 background**，于是吃到了 <button> 的 UA 默认底色
   （buttonface，浅灰近白）。深色游戏 UI 上顶着三个白圆盘，一眼就看出是没做完的东西。
   战斗三键（tb-atk/use/jump）各写了 background 所以正常 —— 漏的只有功能三键。
   这里按「同族深色底 + 各自一抹强调色」补齐，和右下战斗键同一套视觉语言。 */
.tbtn.tb-bag {
  border-color: rgba(240, 198, 120, .56);
  background: linear-gradient(180deg, rgba(78, 60, 26, .86), rgba(42, 31, 12, .92));
}
.tbtn.tb-stat {
  border-color: rgba(158, 200, 255, .52);
  background: linear-gradient(180deg, rgba(28, 52, 78, .86), rgba(14, 26, 40, .92));
}
/* 第 53 轮：奖励中心键。⚠️ 必须自己写 background —— 第 37 轮踩过：
   功能三键漏写底色就会吃 <button> 的 UA 默认 buttonface（浅灰近白），
   在深色 UI 上顶着一个白圆盘。新键一律配底色。 */
.tbtn.tb-mail {
  border-color: rgba(240, 198, 120, .56);
  background: linear-gradient(180deg, rgba(74, 56, 24, .88), rgba(40, 30, 12, .92));
}
/* 有可领奖励时的红点（和 PC 右上角信封键同一套语言）。
   ⚠️ 必须 position:relative —— .tzone-sys 里的按键是静态定位，
   不给的话 ::after 会去贴 .tzone-sys 的角上，红点飘到键簇外面。 */
.tbtn.tb-mail { position: relative; }
.tbtn.tb-mail.has-new::after {
  content: ''; position: absolute; top: 3px; right: 3px;
  width: 8px; height: 8px; border-radius: 50%;
  background: #ff5a5a; box-shadow: 0 0 0 2px rgba(10, 18, 26, .8);
}
.tbtn.tb-first {
  border-color: rgba(200, 170, 255, .52);
  background: linear-gradient(180deg, rgba(52, 38, 84, .86), rgba(26, 18, 44, .92));
}
/* r144：画质键（齿轮）。钢灰色 —— "设置/机械"的通用色，和金(背包)/蓝(属性)/
   紫(视角)/青(社交)/橙(表情)都能一眼分开。
   ⚠️ 必须自己写 background：第 37 轮踩过 buttonface 坑（漏写底色的新键在深色 UI 上
      顶着 UA 默认的浅灰圆盘）。 */
.tbtn.tb-gfx {
  border-color: rgba(190, 200, 214, .50);
  background: linear-gradient(180deg, rgba(46, 52, 62, .88), rgba(22, 26, 32, .93));
}
/* r141：社交键。⚠️ 同样必须自己写 background（第 37 轮的 buttonface 坑）。
   青色系 —— 跟"人"相关的功能统一用这支色（队伍名牌 .mtag.mate 也是），
   和背包(金)/属性(蓝)/视角(紫)/邮件(暗金)都能一眼分开。
   ⚠️ .has-inv 是"有人邀你组队"的高亮，由 main.js 推过来（同 tb-mail 的红点套路）。 */
.tbtn.tb-social {
  border-color: rgba(126, 200, 230, .52);
  background: linear-gradient(180deg, rgba(20, 52, 70, .88), rgba(10, 26, 36, .92));
  position: relative;
}
.tbtn.tb-social.has-inv {
  border-color: rgba(140, 235, 255, .9);
  box-shadow: 0 0 14px rgba(110, 210, 245, .5), 0 3px 12px rgba(0, 0, 0, .42);
}

/* ══════════════ ⑦-2 第 114 轮：潜行键（右下·小一号·贴边） ══════════════ */
/* 用户需求：「潜行是动作，应该放在右下角那个区域才对，但潜行不怎么用，
   按钮要小而且边缘化」。所以：
     · 底板 42px（攻击键 78px 的一半多一点），塞在攻击键**左侧**、贴屏幕下沿；
     · 不给它强调色，用低饱和的墨绿 —— 视觉重量明显低于战斗三键；
     · 潜行是**切换态**，开着时按键要常亮（.sneak-on），否则玩家不知道自己蹲着。
   ⚠️ 又必须自己写 background：.tbtn 基础样式没设底色，新键会吃 <button> 的
      UA 默认 buttonface（浅灰近白），深色 UI 上顶一个白圆盘（第 37 轮踩过）。 */
.tbtn.tb-sneak {
  right: 106px; bottom: 20px; width: 42px; height: 42px;
  border-color: rgba(140, 210, 190, .40);
  background: linear-gradient(180deg, rgba(24, 50, 44, .82), rgba(10, 22, 20, .90));
  opacity: .92;
}
.tbtn.tb-sneak .tb-ico { font-size: 15px; }
.tbtn.tb-sneak .tb-txt { font-size: 8px; }
.tbtn.tb-sneak.sneak-on {
  border-color: rgba(150, 240, 210, .85);
  background: linear-gradient(180deg, rgba(42, 104, 86, .95), rgba(18, 52, 44, .96));
  box-shadow: 0 0 16px rgba(110, 230, 190, .55), 0 3px 12px rgba(0, 0, 0, .42);
  opacity: 1;
  transform: none;                 /* .tbtn:active 会缩，常亮态别跟着缩 */
}

/* ══════════════ ⑦-3 第 114 轮：表情键 + 扇形菜单 ══════════════ */
/* 点一下只开/收菜单，不播任何动作；真正播是在菜单格子上再点一下。 */
.tbtn.tb-emote {
  border-color: rgba(255, 196, 120, .50);
  background: linear-gradient(180deg, rgba(74, 50, 26, .86), rgba(38, 24, 12, .92));
}
.tbtn.tb-emote.fan-on {
  border-color: rgba(255, 210, 140, .92);
  box-shadow: 0 0 16px rgba(255, 190, 110, .50), 0 3px 12px rgba(0, 0, 0, .42);
}

/* 菜单本体：一个 0×0 的锚点，用 fixed 钉在表情键中心；
   6 个格子各自按 --dx/--dy（**相对按钮中心**的偏移）飞出去，落成 3 列 × 2 行。
   位置由 touch.js 的 makeEmoteFan 按实测 rect 算：竖屏功能簇是横排（挂在按钮下方）、
   横屏是竖排（挂不下就翻到按钮上方），两种布局都稳。
   ⚠️⚠️ z-index 68 是量出来的，别再往下调（r122 修的正是这里）：
     菜单挂在 <body> 下，而 **桌面端 `html:not(.touch) #app{z-index:65}`** ——
     #app 是一个整块的堆叠上下文，菜单只有 > 65 才画得出来。
     原来写 26：手机上（#app 是 z-index 2）正常，**电脑上菜单"打开了却什么都看不见"**，
     因为整块被 #app 压在下面（DOM 里 .temote-fan 明明是 on，屏幕上就是没有）。
     面板(.panel z:70)活在 #app **内部**，所以 68 永远轮不到跟它比；
     而且面板一开菜单就被 setTouchIdle / closeAll 收掉了。 */
.temote-fan {
  position: fixed; left: -9999px; top: -9999px;
  width: 0; height: 0; z-index: 68;
  pointer-events: none;
  -webkit-tap-highlight-color: transparent;
}
.temote-it {
  position: absolute; left: 0; top: 0;
  width: 48px; height: 48px; margin: 0; padding: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  border-radius: 50%; border: 1.5px solid rgba(255, 255, 255, .30);
  color: #eaf4ff; font: inherit;
  background: linear-gradient(180deg, rgba(44, 44, 58, .94), rgba(16, 16, 24, .96));
  box-shadow: 0 3px 12px rgba(0, 0, 0, .5);
  cursor: pointer;
  /* 收起态：不可点、缩在锚点上（否则 6 个隐形按钮叠在表情键中心，乱吃点击） */
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(.35);
  transition: transform .17s cubic-bezier(.34, 1.56, .64, 1), opacity .13s;
  touch-action: none;
}
.temote-it .te-ico { font-size: 18px; line-height: 1; }
.temote-it .te-tx { font-size: 9px; line-height: 1; opacity: .85; }
.temote-fan.on .temote-it {
  pointer-events: auto;
  opacity: 1;
  transform: translate(-50%, -50%) translate(var(--dx, 0px), var(--dy, 0px)) scale(1);
  /* 依次弹出，而不是六格「啪」一下全出现 */
  transition-delay: calc(var(--i, 0) * 24ms);
}
.temote-fan.on .temote-it:active {
  transform: translate(-50%, -50%) translate(var(--dx, 0px), var(--dy, 0px)) scale(.88);
  border-color: rgba(255, 210, 140, .9);
  background: linear-gradient(180deg, rgba(96, 72, 34, .96), rgba(44, 30, 14, .98));
}

/* ══════════════ ⑧ 手机端不再单独放玩家名（r135） ══════════════ */
/* 以前：名字放左下（公告栏下方、状态卡上方），邮箱验证与否不显示。
   r135 改掉的理由 —— 左下角是**摇杆热区**（50vw×42vh），那行名字正好压在拇指底下；
   而且它和联机对话栏里的名字是同一份信息。
   ⇒ 手机上整条 #accbar 收掉：名字挪进对话栏顶行，`在线 N` 本来就在对话栏里。
     账号入口不受影响 —— 那个在状态卡第一行 `.hud-lv.acc`（见 gate.js）。 */
html.touch #accbar { display: none !important; }

/* ══════════════ ⑨ 状态卡：手机端 = 圆头像 + 三条阶梯资源条（r138 · F 方案）
 * 用户需求（参考《混沌与秩序2》的头像 HUD，但明确要**不对称、数据不粘连、有等级位**）：
 *   ① 左上角圆形头像，外面一圈是经验进度；
 *   ② 头像右侧**三条各自独立的资源条**（生命 / 能量 / 体力），长度**递减成阶梯**，
 *      每条都把完整数字（如 86/120）直接画在条子右端 —— 不用去 C 面板查；
 *   ③ 等级做成独立小徽章，挑在资源条列的右上角（同时是账号菜单入口）。
 *
 * 为什么 R138 第一版（四条 conic-gradient 同心环）被否：
 *   四条环同心 = 视觉上必然对称、必然粘连成一坨；数字没地方放（只能藏起来）；
 *   等级只能挂在盘下方，和环抢位置。F 方案把这些拆开：
 *   **头像是圆的（唯一对称元素），资源条是直角的（信息密度最高），两者故意不同形**。
 *
 * 几何（全部由 --hw / --bw 两个变量推导，横竖屏只改这两个数）：
 *
 *      ╭──────╮              ┌Lv.12 旅人▾┐   ← 徽章右对齐到条列的右沿
 *      │ 蝶   │  ████████████ 86/120      ← hp：最长（--bw）
 *      │ 1240 │  ██████████   40/60        ← ep：87%
 *      ╰──────╯  ████████     78/90        ← sta：74%
 *        ↑经验环（金，最外一圈最细）+ 环内底部一行经验数字
 *
 * ⚠️⚠️ 三条资源条**必须各自独立成块**（自己的深色槽 + 圆角 + 4px 行距）：
 *   一旦把它们画进同一个容器背景里，就又变回"一坨"了 —— 这正是用户否掉第一版的理由。
 * ⚠️⚠️ 数字画在条子**内部右端**（.em 是 .hud-bar 的绝对定位子元素），不是条子右边另起一列：
 *   另起一列的话，三条的右沿就必须对齐，阶梯感就没了；画在里面才能跟着各自的长度走。
 */
html.touch #hud {
  --hw: 86px;                 /* 头像直径（横屏 78） */
  --bw: 116px;                /* 最长那条资源条的宽度（横屏 104） */
  --bg: 6px;                  /* 头像与资源条之间的缝 */
  --bh: 17px;                 /* 每条资源条的行高（横屏 16） */
  --bgap: 4px;                /* 行距 —— 靠它把三条"拆开"，不能省 */
  position: fixed; z-index: 25;
  left: max(8px, env(safe-area-inset-left));
  top: calc(env(safe-area-inset-top, 0px) + 8px + var(--annoff, 0px));
  right: auto; bottom: auto;
  transform: none;
  width: calc(var(--hw) + var(--bg) + var(--bw));
  height: var(--hw);
  padding: 0; border: none; border-radius: 0;
  background: none; backdrop-filter: none;
  font-size: 10.5px; line-height: 1.25;
  pointer-events: none;        /* 只有 .hud-lv / .hud-foot 里的入口单独开 auto */
}
/* 两个老的结构壳子（桌面端是 flex 排版）在手机上必须**回到静态定位**：
 * 里面每一条资源条都是 absolute，壳子若还留着 inset:0 就会各自成为包含块，
 * 层级一变，--hw 推导出来的坐标就全错。留着 display:block 只为不破坏 DOM 结构。 */
html.touch .hud-top,
html.touch .hud-id { position: static; inset: auto; display: block; margin: 0; }

/* ── 圆形头像 ──
 * 圆 + 渐变 + 内高光 + 外暗缝。那条 1.5px 的暗缝是给经验环让位的：
 * 脸的半径 39、经验环内径 40.2，中间若直接露背景，金环看起来会"飘"在脸上。 */
html.touch .hud-face {
  position: absolute; left: 0; top: 0;
  width: var(--hw); height: var(--hw);
  margin: 0; flex: none; border-radius: 50%;
  padding-bottom: calc(var(--hw) * .10);    /* 把字母往上抬，给底部的经验数字让位 */
  font-size: calc(var(--hw) * .215);
  color: #20140a; text-shadow: 0 1px 0 rgba(255, 255, 255, .32);
  background:
    radial-gradient(circle at 34% 24%, rgba(255, 255, 255, .55), transparent 54%),
    linear-gradient(150deg, #ffd98f 0%, #e0a95c 46%, #8a5f26 100%);
  box-shadow:
    0 3px 14px rgba(0, 0, 0, .62),
    0 0 0 1.5px rgba(6, 12, 18, .92),        /* 脸与经验环之间的暗缝 */
    inset 0 0 0 2px rgba(255, 255, 255, .26),
    inset 0 -10px 18px rgba(0, 0, 0, .22);
}

/* ── r158：圆里放**真人头像**（运行时截的那一张，见 src/headshot.js） ──
 * ⚠️ 照片是**内联 background-image**，能压过上面那条 `background:` 简写
 *    （内联样式 > 样式表，不需要 !important）。所以这里只负责三件事：
 *    ① 把给字母留的 padding 收掉（不然照片被顶上去了）；
 *    ② 把字母变透明（textContent 还有 gate.js / 刷新 HUD 在写，留着当兜底）；
 *    ③ 让图铺满 + 换一套 box-shadow（原来那圈 inset 白高光叠在照片上会糊成一层雾）。
 * ⚠️⚠️ 特异度必须压过 `html.touch .hud-face`（[0,2,1]）—— 只写 `.hud-face.hasface`
 *    是 [0,2,0]，会被那条 `padding-bottom: calc(var(--hw)*.10)` 反杀：
 *    实测结果就是"类加上了、图也有了，但照片被顶上去 10.8px、字母还黑压压盖在脸上"。
 *    所以圆这一档必须带 `html.touch` 前缀（和参照规则同层，靠源码顺序赢）。
 * ⚠️ 失败（截图拿不到 / 还没截）时这个类不会加上，圆里继续显示名字首字。 */
.hud-face.hasface {
  padding-bottom: 0;
  color: transparent;
  text-shadow: none;
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
}
html.touch .hud-face.hasface {
  padding-bottom: 0;
  color: transparent;
  text-shadow: none;
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
  box-shadow:
    0 3px 14px rgba(0, 0, 0, .62),
    0 0 0 1.5px rgba(6, 12, 18, .92),        /* 脸与经验环之间的暗缝，和字母版一致 */
    inset 0 0 0 1px rgba(255, 255, 255, .14),
    inset 0 -14px 22px rgba(0, 0, 0, .32);   /* 底部压暗，让经验数字压得住 */
}

/* ── 经验：头像外面一整圈金环（最外、最细）+ 环内底部一行数字 ──
 * ⚠️⚠️ conic-gradient 和 mask **写在 .hud-bar.xp > i 上，不能写在 .hud-bar.xp 上**：
 *   mask 会把该元素**整棵子树**裁成环带，写在父层的话环里那行 "1240/2000" 会被裁没
 *   （只剩环带那一圈字，看起来像乱码）。 */
html.touch #hud .hud-bar.xp {
  position: absolute;
  left: calc(var(--hw) * -.055); top: calc(var(--hw) * -.055);
  width: calc(var(--hw) * 1.11); height: calc(var(--hw) * 1.11);
  margin: 0; padding: 0;
  transform: none; border-radius: 50%;
  background: none;
}
html.touch #hud .hud-bar.xp > i {
  position: absolute; left: 0; top: 0;
  width: 100%; height: 100%;
  border-radius: 50%; overflow: visible;
  box-shadow: none;
  background: conic-gradient(#ffd08a calc(var(--p, 0) * 1turn),
                             rgba(255, 255, 255, .15) 0);
  -webkit-mask: radial-gradient(farthest-side,
      #0000 calc(100% - var(--hw) * .036), #000 calc(100% - var(--hw) * .036 + .8px));
          mask: radial-gradient(farthest-side,
      #0000 calc(100% - var(--hw) * .036), #000 calc(100% - var(--hw) * .036 + .8px));
}
/* 进度由 conic-gradient 的 --p 表达，--w 那条填充不要了（留着会被裁成半环） */
html.touch #hud .hud-bar.xp > i::after { display: none; }
html.touch #hud .hud-bar.xp > em {
  /* ⚠️ 用 left:50% + translateX(-50%) 让**盒子本身**就是文字宽度（absolute 的
   * shrink-to-fit），不要写 left:0;right:0 —— 那样盒子会横跨整个环（86.6px 宽），
   * 视觉上文字虽然在圆内，但盒子两端在圆外，自检量矩形会判"经验数字跑到圆外面"。 */
  position: absolute; left: 50%; right: auto; top: auto;
  transform: translateX(-50%);
  /* 经验环那个盒子比脸大 1.11 倍（上下各多出 --hw*.055），所以这个 bottom 是
   * 「相对环外沿」的：先退回环多出来的那一圈，再留 5px 呼吸位，字就落在脸内侧底部。
   * 直接写 .085hw 会贴着圆的下沿压到金环上（实测字底 84、脸底 86，只差 2px）。 */
  bottom: calc(var(--hw) * .055 + 12px);
  text-align: center;
  font-style: normal; font-size: max(7px, calc(var(--hw) * .098));
  font-weight: 700; line-height: 1; letter-spacing: -.2px;
  color: #ffd08a;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95);
  white-space: nowrap;
}

/* ── 生命 / 能量 / 体力：三条左端对齐、长度递减的独立横条 ── */
html.touch #hud .hud-bar.hp,
html.touch #hud .hud-bar.ep,
html.touch #hud .hud-bar.sta {
  position: absolute;
  left: calc(var(--hw) + var(--bg));
  transform: none;
  height: var(--bh);
  margin: 0; padding: 0;
  border-radius: calc(var(--bh) * .5);
  background: none;
}
html.touch #hud .hud-bar.hp {
  top: 22px;                              /* 徽章(≈19) + 3 缝 */
  width: var(--bw);
}
html.touch #hud .hud-bar.ep {
  top: calc(22px + var(--bh) + var(--bgap));
  width: calc(var(--bw) * .87);
}
html.touch #hud .hud-bar.sta {
  top: calc(22px + (var(--bh) + var(--bgap)) * 2);
  width: calc(var(--bw) * .74);
}
/* 槽：深色底 + 1px 内描边 + 顶部内阴影（做成"嵌进去"的凹槽感，而不是贴上去的色块） */
html.touch #hud .hud-bar > i {
  position: absolute; left: 0; top: 0;
  width: 100%; height: 100%;
  border-radius: inherit; overflow: hidden;
  background: linear-gradient(180deg, rgba(4, 10, 16, .88), rgba(0, 0, 0, .66));
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .15),
    inset 0 1px 2px rgba(0, 0, 0, .72);
}
/* 填充：顶部一条亮边 + 底部压暗 = 圆柱高光，比纯色块耐看 */
html.touch #hud .hud-bar > i::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--w, 0%);
  border-radius: inherit;
  transition: width .18s linear;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .34),
    inset 0 -1px 2px rgba(0, 0, 0, .26);
}
html.touch #hud .hud-bar.hp > i::after {
  background: linear-gradient(180deg, #ff9a86 0%, #e4544a 52%, #a5322c 100%);
}
html.touch #hud .hud-bar.ep > i::after {
  background: linear-gradient(180deg, #8fd0ff 0%, #4d94e8 52%, #2a5fae 100%);
}
html.touch #hud .hud-bar.sta > i::after {
  background: linear-gradient(180deg, #a8e88a 0%, #5fa84e 52%, #3b7030 100%);
}
/* 数字：画在条子**内部右端**（跟着各自长度走，阶梯感才在）。
 * 白字 + 双层黑投影 —— 满血时压在亮红填充上、空血时压在黑槽上，两种底色都要读得清。 */
html.touch #hud .hud-bar > em {
  position: absolute; right: 5px; top: 50%;
  transform: translateY(-50%);
  font-style: normal; font-size: 9px; font-weight: 700; line-height: 1;
  color: #fff;
  /* ⚠️ 必须清掉桌面端那条 `.hud-bar > em { background: linear-gradient(...); padding-left: 8px }`
   * —— 那是给"数字浮在横条右端之外"准备的黑色衬底。新方案里数字画在条子**内部**，
   * 留着衬底会在条子里再套一个黑方块（4x 特写里非常明显，像贴了块膏药）。 */
  background: none;
  padding: 0;
  border-radius: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .92), 0 0 4px rgba(0, 0, 0, .78);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 图标（❤ 🔮 💪）收掉：三条加起来只有 100 多像素宽，emoji 每格要吃掉 14px，
 * 而「红=血 / 蓝=蓝 / 绿=体力」本来就是通用约定，颜色已经说清楚了。 */
html.touch #hud .hud-bar > span { display: none; }
html.touch #hud .hud-bar.sh { display: none !important; }
/* 低血量：整条呼吸 */
html.touch #hud .hud-bar.hp.low { animation: hudPulse 1.1s ease-in-out infinite; }

/* ── 等级徽章：挑在资源条列的**右上角**（也是账号菜单入口）──
 * 右沿和三条资源条的右沿**任意两条**对齐（hp 最长那条）= 右边一条干净的读数轴。
 * 高度必须压到 19px 以内 —— 下面 hp 条从 22px 开始，多 1px 就叠上了。 */
html.touch .hud-lv {
  position: absolute; right: 0; left: auto; top: 0; bottom: auto;
  transform: none;
  z-index: 3;
  display: flex; align-items: center; gap: 3px;
  padding: 1px 7px 1px 6px;
  /* ⚠️ 必须清掉桌面端的 `.hud-lv.acc { margin: -1px -4px }` —— 那是为了把点击热区
   * 撑大 4px 的负边距，在手机上新徽章是**贴着条列右沿对齐**的，留着负边距
   * 会让徽章整体右飘 4px、上飘 1px（实测 rect 200/7，而条列右沿是 196）。 */
  margin: 0;
  border-radius: 9px;
  background: linear-gradient(180deg, rgba(26, 38, 50, .96), rgba(8, 14, 20, .96));
  border: 1px solid rgba(255, 208, 138, .46);
  font-size: 9.5px; line-height: 1.35; white-space: nowrap;
  color: #cfe4f2;
  max-width: none; overflow: visible;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .55);
  pointer-events: auto;        /* 账号菜单入口 */
}
html.touch .hud-lv b { font-size: 10.5px; color: #ffd08a; }
html.touch #hud-name { max-width: 46px; font-size: 9.5px; }
html.touch .hud-lv.acc .acc-arrow { font-size: 8px; }

/* ── 底部小字：只留时间/天气/金币/声音/设置 ──
 * top 用 --hw + 6：横屏（78，条列底 78）刚好贴住，竖屏（86）留 8px。 */
html.touch .hud-foot {
  position: absolute; left: 0; top: calc(var(--hw) + 6px);
  width: calc(var(--hw) + var(--bg) + var(--bw));
  margin: 0; padding: 0; border: none;
  font-size: 9px; gap: 1px 5px; line-height: 1.5;
}
html.touch #hud-pos, html.touch #hud-spd,
html.touch #hud-net, html.touch #hud-fps { display: none !important; }
/* ⚠️ r157：#hud-ver **从这个隐藏列表里拿出来了**。r156 时我以为手机不需要版本号
   （只保留"落后态例外"），用户一句话打回来：「版本号 pc 端显示了，但是手机端没显示」。
   就是该显示 —— 手机才是缓存最难清的那一端（微信/QQ 内置浏览器），玩家连"我这台跑的是
   哪个版本"都看不到，客服就没法一句话判掉。
   宽度实测：手机 .hud-foot 188px，时间 23 + 天气 9 + 金币 18 + 「r156」21 + 3 个 5px 间隙
   ≈ 86px，一行放得下；落后态长串 97px → 合计 162px 也放得下（见 probe_hud_ver_mobile.py）。
   nowrap：防落后态那句「r156 → r157 点此刷新」从中间断行，把 .hud-foot 撑成两行压到 #hud-team。 */
html.touch #hud-ver { white-space: nowrap; }

/* 窄竖屏（≤374，典型 360×800 安卓）：条列右沿会顶到右上角那个 ☰。
 *   360 宽时 ☰ 落在 214（right = 10+92 = 102，键宽 44），而 86+6+116 的条列右沿是 216
 *   → 压 2px，实际观感是两个控件贴在一起。整体降一档：78+6+104 = 188（右沿 196，留 18px 缝）。 */
@media (orientation: portrait) and (max-width: 374px) {
  html.touch #hud { --hw: 78px; --bw: 104px; --bh: 16px; }
}

/* ── 队伍信息（r138 第 5 步下半）────────────────────────────────────────
 * 用户原话：「玩家头像下方是队伍内（如果存在）其他玩家的信息：名称，血，蓝」。
 * 位置：仪表盘下方、那行小字（hud-foot，top = hw+24 高约 14）**再往下**，
 *   所以这里用 `hw + 44`（横屏 132 / 竖屏 148），两边都留 6px 缝。
 * ⚠️ 行里的血条叫 `.tm-hp`，**绝不能叫 `.hud-bar`** —— `html.touch #hud .hud-bar`
 *   会把 #hud 里每一条 hud-bar 都变成同心圆环（那条规则就是冲着整个 #hud 去的）。
 * ⚠️ 蓝条现在不画：多人的位置包（服务端 near 行）只带得动 hp 一格，
 *   画一条永远空的蓝槽和"界面坏了"没区别（第 5 步上半的怪目标刚踩过这个坑）。
 *   等 near 行加了 ep 再让它出现 —— 结构和 CSS 都留着，加一行 JS 就行。
 */
html.touch .hud-team {
  /* r138 F 方案：状态卡不再是正方形盘，改成「头像 + 条列」的横块（宽 = hw+bg+bw），
     底行小字在 --hw+6，所以队伍行从 --hw+26 起（中间 6px 缝）。 */
  position: absolute; left: 0; top: calc(var(--hw) + 26px);
  width: calc(var(--hw) + var(--bg) + var(--bw));
  margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 2px;
  pointer-events: none;
}
html.touch .hud-team.hidden { display: none; }
html.touch .tm-row {
  display: flex; align-items: center; gap: 4px;
  height: 15px;
}
html.touch .tm-n {
  flex: 0 1 auto; max-width: 54px;
  font-size: 9px; font-weight: 600; line-height: 1;
  color: #d8e9f4; text-shadow: 0 1px 3px rgba(0, 0, 0, .92);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
html.touch .tm-lead { flex: none; font-size: 8px; line-height: 1; color: #ffd08a; }
/* r138：血条和能量条**竖着叠**（名字在左边）。
   `.hud-team` 宽 = 状态盘 + 44（横屏 132 / 竖屏 148），名字最多占 54 →
   条子只剩 70~90px，横着并排两条会把名字挤没，所以两条都占满剩余宽度、上下叠。 */
html.touch .tm-bars {
  flex: 1 1 auto; min-width: 18px;
  display: flex; flex-direction: column; gap: 1px;
}
/* 血条：4px 细轨 + 红填充。没有 near 数据（队友在远处）时 .far → 压暗当"未知" */
html.touch .tm-hp {
  flex: 0 0 4px; width: 100%; height: 4px; min-width: 18px;
  border-radius: 3px; overflow: hidden;
  background: rgba(0, 0, 0, .45);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .10);
}
html.touch .tm-hp > i {
  display: block; height: 100%; width: var(--w, 0%);
  border-radius: 3px;
  background: linear-gradient(90deg, #c0403a, #e8705a);
}
html.touch .tm-hp.far { opacity: .30; }
/* 能量条（蓝）：**只在服务端真的给了 ep 时才出现在 DOM 里** ——
   旧服务端不返回这一格，main.js 就不建这个元素，所以这里不存在"空槽"状态。 */
html.touch .tm-ep {
  flex: 0 0 3px; width: 100%; height: 3px; min-width: 18px;
  border-radius: 2px; overflow: hidden;
  background: rgba(0, 0, 0, .42);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08);
}
html.touch .tm-ep > i {
  display: block; height: 100%; width: var(--w, 0%);
  border-radius: 2px;
  background: linear-gradient(90deg, #3a76c0, #5aa8e8);
}

/* ⚠️ r138：原来这里还有四条 `html.touch .hud-bar{height:11px}` / `> i{height:5px}` 之类的
   横条尺寸，现在全都变成圆环了，留着会和新规则打架（尤其是 height）。
   已全部删除 —— 环的尺寸统一在 ⑨ 那一节写。 */

html.touch .hud-buffs { display: none; }         /* buff 行在手机上太占地方（r138 环里也没位置） */
html.touch .hud-gpu, html.touch .hud-dbg { display: none !important; }

/* ══════════════ 面板关闭叉号（第 36 轮）══════════════
   为什么必须有：手机上**横屏没有键盘**，面板底部的「Esc 关闭」等于不存在
   ——用户打开背包/属性/地图后就再也关不掉，只能刷新页面（用户报「无法关闭」）。
   桌面端也挂上：更直观，不用记快捷键。 */
.p-x {
  position: absolute; z-index: 3;
  top: 8px; right: 8px;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(20, 34, 46, .92);
  color: #eaf4ff;
  font-size: 19px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  touch-action: none;
  transition: transform .1s, background .1s;
}
.p-x:active { transform: scale(.9); background: rgba(180, 70, 60, .9); }
/* 面板标题要给它让位，否则「背包 · 装备」会被叉号压住 */
.panel h2, .wm-head h2 { padding-right: 48px; }

/* ══════════════ 装备格（第 36 轮，见 #gear-grid 上方注释）══════════════ */

/* ══════════════ 通用按钮样式 ══════════════ */
.tbtn {
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .30);
  color: #eaf4ff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .42);
  transition: transform .08s, background .08s;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
  padding: 0;
  font: inherit;
}
.tbtn .tb-ico { font-size: 20px; line-height: 1; }
.tbtn .tb-txt { font-size: 9.5px; opacity: .85; line-height: 1; }
.tbtn:active, .tbtn.on { transform: scale(.90); }

/* ══════════════ 面板：居中近全屏，层级最高 ══════════════ */
html.touch .panel {
  position: fixed !important;
  left: 50% !important; right: auto !important; top: 50% !important;
  transform: translate(-50%, -50%) !important;
  width: calc(100vw - 20px) !important;
  max-width: calc(100vw - 20px) !important;
  max-height: 74vh;
  padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
  border-radius: 14px;
  z-index: 70 !important;
}
html.touch .panel h2 { font-size: 15px; margin-bottom: 8px; }
html.touch .tabs { gap: 8px; margin-bottom: 10px; }
/* 面板内的 tab 按钮加大，手指好点 */
html.touch .tabs button,
html.touch .sp-tabs button { font-size: 13px; padding: 9px 4px; }
/* 槽位格子加大 */
html.touch .slot { width: 60px; height: 60px; }
html.touch .slot .sl-n { font-size: 9px; }
/* 属性面板内容区限高（六个标签横排时内容很长） */
html.touch #sp-body { max-height: 44vh; }
/* 全屏地图 */
html.touch #map { padding: 6px; }
html.touch .wm-tip { font-size: 11px; }
/* 区域提示 */
html.touch #region { padding: 10px 0; }
html.touch .rg-name { font-size: 20px; }
html.touch .rg-sub { font-size: 11px; }
/* 键位条：手机没键盘，隐藏 */
html.touch #keys { display: none !important; }

/* ══════════════ 横屏：纵向空间紧张，整体收紧 ══════════════ */
@media (orientation: landscape) {
  /* ⚠️ 第 38 轮：横屏摇杆热区改成「左侧除小地图以外的全部」。
     之前写的是 height:56vh（只占左下），左侧上半部分既不是摇杆也不是视角区：
     玩家把手放上去毫无反应，**用「再碰一下左侧」来取消奔跑也就时灵时不灵**
     （用户报「有概率无法取消」的第三个来源）。需求原文是「左侧任意位置」。
     ⚠️ 但**不能真的铺满左侧**：左上角那个圆形小地图（点它开大地图）
     在 `#app`（z-index:2）里，而摇杆区是 `<body>` 下的 z-index:20 ——
     跨堆叠上下文比 z-index 没用，铺满就会把小地图整个盖住、点不开地图。
     所以从 y=122（小地图下沿再往下一点）开始，其余左侧全归摇杆。 */
  .tzone-stick {
    width: 32vw; max-width: 230px;
    /* r138：**回到 top:0**。原来这里写 122px，是为了不盖住左上角的圆形小地图
       （小地图在 #app(z=2) 里、摇杆区在 body 下 z=20，跨上下文比 z-index 无效，
       铺满就点不开地图）。现在小地图搬到右上，左上是圆头像 +
       只读小字，那几个还能点的入口（hud-lv 账号）
       由 touch.js 的 hudAt() 用 elementsFromPoint 认出来转交，不靠 z 序。
       ⇒ 终于兑现了第 36 轮那句「左侧任意位置按住出摇杆」。 */
    top: 0; bottom: 0;
    height: auto; max-height: none;
  }
  .tzone-look { width: 66vw; }
  .tzone-btns { width: 42vw; max-width: 240px; height: 56vh; max-height: 250px; }
  .tbtn.tb-atk { width: 72px; height: 72px; right: 14px; bottom: 18px; }
  .tbtn.tb-atk .tb-ico { font-size: 26px; }
  .tbtn.tb-jump { width: 54px; height: 54px; right: 24px; bottom: 100px; }

  /* ⚠️ 第 36 轮（用户报「右上三键竖排会挡住右下三键」）：
     横屏时左右两簇都在屏幕右侧，竖排的右上三键（高约 160px）和右下三键
     在 390px 高的横屏里会**上下叠在一起**。
     行业最优解不是「挪位置」而是**把右上改成横排**：
       · 横屏的可用宽度是高度的两倍多，横向有大量空白，纵向却极其紧张；
       · 竖排吃掉的是**纵向**空间（横屏最缺的），横排几乎不占；
       · 拇指横向扫比纵向够远更省力（原生手游的右上角功能键都是横排）。
     贴右上角横向排开，键更紧凑（44px），第一键离右边缘留 12px。 */
  .tzone-sys {
    flex-direction: row;         /* 关键：竖排 → 横排 */
    gap: 8px;
    top: calc(env(safe-area-inset-top, 0px) + 30px + var(--annoff, 0px));
    /* r138：让到小地图（72px）左侧 —— 72 + 10(右边距) + 8(间隙) = 90 */
    right: calc(max(10px, env(safe-area-inset-right)) + 80px);
    padding: 0;
    width: auto;                  /* 宽度交给内容，别再占位 */
  }
  .tzone-sys .tbtn { width: 44px; height: 44px; }
  .tzone-sys .tb-ico { font-size: 17px; }
  .tzone-sys .tb-txt { font-size: 8px; }
  /* 横屏小地图可以小一点（纵向空间宝贵） */
  html.touch #minimap { width: 72px; top: calc(env(safe-area-inset-top, 0px) + 8px + var(--annoff, 0px)); }
  html.touch #mmcv { width: 60px; height: 60px; }
  /* r138 F 方案：横屏头像缩到 78px（= 屏高的 20%），条列 104px */
  html.touch #hud {
    --hw: 78px; --bw: 104px; --bh: 16px;
    top: calc(env(safe-area-inset-top, 0px) + 8px + var(--annoff, 0px));
  }
}

/* ══════════════ 竖屏：按键稍大（拇指活动范围更纵向） ══════════════ */
@media (orientation: portrait) {
  .tzone-stick { width: 46vw; height: 40vh; }
  .tzone-btns { width: 52vw; height: 40vh; }
  /* 竖屏也横排：竖屏顶部横向空间充裕（390px 放 3 个 44px 的键绰绰有余），
     而竖排会在竖屏顶部占掉 160px 纵向——那正是小地图和状态卡要用的地方。
     横排同时解决了「竖屏右上键压住右侧视野」的问题。 */
  .tzone-sys {
    flex-direction: row;
    gap: 8px;
    /* r138：让到小地图（84px）左侧 —— 84 + 8(间隙) = 92，右边距由 max() 负责 */
    right: calc(max(10px, env(safe-area-inset-right)) + 92px);
    width: auto; padding: 0;
  }
  .tzone-sys .tbtn { width: 44px; height: 44px; }
  .tzone-sys .tb-ico { font-size: 17px; }
  .tzone-sys .tb-txt { font-size: 8px; }

  /* 移动端地图：世界地图在上、地点在下（替换桌面端的左右并排，解决竖屏被压成左侧竖长条） */
  .wm-body { flex-direction: column; gap: 10px; }
  .wm-stage { flex: 1 1 auto; min-height: 0; }
  .wm-side { flex: 0 0 40%; min-height: 0; }
}

/* ══════════════════════════════════════════════════════════════════════
   第 37 轮：按「实测可视高度」分档（真正的自适应，跟地址栏/机型都无关）
   ──────────────────────────────────────────────────────────────────────
   为什么不用 @media (orientation:landscape) 就够了：
     横屏 + Chrome 地址栏压缩之后，实测可视高度可能只有 300px，
     比很多**竖屏**设备还矮 —— orientation 这个信号根本区分不出来。
     唯一可靠的输入是「当前真正看得见的高度」，它由 touch.js 用
     visualViewport 量出来，再打在 <html> 上：t-h-hi / t-h-mid / t-h-lo。

   三档策略（行业最优解）：
     · 高   ≥430：标准尺寸，功能键带文字
     · 中 340~430：收紧一号，间距压缩
     · 低   <340：战斗弧**压成贴底一排**（弧形在矮屏上必然向上顶到功能键），
                  功能键只留图标、38px —— 横屏最缺的就是纵向，横向随便用。
   分档只负责「几何」，位置补偿（缩放/位移）仍由 JS 实测收敛，双保险。
   ══════════════════════════════════════════════════════════════════════ */

/* ⚠️ 三档的战斗键几何**全部写在这里**，不再散落在 @media 里 ——
   单一数据源，改一处就不会漏。数字都是算过的：相邻两键外接矩形至少留 6px 间隙，
   绝不出现「圆没碰上但方形热区擦边」（第 37 轮自检抓到 2px 擦边，就是这么来的）。 */

/* ---- 高：≥430（多数竖屏 / 大屏横屏）---- */
html.touch.t-h-hi .tzone-sys .tbtn { width: 46px; height: 46px; }
html.touch.t-h-hi .tzone-sys .tb-ico { font-size: 18px; }
html.touch.t-h-hi .tzone-sys .tb-txt { font-size: 8.5px; }
html.touch.t-h-hi .tbtn.tb-atk { width: 78px; height: 78px; right: 16px; bottom: 22px; }
html.touch.t-h-hi .tbtn.tb-atk .tb-ico { font-size: 28px; }
html.touch.t-h-hi .tbtn.tb-jump { width: 58px; height: 58px; right: 28px; bottom: 106px; }
/* 潜行：贴下沿、在攻击键左边，和攻击键横向留 12px 间隙 */
html.touch.t-h-hi .tbtn.tb-sneak { width: 42px; height: 42px; right: 106px; bottom: 20px; }
html.touch.t-h-hi .tbtn.tb-sneak .tb-ico { font-size: 15px; }

/* ---- 中：340~430（典型手机横屏）---- */
html.touch.t-h-mid .tzone-sys {
  top: calc(env(safe-area-inset-top, 0px) + 30px + var(--annoff, 0px));
  gap: 7px;
}
html.touch.t-h-mid .tzone-sys .tbtn { width: 42px; height: 42px; }
html.touch.t-h-mid .tzone-sys .tb-ico { font-size: 16px; }
html.touch.t-h-mid .tzone-sys .tb-txt { font-size: 7.5px; }
html.touch.t-h-mid .tbtn.tb-atk { width: 68px; height: 68px; right: 14px; bottom: 16px; }
html.touch.t-h-mid .tbtn.tb-atk .tb-ico { font-size: 25px; }
html.touch.t-h-mid .tbtn.tb-jump { width: 50px; height: 50px; right: 22px; bottom: 90px; }
/* 潜行：矮一档，贴下沿；攻击键左边缘在 right:82，这里 86 起 → 4px 间隙 */
html.touch.t-h-mid .tbtn.tb-sneak { width: 38px; height: 38px; right: 86px; bottom: 12px; }
html.touch.t-h-mid .tbtn.tb-sneak .tb-ico { font-size: 14px; }
html.touch.t-h-mid #minimap { width: 70px; }
html.touch.t-h-mid #mmcv { width: 58px; height: 58px; }

/* ---- 低：<340（横屏被 Chrome 地址栏压缩 / 小屏横屏）----
   核心变化：右下三键从「斜弧」改成「贴底一排」。
   弧形的本质是拿纵向换拇指行程，矮屏没有纵向可换 —— 压平是唯一解。 */
html.touch.t-h-lo .tzone-sys {
  top: calc(env(safe-area-inset-top, 0px) + 28px + var(--annoff, 0px));
  gap: 6px;
}
html.touch.t-h-lo .tzone-sys .tbtn { width: 38px; height: 38px; }
html.touch.t-h-lo .tzone-sys .tb-ico { font-size: 16px; }
html.touch.t-h-lo .tzone-sys .tb-txt { display: none; }   /* 只留图标，省纵向 */
html.touch.t-h-lo .tbtn.tb-atk { width: 60px; height: 60px; right: 14px; bottom: 12px; }
html.touch.t-h-lo .tbtn.tb-atk .tb-ico { font-size: 22px; }
/* r138：互动键取消后这排少了一个 —— 跳跃补到 right:80（原来 use 的位置），
   潜行跟着左移到 136（跳跃右缘 126 + 10），中间不留可见的豁口。
   矮屏这排是「贴底一字排开」，豁口特别明显，跟斜弧那几档不一样。 */
html.touch.t-h-lo .tbtn.tb-jump { width: 46px; height: 46px; right: 80px; bottom: 16px; }
html.touch.t-h-lo .tbtn.tb-sneak { width: 34px; height: 34px; right: 136px; bottom: 16px; }
html.touch.t-h-lo .tbtn.tb-sneak .tb-ico { font-size: 13px; }
html.touch.t-h-lo .tbtn.tb-sneak .tb-txt { display: none; }
html.touch.t-h-lo #minimap { width: 62px; padding: 4px; }
html.touch.t-h-lo #mmcv { width: 52px; height: 52px; }
/* ⚠️ r138 F 方案：这条原来是 `top: 96px; width: 90px` —— 那是**小地图还在左上**的时代留下的，
   目的把 88px 的圆盘往下推、让开小地图。小地图已经搬到右上，左上彻底空出来了，
   所以在 330px 高的矮横屏上再往下推 96px 等于白扔三分之一的屏幕。现在回到 top 8。
   width 也不再写死：新状态卡是「头像 + 条列」的横块，宽度由 --hw/--bw 推导，
   写死 90px 会让 .hud-lv 的 `right:0` 落到 x=90（徽章飞到屏幕上），见 ⑨ 那一节。 */
html.touch.t-h-lo #hud { top: calc(env(safe-area-inset-top, 0px) + 8px + var(--annoff, 0px)); }


/* ══════════════ 第 41 轮：丢弃区 + 中央属性增减提示 + 拖拽幽灵 ══════════════ */
#dropzone {
  margin-top: 10px; padding: 12px 10px; text-align: center; cursor: pointer;
  border: 2px dashed rgba(224, 138, 122, .34); border-radius: 12px;
  background: rgba(224, 138, 122, .06); color: #e8b6ab;
  transition: background .15s, border-color .15s, transform .12s;
}
#dropzone span { font-size: 17px; margin-right: 6px; }
#dropzone b { font-size: 12px; font-weight: 600; }
#dropzone i { display: block; font-style: normal; font-size: 10px; opacity: .68; margin-top: 3px; }
/* 有东西正被拖过来时整体"吸"一下 —— 不给任何视觉反馈的话玩家不知道松手会发生什么 */
#dropzone.hot { border-color: rgba(224, 138, 122, .7); background: rgba(224, 138, 122, .12); }
#dropzone.over { background: rgba(224, 138, 122, .26); border-style: solid; transform: scale(1.02); }

.drag-ghost {
  position: fixed; z-index: 150; pointer-events: none; opacity: 0;
  width: 46px; height: 46px; display: none; align-items: center; justify-content: center;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .6));
  transform: translate(-50%, -50%) scale(.9); transition: opacity .12s;
}
.drag-ghost.on { display: flex; opacity: .95; }
.drag-ghost img.ic { width: 38px; height: 38px; }

#deltatip {
  position: fixed; left: 50%; top: 26%; transform: translateX(-50%); z-index: 130;
  max-width: 74vw; padding: 12px 18px; border-radius: 14px; pointer-events: none;
  background: rgba(9, 16, 23, .93); border: 1px solid var(--line);
  box-shadow: 0 14px 40px rgba(0, 0, 0, .55); backdrop-filter: blur(6px);
  animation: dt-in .18s ease-out;
}
#deltatip.hidden { display: none; }
@keyframes dt-in { from { opacity: 0; transform: translateX(-50%) translateY(-8px); } }
#deltatip .dt-t { font-size: 13px; font-weight: 600; color: var(--gold); }
#deltatip .dt-t.bad { color: #e8b6ab; }
#deltatip .dt-r { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 7px; }
#deltatip .dt-r span { font-size: 11.5px; color: var(--dim); white-space: nowrap; }
#deltatip .dt-r span.up b { color: #8fe08f; }
#deltatip .dt-r span.dn b { color: #e8a08f; }
#deltatip .dt-r b { font-variant-numeric: tabular-nums; margin-left: 2px; }
/* 触屏上点空格子会触发 :active 高亮，影响"点击即穿戴"的判断 */
html.touch .inv-cell:active, html.touch .slot:active { background: rgba(255, 255, 255, .04); }

/* 第 41 轮：掉落泡泡（世界坐标投影的 DOM） */
#popups { position: fixed; inset: 0; z-index: 120; pointer-events: none; overflow: hidden; }
#popups .pop {
  position: absolute; transform: translate(-50%, 0); white-space: nowrap;
  font-size: 12px; font-weight: 600; text-shadow: 0 1px 3px #000, 0 0 8px rgba(0, 0, 0, .8);
  animation: pop-up .95s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes pop-up {
  0%   { opacity: 0; transform: translate(-50%, 6px) scale(.86); }
  18%  { opacity: 1; transform: translate(-50%, -2px) scale(1.04); }
  100% { opacity: 0; transform: translate(-50%, -22px) scale(1); }
}
html.touch #popups .pop { font-size: 13px; }
#hud-coin { color: #ffd76a; font-variant-numeric: tabular-nums; }
#itemtip .tip-drag { margin-top: 7px; font-size: 10.5px; color: #8a9aa6; opacity: .8; }
/* 第 44 轮：装备详情卡里的操作提示（取代原来的「穿戴 / 卸下」按钮）。
   ⚠️ 必须比说明文字亮一点、并带一条左侧竖线 —— 它是一条"可执行的指令"，
   不是补充信息，混在灰色小字里玩家根本不会照着做。 */
#itemtip .tip-hint {
  margin-top: 9px; font-size: 11.5px; color: #ffd98a; letter-spacing: .3px;
  padding: 5px 8px; border-radius: 6px;
  background: rgba(255, 208, 138, .10); border-left: 2px solid rgba(255, 208, 138, .55);
}

/* ══════════════ 第 56 轮：手机端详情卡 = 贴底抽屉 ══════════════
 * 为什么不用"钉在格子下方"那套：手机屏就这么高，格子常常已经在下半屏，
 * 卡片再往下弹会被视口夹到最底 —— 既盖住刚点的格子（没法对照），
 * 按钮也顶到屏幕边缘，拇指够不着。
 * 所以钉住态（.sheet）改成贴底：宽度撑满、内容超长可滚、按钮在最下面。
 * ⚠️ 只给手机端加这个类（placeTip 里 `pinned && !HOVERABLE` 时才 add），
 *    PC 的点选卡仍是"钉在格子下方"，两边互不干扰。 */
#itemtip.sheet {
  left: 8px; right: 8px;
  width: auto; max-width: none;
  max-height: calc(100% - 20px); overflow-y: auto;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding-bottom: 14px; border-radius: 14px;
  border-color: rgba(255, 208, 138, .45);
}
/* 手机上按钮是**唯一的操作入口**，必须按 44px 触摸目标来，不能沿用 PC 的小按钮 */
#itemtip.sheet .tip-c { margin-top: 12px; gap: 8px; }
#itemtip.sheet .tip-c button {
  flex: 1 1 0; min-height: 44px; font-size: 13.5px; letter-spacing: .3px;
}

/* ⚠️⚠️ 第 56 轮：手机上长按格子会弹出**浏览器自己的**菜单（复制图片 / 下载图片），
 *   把游戏的长按拖拽整个顶掉。iOS Safari 必须这条 + main.js 里的
 *   contextmenu preventDefault **同时**到位才压得住，缺一个都不生效。 */
.inv-cell, .slot, .cx-row, #itemtip {
  -webkit-touch-callout: none;
  -webkit-user-select: none; user-select: none;
}

/* ══════════════ 第 42 轮：濒死血丝 ══════════════
 * ⚠️ 必须用**径向渐变**：用户要「屏幕中间完全透明、向四周逐渐增长」。
 *   如果用普通半透明红色层（position:fixed 铺满 + rgba），整屏都会被糊红，
 *   玩家反而看不清自己在哪、往哪跑 —— 那不是濒死，那是致盲。 */
#dyingfx {
  position: fixed; inset: 0; z-index: 90; pointer-events: none;
  opacity: 0; transition: opacity .12s linear;
  --dk: 0;
  background:
    radial-gradient(ellipse 78% 74% at 50% 50%,
      rgba(150, 8, 12, 0) 42%,
      rgba(158, 10, 14, calc(var(--dk) * 0.42)) 68%,
      rgba(126, 6, 10, calc(var(--dk) * 0.80)) 88%,
      rgba(92, 2, 6, calc(var(--dk) * 0.97)) 100%);
}
/* 血量越低，边缘的血丝越"糊"（多一层柔和扩散），像视野在收窄 */
#dyingfx::after {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(ellipse 58% 54% at 50% 50%,
    rgba(0,0,0,0) 55%, rgba(120, 4, 8, calc(var(--dk) * 0.55)) 100%);
  mix-blend-mode: multiply;
}
html.touch #dyingfx { background:
  radial-gradient(ellipse 86% 80% at 50% 50%,
    rgba(150, 8, 12, 0) 38%,
    rgba(158, 10, 14, calc(var(--dk) * 0.46)) 66%,
    rgba(120, 5, 9, calc(var(--dk) * 0.84)) 100%); }

/* ══════════════ 奖励中心（第 50 轮）══════════════
   架构说明：奖励不进存档，进服务端独立的 player_mail 表，玩家自己领。
   所以这里是"收件箱"，不是"背包" —— 领取动作在服务端完成，幂等。 */
.mail-bar {
  display: flex; align-items: center; gap: 10px;
  font-size: 12px; color: #7d94a4;
  padding: 2px 2px 10px; border-bottom: 1px solid rgba(255, 255, 255, .08);
  margin-bottom: 10px;
}
.mail-bar span { flex: 1; }
.mail-all {
  flex: none; padding: 6px 14px; border-radius: 8px; cursor: pointer;
  border: 1px solid rgba(120, 200, 255, .35);
  background: rgba(38, 78, 108, .9); color: #dff0ff; font-size: 12px;
}
.mail-all:active { transform: scale(.96); }

#mail-list { display: flex; flex-direction: column; gap: 8px; }
.mi {
  border: 1px solid rgba(255, 255, 255, .1);
  background: rgba(255, 255, 255, .04);
  border-radius: 10px; padding: 10px 12px;
}
.mi.new { border-color: rgba(255, 196, 90, .45); background: rgba(255, 196, 90, .07); }
.mi.done, .mi.dead { opacity: .55; }
.mi-h { display: flex; align-items: baseline; gap: 8px; }
.mi-h b { font-size: 14px; color: #eaf4ff; }
.mi-time { margin-left: auto; font-size: 11px; color: #6d8290; }
.mi.new .mi-h b::before { content: '● '; color: #ffc45a; }
.mi-body { font-size: 12px; color: #9db3c2; margin-top: 4px; line-height: 1.5; }
.mi-att-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.mi-att {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border-radius: 7px; font-size: 12px;
  background: rgba(255, 255, 255, .07); border: 1px solid rgba(255, 255, 255, .12);
  color: #dfeaf3;
}
.mi-att i { font-style: normal; }
.mi-att b { color: #9fd0ff; font-weight: 600; }
.mi-att.r4 { border-color: rgba(200, 130, 255, .5); }
.mi-att.r5 { border-color: rgba(255, 170, 70, .55); }
.mi-att.coin b { color: #ffd76a; }
.mi-att.exp b { color: #9ff0c0; }
.mi-att.none { color: #6d8290; background: none; border-style: dashed; }
.mi-f { display: flex; align-items: center; gap: 8px; margin-top: 9px; }
.mi-exp { font-size: 11px; color: #6d8290; flex: 1; }
.mi-claim {
  flex: none; padding: 6px 18px; border-radius: 8px; cursor: pointer;
  border: 1px solid rgba(120, 220, 160, .4);
  background: rgba(40, 96, 72, .92); color: #dfffe9; font-size: 13px; font-weight: 600;
}
.mi-claim:active { transform: scale(.96); }
.mi-st { font-size: 12px; color: #7d94a4; padding: 6px 4px; }
.mi-st.done { color: #8fd8ac; }
.mi-st.dead { color: #c98a8a; }
.mi-empty { text-align: center; color: #6d8290; font-size: 13px; padding: 30px 10px; line-height: 1.8; }
.mi-empty i { font-style: normal; font-size: 11px; opacity: .8; }

/* 背包里的奖励中心入口（移动端的路：右上角三键在手机上不显示） */
.mail-open {
  display: block; width: 100%; margin-top: 10px;
  padding: 10px; border-radius: 9px; cursor: pointer;
  border: 1px solid rgba(255, 196, 90, .35);
  background: rgba(66, 52, 28, .85); color: #ffe1a8; font-size: 13px;
}
.mail-open b {
  display: inline-block; margin-left: 6px; min-width: 18px; padding: 1px 6px;
  border-radius: 9px; background: #ff5a5a; color: #fff; font-size: 11px;
}
.mail-open b.hidden { display: none; }
.mail-open:active { transform: scale(.99); }

/* PC 右上角信封键的红点 */
#pcb-mail.has-new::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 9px; height: 9px; border-radius: 50%;
  background: #ff5a5a; box-shadow: 0 0 0 2px rgba(10, 18, 26, .8);
}

/* ══════════════ ⑩ 功能键抽屉（r138 · 参考《混沌与秩序2》）══════════════
 * 需求原文：「右上角的背包、奖励、属性、视角、表情，也收成一个抽屉按钮，
 *   这个抽屉按钮在小地图左侧，点击后向左展开这些」。
 *
 * 结构（见 touch.js 的 mountTouchControls）：
 *   .tzone-sys            常驻、只装一个 ☰ 开关，**进布局流**，autoFitTouchLayout 量它
 *     └ .tzone-drawer     五个功能键，position:absolute 飘出去，**不进父盒尺寸**
 *
 * ⚠️⚠️ 抽屉必须 absolute 飘出去，不能让它撑宽 .tzone-sys ——
 *   autoFitTouchLayout 是拿 .tzone-sys 的矩形去和右下战斗簇做防撞的（③ 防撞收敛），
 *   一旦展开会把父盒撑到 260px 宽 → 判定和战斗簇横向重叠 → 把**两簇一起缩小**。
 *   表现就是：玩家点开抽屉，右下攻击键莫名其妙缩水，关掉也不恢复原位。
 */
html.touch .tzone-drawer {
  position: absolute;
  right: 100%;                 /* 贴到开关左侧（横屏 = 向左展开，和 O&C2 一致） */
  top: 0;
  margin-right: 8px;
  display: none;
  gap: 8px;
  pointer-events: none;        /* 容器本身不吃触摸，只有里面的键吃 */
}
html.touch .tzone-sys.open .tzone-drawer { display: flex; }
html.touch .tzone-drawer .tbtn { pointer-events: auto; position: relative; }

/* 抽屉开关：比同簇的键显眼一档（金色边 + 更实的底），因为它是唯一的常驻入口 */
html.touch .tbtn.tb-drawer {
  border-color: rgba(255, 208, 138, .52);
  background: linear-gradient(180deg, rgba(58, 48, 26, .90), rgba(30, 24, 12, .94));
}
html.touch .tzone-sys.open .tbtn.tb-drawer {
  border-color: rgba(255, 208, 138, .88);
  background: linear-gradient(180deg, rgba(98, 78, 38, .95), rgba(52, 40, 18, .95));
}

/* 竖屏：横向空间紧（390px 放不下 44+8+5×44 那一条，会压到左上小地图），
   改成**从开关往下垂一列**。竖屏顶部往下的空白比横向的空白多得多。 */
@media (orientation: portrait) {
  html.touch .tzone-drawer {
    right: 0; top: 100%;
    margin: 8px 0 0;
    flex-direction: column;
  }
}

/* ══════════════ ⑪ 右侧快捷槽抽屉（r138 第 4 步 → 第 7 步改 2 列 × 8 行）══════════════
 * 需求原文：「右侧快捷槽抽屉，应该是两列八行从屏幕右侧边快速平滑拉出，
 *           同时右侧的东西整体向左移动（中间的聊天框、中间的提示框和左侧部分不动）」。
 *           「可以放任意消耗品或者技能，给出自由度」。
 * 结构见 touch.js 的 makeSlotDrawer：
 *   .tzone-slots   常驻、只装一个 ▦ 拉手（贴右边缘的舌头）
 *     ├ .slot-tab  唯一常驻入口
 *     └ .slot-panel 16 格（2 列 × 8 行），fixed 往左拉出
 *
 * 拉手为什么 `top: 50% - 10px`（r138 第 7 步改，之前是 safe-top + 100px）：
 *   面板要 8 行高（横屏 303px），只能**垂直居中**才装得下（390 高的横屏：43~346）；
 *   面板又挂在拉手中心上，所以拉手也得跟着居中。−10px 是为了和战斗键簇
 *   （42vh 高，横屏上沿在 y=226）留 10px 缝，不然两个圆角会贴在一起。
 *
 * ⚠️⚠️ 面板必须 absolute/fixed 飘出去，**绝不能撑大父盒**：
 *   autoFitTouchLayout 的③防撞收敛会把撑大的簇和别的键一起缩小。
 * ⚠️⚠️ 开合走 transform/opacity，不再 `display:none` —— 要"平滑"就必须有个起始态。
 *   代价：无头 + swiftshader 环境里 transition 会冻在 t=0（见 MEMORY），
 *   所以探针测开合**不能用 computed 值**，要么等，要么临时把 transition 关掉再量。
 */
html.touch .tzone-slots {
  z-index: 22;
  right: 0; left: auto;
  /* 拉手中心 = 视口垂直中线 − 10px。
   * ⚠️⚠️ 这里**绝不能用 transform: translateY(-50%)** 来做居中：
   *   祖先一旦有 transform，就成了 `position:fixed` 子元素的**包含块**，
   *   面板的 `top:50%` 会变成"相对这个 62px 高的拉手盒"，于是 8 行面板
   *   既不在屏幕中间（实测 top=26 / 底部留 46），JS 按面板矩形算出来的
   *   挑选菜单坐标也会整体右移 750px 飞出屏幕（实测 left=1282 @780 宽）。
   *   代价只是 `top` 要手算：50% − 10 − 31(半高) = 50% − 41。
   */
  top: calc(50% - 41px);
  bottom: auto;
  width: 30px; height: 62px;
  pointer-events: none;        /* 只有拉手和格子吃触摸 */
}
html.touch .slot-tab {
  width: 30px; height: 62px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; margin: 0;
  border: 1px solid rgba(255, 255, 255, .20);
  border-right: none;                      /* 贴边，右边不要边框 */
  border-radius: 12px 0 0 12px;
  background: linear-gradient(90deg, rgba(20, 34, 46, .90), rgba(10, 18, 26, .94));
  color: #cfe4f2; font-size: 15px; line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  box-shadow: -3px 0 10px rgba(0, 0, 0, .35);
}
html.touch .slot-tab-ico { display: block; font-size: 15px; }
html.touch .tzone-slots.open .slot-tab {
  color: #ffd08a;
  border-color: rgba(255, 208, 138, .62);
  background: linear-gradient(90deg, rgba(58, 48, 26, .92), rgba(30, 24, 12, .95));
}
/* ── 16 格面板：2 列 × 8 行，从右边缘平滑拉出 ──
 * 用 fixed 而不是 absolute：
 *   absolute 的 top 是相对拉手（62 高）算的，8 行 303px 会往上溢出到屏幕外；
 *   改成 fixed + top:50% 才是**相对视口**垂直居中，横屏 390 高刚好装得下。
 * 开合三件套：opacity（淡入）+ scale(.94→1) 并向右收（transform-origin 在右中），
 *   合起来就是"从右边那道缝里抽出来"的手感。
 */
html.touch .slot-panel {
  position: fixed;
  right: 36px;                             /* 0 + 拉手 30 + 缝 6 */
  top: 50%;
  transform: translateY(-50%) scale(.94);
  transform-origin: 100% 50%;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, var(--qsw, 34px));
  /* 8 行写死 + column 流向：DOM 顺序 = 先左列 8 格、再右列 8 格，
     于是「左列消耗品 / 右列技能」这种排法在数据里是天然顺序，不用跳着填。 */
  grid-template-rows: repeat(8, var(--qsw, 34px));
  grid-auto-flow: column;
  gap: var(--qsg, 3px);
  padding: 5px;
  border-radius: 14px;
  background: rgba(8, 16, 24, .92);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .52);
  opacity: 0;
  visibility: hidden;                      /* 关着的时候不吃触摸，也不进无障碍树 */
  pointer-events: none;                    /* 容器不吃触摸，只有格子吃 */
  transition: transform .22s cubic-bezier(.22, .9, .24, 1), opacity .16s ease-out;
}
html.touch .tzone-slots.open .slot-panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) scale(1);
}
/* ⚠️ 右侧四样（战斗键簇 / 技能转轮 / 小地图 / ☰）整体左移一个抽屉宽度。
 *   位移量由 touch.js 实测算出来写进 --slotdx（拉手 30 + 缝 6 + 面板宽 + 8 余量），
 *   不写死 —— 横屏格子 34、竖屏 40，面板宽度不一样。
 *   ⚠️ 不动的：左侧状态盘/队伍、中间聊天与提示、目标条 —— 用户明确要求。 */
html.touch .tzone-btns {
  transform: translate3d(calc(var(--slotdx, 0px) * -1), var(--cbtdy, 0px), 0)
             scale(var(--tbs, 1));
}
html.touch .tzone-sys {
  transform: translate3d(calc(var(--sysdx, 0px) - var(--slotdx, 0px)), var(--sysdy, 0px), 0)
             scale(var(--tss, 1));
}
html.touch #minimap {
  transform: translate3d(calc(var(--slotdx, 0px) * -1), 0, 0);
}
/* 只在**第一次开抽屉之后**才给 transform 加过渡：
 * 加载期 autoFitTouchLayout 会连改好几次 --tbs/--cbtdy，带过渡会看到键簇自己在抖。 */
html.slot-anim .tzone-btns,
html.slot-anim .tzone-sys,
html.slot-anim #minimap {
  transition: transform .24s cubic-bezier(.22, .9, .24, 1);
}
/* ⚠️ r138 F 方案：这里原来有一条 `html.slot-open #target { opacity: .34 }`，
 * 原因是旧布局里竖屏目标条在 x122..234、右上 ☰ 左移后会压上去。
 * 现在竖屏目标条挂在小地图正下方（x256..380，见 #target 的 portrait 段），
 * ☰ 左移后落在 106..150 一带，两边彻底不碰了 —— 压暗反而会让玩家以为目标条坏了，故删除。 */
html.touch .qslot {
  position: relative;
  width: 100%; height: 100%; padding: 0; margin: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px;
  border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, .18);
  background: linear-gradient(180deg, rgba(34, 52, 68, .92), rgba(18, 28, 38, .96));
  color: #eaf4ff;
  cursor: pointer;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  transition: transform .08s;
}
html.touch .qslot:active { transform: scale(.92); }
html.touch .qslot .qs-ico { font-size: 17px; line-height: 1; }
html.touch .qslot .qs-n {
  font-size: 8.5px; line-height: 1;
  color: #ffd08a;
  font-variant-numeric: tabular-nums;      /* 数量变两位数时数字不抖 */
}
html.touch .qslot.empty { opacity: .40; }
html.touch .qslot.empty .qs-n { color: #ff8a7a; }
/* 技能格：虚线 + 半透明 + 🔒 —— 一眼看出「这里有格子，但还没东西」 */
html.touch .qslot.lock {
  opacity: .42;
  border-style: dashed;
  border-color: rgba(159, 182, 200, .42);
  background: rgba(16, 26, 36, .72);
}
html.touch .qslot.lock .qs-txt { font-size: 7px; line-height: 1; color: #9fb6c8; }
/* 空格（还没配东西）：只有一圈极淡的点线 + 一个「+」，和「有物品但用光了」区分开
 * —— 后者是 .empty（半透明 + 红色 0），两者长得一样会让玩家以为东西丢了。 */
html.touch .qslot.void {
  border-style: dotted;
  border-color: rgba(159, 182, 200, .22);
  background: rgba(12, 20, 28, .55);
  color: #5d7488;
}
html.touch .qslot.void .qs-ico { font-size: 14px; opacity: .55; }

/* ── 挑选菜单（r138 第 7 步）：给某一格挑消耗品或技能 ──
 * 定位由 JS 按面板实测左边缘算（`left = 面板.left - 菜单宽 - 8`），
 * 因为面板宽度横/竖屏不一样，写死 right 会在某一档飘到屏幕外。
 * ⚠️ 这个菜单**也挂在 .tzone-slots 里**，所以 setTouchIdle 收面板时它一起收掉。 */
html.touch .slot-pick {
  position: fixed;
  z-index: 24;
  width: 132px;
  max-height: 62vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 5px;
  display: flex; flex-direction: column; gap: 3px;
  border-radius: 12px;
  background: rgba(6, 13, 20, .96);
  border: 1px solid rgba(255, 208, 138, .34);
  box-shadow: 0 8px 26px rgba(0, 0, 0, .6);
  pointer-events: auto;
  touch-action: pan-y;
}
html.touch .slot-pick.hidden { display: none; }
html.touch .slot-pick .sp-hd {
  font-size: 9px; line-height: 1.2; color: #8ea8bc;
  padding: 1px 2px 3px;
}
html.touch .slot-pick .sp-it {
  display: flex; align-items: center; gap: 6px;
  min-height: 30px; padding: 3px 6px;
  border-radius: 7px; border: 1px solid rgba(255, 255, 255, .12);
  background: rgba(24, 38, 52, .82);
  color: #eaf4ff; font-size: 11px; line-height: 1;
  text-align: left;
  pointer-events: auto;
}
html.touch .slot-pick .sp-it .sp-ico { font-size: 14px; flex: none; }
html.touch .slot-pick .sp-it .sp-nm {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.touch .slot-pick .sp-it .sp-n {
  flex: none; font-size: 9px; color: #ffd08a;
  font-variant-numeric: tabular-nums;
}
html.touch .slot-pick .sp-it.on { border-color: rgba(255, 208, 138, .68); }
html.touch .slot-pick .sp-it.clr { color: #ff8a7a; justify-content: center; }
/* 竖屏：格子放到 40px（8×40 + 7×4 + 12 = 348，844 高的屏上 y 248~596，居中很好按）。
 * 横屏只能用 34（8×34 + 7×3 + 10 = 303，390 高的屏上 y 43~346）。
 * ⚠️ 面板宽高都由这两个变量推出来，--slotdx 也在 JS 里按实测 offsetWidth 算，
 *   所以这里只改格子尺寸，不用再手算面板宽度。
 */
@media (orientation: portrait) {
  html.touch .slot-panel { --qsw: 40px; --qsg: 4px; }
  html.touch .qslot .qs-ico { font-size: 19px; }
  html.touch .qslot.lock .qs-txt { font-size: 7.5px; }
}
/* 极矮横屏（部分安卓横屏、分屏）：再缩一档，保证 8 行不溢出 */
@media (orientation: landscape) and (max-height: 340px) {
  html.touch .slot-panel { --qsw: 29px; --qsg: 2px; }
  html.touch .qslot .qs-ico { font-size: 14px; }
}

/* ══════════════════════════════════════════════════════════════════════
   技能转轮（r138 第 6 步 · 参考《混沌与秩序2》）
   ──────────────────────────────────────────────────────────────────────
   需求原文：「跳跃键周围三个键是一个技能转轮，以跳跃键大圆圆心为中心可以转动，
             同时也可以放自定义的技能」。

   结构：
     .tzone-wheel   一个 **0×0 的定位锚点**，JS 把它的 left/top 设成上下文主键的圆心，
                    三个键都相对这个原点用 translate3d 摆放
       ├ .sw-arc    极淡的虚线圆环（纯装饰，标出"转轮"的范围）
       └ .sw-btn×3  技能键，角度由 JS 算

   ⚠️⚠️ 为什么用 0×0 锚点 + JS 算坐标，而不是写死在 CSS 里：
     战斗键簇有三套几何（t-h-hi / mid / lo，其中 lo 档直接压成贴底一排），
     再加横竖屏两套 —— 共 6 种组合。转轮要"围着跳跃键"就必须知道跳跃键**当前**
     的圆心，而它还会被 autoFitTouchLayout 的 --tbs 缩放和 --cbtdy 位移影响。
     唯一可靠的做法就是**量 getBoundingClientRect**（已含 transform），
     CSS 里一个坐标都不写。改任何按键几何，转轮自动跟着走。
   ⚠️⚠️ 转轮必须是**独立 zone**（.tzone-wheel），不能塞进 .tzone-btns：
     autoFitTouchLayout 的防撞收敛是量 .tzone-btns 里所有 .tbtn 的外接矩形，
     转轮一旦塞进去，它那三个飘在外面的键会把包围盒撑大 → 右下整簇被迫缩小。
   ⚠️ z-index 23：压过战斗簇(22)、低于快捷槽(24)。转轮不许盖住右侧 ▦ 拉手。
   ══════════════════════════════════════════════════════════════════════ */
html.touch .tzone-wheel {
  position: fixed; z-index: 23;
  left: 0; top: 0; width: 0; height: 0;   /* 纯锚点，本身不占任何面积 */
  pointer-events: none;                   /* 只有三个键吃触摸 */
}
/* 装饰环：让玩家看出"这三个键是一个轮子"。用 dashed 边 + 极低透明度，
   绝不能比技能键本身更显眼（否则像是多了一个大按钮）。 */
html.touch .sw-arc {
  position: absolute; left: 0; top: 0;
  width: var(--swd, 120px); height: var(--swd, 120px);
  margin-left: calc(var(--swd, 120px) / -2);
  margin-top: calc(var(--swd, 120px) / -2);
  border: 1px dashed rgba(159, 182, 200, .18);
  border-radius: 50%;
  pointer-events: none;
}
html.touch .sw-btn {
  position: absolute; left: 0; top: 0;
  width: var(--swb, 40px); height: var(--swb, 40px);
  margin-left: calc(var(--swb, 40px) / -2);
  margin-top: calc(var(--swb, 40px) / -2);
  padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(150, 196, 240, .46);
  background: linear-gradient(180deg, rgba(30, 46, 68, .86), rgba(14, 24, 38, .92));
  box-shadow: 0 2px 8px rgba(0, 0, 0, .40);
  color: #dceaf6;
  font-size: calc(var(--swb, 40px) * .46);
  line-height: 1;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  transition: none;               /* ⚠️ 不要过渡：无头环境 CSS transition 会冻在 t=0 */
}
html.touch .sw-btn.on {
  border-color: rgba(255, 208, 138, .82);
  background: linear-gradient(180deg, rgba(74, 58, 30, .92), rgba(40, 30, 14, .94));
}
/* 未解锁：虚线边 + 半透明。**不是 disabled** —— disabled 的按钮在手机上
   连 pointerdown 都收不到，玩家点了毫无反馈会以为界面卡了。 */
html.touch .sw-btn.lock {
  opacity: .52;
  border-style: dashed;
  border-color: rgba(159, 182, 200, .40);
}
/* 转轮整体被拖动时：三个键都轻微提亮，给"正在转"的反馈 */
html.touch .tzone-wheel.dragging .sw-btn { opacity: .78; }
/* 藏起来统一用全局 .hidden（style.css:11 有 display:none !important），此处不再重复。 */

/* ── r192：技能冷却遮罩（design/gdd/skills-mvp.md §3.2 第 3 条）────────────────
   变量 --cd 由 skillbar.js 每 0.1s 写一次（1 → 0），这里只负责画。

   ⚠️ 为什么用 conic-gradient 而不是再叠一个 canvas / 圆环 mesh：
      纯 CSS、零 draw call、零 GC。技能施放时的峰值 draw call 增量已经花在
      地面裂纹环和粒子上了（AC-9 要求 ≤3），冷却这种"每秒都在变"的东西绝不能上 GPU。
   ⚠️ `::after` 是**最后一个子节点**，会盖住 .sw-ico 和 .sw-key ——
      所以角标与秒数要显式抬 z-index（见下面的 2）。
   ⚠️ **不加 transition / animation**：无头（SwiftShader）环境里 transition 会冻在 t=0，
      探针会永远读到"没在冷却"。最后 1 秒用静态变色代替闪烁。 */
html.touch .sw-btn::after {
  content: '';
  position: absolute; left: 0; top: 0; right: 0; bottom: 0;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
  /* --cd = 剩余比例：1 = 满圈遮住，0 = 全透明（conic-gradient 的 0deg 段为空） */
  background: conic-gradient(rgba(5, 9, 14, .74) calc(var(--cd, 0) * 360deg), rgba(0, 0, 0, 0) 0);
}
/* 冷却中：图标压暗 45%、边框退成灰蓝（"暂时不可用"一眼可辨） */
html.touch .sw-btn.cooling { border-color: rgba(120, 140, 160, .30); }
html.touch .sw-btn.cooling .sw-ico { opacity: .55; }
/* 剩余秒数：压在键的**正中央**（遮罩已经把图标压暗了，数字要最醒目） */
html.touch .sw-cd {
  position: absolute; left: 0; top: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center;
  z-index: 2;
  font-size: calc(var(--swb, 40px) * .42);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #ffe3b4;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
  pointer-events: none;
}
/* 最后一秒（显示 0.5）：改成暖橙 + 加粗放大，代替"闪烁"（原因见上面的 ② ） */
html.touch .sw-cd.soon {
  color: #ffb04a;
  font-size: calc(var(--swb, 40px) * .46);
  letter-spacing: -.5px;
}
/* 角标要压在遮罩之上，否则冷却中看不到"按哪个键" */
html.touch .sw-key { z-index: 2; }

/* ══════════════════════════════════════════════════════════════════════════
   r142：桌面端「照搬移动端横屏」
   ──────────────────────────────────────────────────────────────────────────
   用户原话：「我们刚才只改了手机端，电脑端还是老样子，现在请把电脑端也给改成
   跟移动端横屏一样的体验；那些原本手按的技能键快捷键也可以保留，鼠标点击也要
   有交互，但更重要的是这些按键上都显示一个映射到键盘上的快捷键字母」。

   做法：mountTouchControls({ pcLike: true }) 在桌面上也挂这套 UI，并额外打上
   `html.pcdesk`。⚠️ 它同时带 `html.touch`（复用那一整套移动端样式，省得维护
   两份），**pcdesk 只用来做桌面特例覆盖**：
     · 不建摇杆区 / 视角区（WASD + 鼠标拖视角本来就有，再铺一层会算两遍）
     · 键上显示键盘字母（手机没键盘，角标是噪音）
     · 几处按桌面尺寸放大（状态卡 / 聊天框宽度）
   ══════════════════════════════════════════════════════════════════════════ */

/* ① 键位角标：右下角一枚小字母，**常显** —— PC 玩家要的就是"不用 hover 就知道按哪个"。
   ⚠️ 父元素必须已经定位：.tzone-btns 的 .tbtn 是 absolute（弧形排布）、
      .qslot 是 relative、.sw-btn 是 absolute，都是定位元素；
      只有右上抽屉的开关（.tzone-sys 直接子级）是静态的，下面单独补 relative。 */
.tb-key, .qs-key, .sw-key {
  position: absolute; right: 1px; bottom: 0px;
  font-style: normal; font-size: 9.5px; line-height: 1;
  padding: 1px 3px; border-radius: 4px;
  background: rgba(4, 10, 16, .82);
  border: 1px solid rgba(255, 208, 138, .40);
  color: #ffd08a;
  pointer-events: none;                 /* 角标绝不能吃掉按钮的点击 */
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
html.pcdesk .tzone-sys .tbtn { position: relative; }   /* 抽屉开关：给角标一个定位父级 */
/* 快捷槽格子小（34px），角标再小一号、贴到左上角，别压住数量角标 .qs-n */
html.pcdesk .qs-key { right: auto; left: 1px; font-size: 8px; padding: 0 2px; }
/* 转轮键是圆的，角标压在右下弧外会出界 —— 往里收一点 */
html.pcdesk .sw-key { right: -1px; bottom: 1px; font-size: 8.5px; }

/* ② 尺寸：移动端那套是按 6~7 寸屏定的，1440×900 上偏小 —— 桌面整体放大一档。
   ⚠️⚠️ r144：这一整段以前**大部分是死规则** —— `autoFitTouchLayout` 在桌面上也会加
      `t-h-hi`（按可视高度分档），而 `html.touch.t-h-hi .tzone-sys .tbtn`（4 个类）
      比这里的 `html.pcdesk .tzone-sys .tbtn`（3 个类）特异度更高 → 桌面上显示的
      一直是手机的 46px / 8.5px 字号。现在 touch.js 那边对 pcdesk **不再分档**
      （见 autoFitTouchLayout 的 ① 段），这批规则才真正生效。 */
html.pcdesk #hud {
  --hw: 108px;    /* 头像直径（移动横屏 78） */
  --bw: 168px;    /* 最长那条资源条（移动横屏 104） */
  --bh: 24px;     /* 每条资源条行高（移动横屏 16） */
  --bgap: 6px;
  --bg: 8px;
  font-size: 13px;
}
html.pcdesk .tzone-sys .tbtn { width: 56px; height: 56px; }
html.pcdesk .tzone-sys .tb-ico { font-size: 24px; }
html.pcdesk .tzone-sys .tb-txt { font-size: 10.5px; }
html.pcdesk .tzone-btns { max-width: 340px; max-height: 320px; }
html.pcdesk .tbtn.tb-atk { width: 100px; height: 100px; }
html.pcdesk .tbtn.tb-atk .tb-ico { font-size: 34px; }
html.pcdesk .tbtn.tb-jump { width: 76px; height: 76px; }
html.pcdesk .tbtn.tb-jump .tb-ico { font-size: 26px; }
html.pcdesk .tbtn.tb-sneak { width: 48px; height: 48px; right: 118px; bottom: 26px; }
html.pcdesk .tbtn.tb-sneak .tb-ico { font-size: 18px; }

/* ②-1 r144：**上端组件离屏幕上沿远一点**（用户：「上端组件离屏幕上顶边边缘距离太近」）。
   移动端贴 8px 是给 6 寸屏省纵向空间；PC 上 8px 会顶住浏览器工具栏，视觉上像"卡住了"。
   统一改成 16px，右下角/右边界也各退一点。
   ⚠️ 每条都带 var(--annoff)：有公告条时整体再让 34px（见 :root 的说明）。 */
html.pcdesk #minimap {
  width: 104px; padding: 6px;
  right: 16px;
  top: calc(env(safe-area-inset-top, 0px) + 16px + var(--annoff, 0px));
}
/* 小地图放大靠的是这里（display 尺寸），画布属性还是 176 —— 缩小显示更清晰，
   不用改 JS，也不会让 drawMinimap 的 toPx 换算跑偏。 */
html.pcdesk #mmcv { width: 90px; height: 90px; }
html.pcdesk .mm-cap { font-size: 11px; }
html.pcdesk #hud {
  top: calc(env(safe-area-inset-top, 0px) + 16px + var(--annoff, 0px));
}
/* 目标条跟在状态卡右边：16(left) + 108(--hw) + 8(--bg) + 168(--bw) + 12 = 312 */
html.pcdesk #target {
  left: 312px;
  top: calc(env(safe-area-inset-top, 0px) + 16px + var(--annoff, 0px));
}
/* 在线徽标 / 账号条：让开 104px 的小地图（16 + 104 = 120）再留 10 */
html.pcdesk #accbar { top: calc(130px + var(--annoff, 0px)); }
/* 右上功能键簇：小地图右沿 16、宽 104 → 簇的右边界退到 130（原来按 72px 小地图算的 90） */
html.pcdesk .tzone-sys {
  right: 130px;
  top: calc(env(safe-area-inset-top, 0px) + 40px + var(--annoff, 0px));
  gap: 10px;
}

/* ②-2 r144：底部按键提示条与右侧快捷槽 —— 桌面端放大一档，字才看得清。
   ⚠️⚠️ 提示条**必须抬到聊天框上方**：两者都在屏幕底部居中（#keys bottom:12，
      聊天框 bottom:14 宽 min(460px,38vw)），聊天层挂在 body 上、z 比 #app(65) 高，
      所以重叠的部分整条被吃掉 —— 之前只露出两侧的字，中间那截看不见（用户说的
      "各组件间距很紧凑"有一半是这个）。14 + 58(收起态三行) + 6 = 78。 */
html.pcdesk #keys {
  /* ⚠️⚠️ r144 真 bug：`html.touch #keys{display:none!important}` 是给手机定的
     （手机没键盘），但 r142 起桌面也挂 `touch` 类 → **PC 端提示条整条消失**，
     上面这条放大规则跟着一起变成死规则。桌面有键盘，必须显式抬回来。 */
  display: flex !important;
  font-size: 13px; gap: 8px 12px; padding: 8px 16px;
  bottom: 78px;
}
/* ⚠️⚠️ r156 真 bug（用户报「现在游戏好像不显示版本编号了」）：和上面 #keys 一模一样的坑。
   `html.touch ... #hud-ver { display: none !important }` 是给手机省空间的，但 r142 起桌面
   也挂 `touch` 类 → **PC 端版本号整条消失**，连带 main.js 里往同一个 span 写的
   「r155 → r156 点此刷新」也一起看不见（用户说的"更新提示也没了"就是它）。
   ⚠️ r157 起**那条隐藏早就不含 #hud-ver 了**（用户要求手机也显示版本号，见 2170 行），
   所以这里的 `display:inline !important` 已经没有对手、严格说可以删。**故意留着**：
   它是一道廉价的护栏 —— 哪天有人把 #hud-ver 又塞回某个 `display:none !important` 列表，
   桌面这条会继续赢（同特异度靠源码顺序）。真正的防回归判据在
   verify_local_boot_r157.py 的 ⑤（computed style + 命中测试）。
   ⚠️ 字号 10.5px：`.hud-foot` 在 pcdesk 下仍是移动端那套 9px，1440 宽屏上几乎看不清。 */
html.pcdesk #hud-ver { display: inline !important; font-size: 10.5px; }
html.pcdesk .slot-panel { --qsw: 38px; --qsg: 4px; }
html.pcdesk .qs-key { font-size: 9px; }

/* ③ 聊天抽屉：手机横屏那条是 min(340px,44vw)，桌面上给宽一点、离底边远一点，
      免得压住底部的提示行。位置（底部居中）沿用 landscape 那条，不改。
   ⚠️⚠️ **必须 !important**：multi.js 那条 `html.touch #mchat{...}` 是**运行时注入**
      到 <head> 的，排在 style.css **之后**；选择器的特异度和这里一模一样，
      按"后来者胜"它会赢 —— 实测不加 !important 时桌面端被按回 340px / bottom:8px。 */
html.pcdesk #mchat { width: min(460px, 38vw) !important; bottom: 14px !important; }
html.pcdesk #mchat.open { max-height: min(46vh, 330px) !important; }

/* ④ 桌面没有摇杆 → 也不要"按下摇杆浮现的跑键"（Shift 跑步，键盘本来就有）。
   建都没建，这里只是防以后有人改回 pcLike 建摇杆时忘了这条。 */
html.pcdesk .tsprint { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════
   r193 · 收购面板（trade）—— 规格 design/gdd/trade.md §9.2
   ──────────────────────────────────────────────────────────────────────
   只新增 `.tr-*`；`.p-x` / `.p-foot` / `.inv-tool` / `.bulk-go` 直接复用
   （同一个视觉体系里再造一套按钮 = 玩家要学两遍）。
   ⚠️ 置灰行**必须同时**降不透明度 + 显示原因文字（AC-60：灰度截图仍要能区分）——
      只靠颜色的话色盲玩家看到的就是"一行普通条目"。
   ══════════════════════════════════════════════════════════════════════ */

.tr-bar {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: #7d94a4;
  padding: 2px 2px 9px; border-bottom: 1px solid rgba(255, 255, 255, .08);
  margin-bottom: 9px;
}
.tr-bar #trade-sub { flex: 1; min-width: 0; }
.tr-off {
  flex: none; font-size: 11px; line-height: 1;
  padding: 4px 8px; border-radius: 7px;
  color: #ffb9a8; background: rgba(224, 138, 122, .16);
  border: 1px solid rgba(224, 138, 122, .45);
}

.tr-filters { display: flex; flex-direction: column; gap: 5px; margin-bottom: 9px; }
.tr-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.tr-line > .lb { flex: none; min-width: 24px; font-size: 11px; color: var(--dim); }
.tr-chip {
  -webkit-appearance: none; appearance: none; cursor: pointer;
  padding: 5px 9px; border-radius: 9px; font: inherit; font-size: 12px; line-height: 1.2;
  color: var(--rc, var(--txt)); text-align: left;
  background: rgba(255, 255, 255, .045); border: 1px solid rgba(255, 255, 255, .14);
  transition: background .14s, border-color .14s, color .14s;
}
.tr-chip:hover { background: rgba(255, 255, 255, .09); }
.tr-chip.on {
  background: rgba(255, 208, 138, .15); border-color: rgba(255, 208, 138, .60);
  box-shadow: 0 0 0 1px rgba(255, 208, 138, .20) inset; color: #ffe9c4;
}

.tr-prog {
  font-size: 12px; color: #ffd08a; line-height: 1.4;
  padding: 6px 9px; margin-bottom: 8px; border-radius: 8px;
  background: rgba(255, 208, 138, .10); border: 1px solid rgba(255, 208, 138, .30);
}

.tr-list { display: flex; flex-direction: column; gap: 5px; }
.tr-empty { font-size: 12px; color: #7d94a4; padding: 18px 6px; text-align: center; }

/* 一行 = 勾选 · 图标 · 名称/单价 · 数量 · 行合计；(原因 / 卸下按钮) 换行到第二排 */
.tr-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  padding: 6px 8px; border-radius: 9px; font-size: 12.5px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .09);
  transition: background .12s, border-color .12s;
}
.tr-row.on { background: rgba(255, 208, 138, .10); border-color: rgba(255, 208, 138, .38); }
/* 稀有以上的可卖行：红边（§4.4 的 HIGH），不置灰 —— 但要在汇总里多问一次 */
.tr-row.hi { border-color: rgba(224, 138, 122, .52); }
.tr-row.hi.on { background: rgba(224, 138, 122, .12); }
/* AC-60：色盲也能区分 —— 不透明度 + 原因文字，两者同时出现 */
.tr-row.off { opacity: .46; }

.tr-pick {
  flex: none; width: 18px; height: 18px; padding: 0; cursor: pointer;
  border-radius: 5px; background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .28);
  position: relative;
}
.tr-row.on .tr-pick { background: rgba(255, 208, 138, .85); border-color: #ffd08a; }
.tr-row.on .tr-pick::after {
  content: '\2713'; position: absolute; inset: 0;
  font-size: 12px; line-height: 18px; text-align: center; color: #241708;
}
.tr-pick:disabled { cursor: default; opacity: .55; }

.tr-ic { flex: none; width: 24px; text-align: center; font-size: 17px; line-height: 1; }
.tr-ic img.ic { width: 22px; height: 22px; vertical-align: middle; }

.tr-nm { flex: 1 1 84px; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tr-nm b {
  font-weight: 500; color: var(--txt);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tr-nm em {
  font-style: normal; font-size: 10.5px; line-height: 1.25; color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.tr-nm .tr-book { color: #ffd08a; }

.tr-qty { flex: none; display: flex; align-items: center; gap: 2px; }
.tr-dec, .tr-inc {
  -webkit-appearance: none; appearance: none; cursor: pointer;
  width: 24px; height: 24px; padding: 0; border-radius: 6px;
  font: inherit; font-size: 14px; line-height: 1;
  color: var(--txt); background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .16);
  /* 长按连发时不要弹浏览器的手势菜单 / 选中文字 */
  touch-action: manipulation; user-select: none;
}
.tr-dec:active, .tr-inc:active { background: rgba(255, 208, 138, .24); }
.tr-n {
  width: 36px; height: 24px; padding: 0 2px; border-radius: 6px;
  font: inherit; font-size: 12px; text-align: center;
  color: var(--txt); background: rgba(0, 0, 0, .28);
  border: 1px solid rgba(255, 255, 255, .16);
  font-variant-numeric: tabular-nums;
}
.tr-max {
  -webkit-appearance: none; appearance: none; cursor: pointer;
  height: 24px; padding: 0 6px; border-radius: 6px;
  font: inherit; font-size: 10.5px; line-height: 1;
  color: #ffe1a8; background: rgba(255, 208, 138, .10);
  border: 1px solid rgba(255, 208, 138, .34);
}
.tr-sum {
  flex: none; min-width: 42px; text-align: right;
  font-size: 12.5px; color: var(--gold); font-variant-numeric: tabular-nums;
}
.tr-row.off .tr-sum { color: #7d94a4; }

/* 原因文字独占第二排（flex-basis 100%），不挤压上面那一行的名字 */
.tr-why {
  flex: 1 0 100%; font-size: 10.5px; line-height: 1.4; color: #ffc9b8;
}
.tr-unequip {
  flex: none; margin-left: auto; cursor: pointer;
  padding: 3px 9px; border-radius: 7px; font: inherit; font-size: 11px;
  color: #ffe1a8; background: rgba(255, 208, 138, .12);
  border: 1px solid rgba(255, 208, 138, .34);
}

.tr-warn {
  font-size: 11.5px; line-height: 1.55; color: #ffe2b8;
  background: rgba(255, 208, 138, .12); border: 1px solid rgba(255, 208, 138, .34);
  border-radius: 8px; padding: 7px 9px; margin-top: 7px;
}
.tr-over {
  font-size: 11.5px; line-height: 1.55; color: #ffcdbe;
  background: rgba(224, 138, 122, .14); border: 1px solid rgba(224, 138, 122, .38);
  border-radius: 8px; padding: 7px 9px; margin-top: 6px;
}

.tr-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 9px; padding-top: 9px;
  border-top: 1px solid rgba(255, 255, 255, .08);
}
.tr-total { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--dim); line-height: 1.4; }
.tr-total:empty { display: none; }

/* ── 回执：替换列表内容区（不另开浮层，避免"结果浮在半空"） ── */
.tr-receipt { display: flex; flex-direction: column; gap: 6px; padding: 2px 1px 4px; }
.tr-rc-ok { font-size: 13px; color: #9ff0c0; line-height: 1.5; }
.tr-rc-bad { font-size: 12.5px; color: #ffb9a8; line-height: 1.55; }
.tr-rc-sub, .tr-rc-why, .tr-rc-note, .tr-rc-hint { font-size: 11.5px; color: var(--dim); line-height: 1.5; }
.tr-rc-note { color: #ffd08a; }
.tr-rc-id {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border-radius: 7px;
  background: rgba(0, 0, 0, .30); border: 1px solid rgba(255, 255, 255, .12);
}
.tr-rc-id code {
  flex: 1 1 auto; min-width: 0; font-size: 12px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  color: #cfe6f5; user-select: all; overflow-wrap: anywhere;
}
.tr-copy {
  flex: none; cursor: pointer; padding: 3px 10px; border-radius: 6px;
  font: inherit; font-size: 11px; color: #dff0ff;
  background: rgba(38, 78, 108, .9); border: 1px solid rgba(120, 200, 255, .35);
}

/* ── 二次确认 ──
   ⚠️ 它是 #trade 的子节点：跟着面板一起显示/隐藏、不单独占 z 层。
   position:fixed 让确认层盖住整屏（桌面），面板本身带 transform 时（手机）
   会自动退化成"相对面板"，正好也是我们要的（手机上面板本来就近全屏）。 */
.tr-cf {
  position: fixed; inset: 0; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  padding: 18px;
  background: rgba(6, 12, 18, .62);
  backdrop-filter: blur(2px);
}
.tr-cf-box {
  width: min(320px, 92vw); padding: 16px 16px 13px;
  display: flex; flex-direction: column; gap: 7px;
  border-radius: 13px; background: rgba(18, 28, 38, .97);
  border: 1px solid rgba(255, 208, 138, .45);
  box-shadow: 0 18px 46px rgba(0, 0, 0, .55);
}
.tr-cf-box > b { font-size: 14px; letter-spacing: 3px; color: var(--gold); }
.tr-cf-l { font-size: 12.5px; color: var(--dim); }
.tr-cf-l b { font-size: 14px; color: var(--txt); font-variant-numeric: tabular-nums; }
.tr-cf-warn {
  font-size: 12px; color: #ffb9a8; line-height: 1.5;
  padding: 6px 9px; border-radius: 8px;
  background: rgba(224, 138, 122, .14); border: 1px solid rgba(224, 138, 122, .40);
}
.tr-cf-btns { display: flex; gap: 8px; margin-top: 3px; }
.tr-cf-btns > button {
  -webkit-appearance: none; appearance: none; cursor: pointer; flex: 1;
  min-height: 34px; padding: 0 12px; border-radius: 8px;
  font: inherit; font-size: 13px; line-height: 1;
  color: var(--txt); background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .18);
}
.tr-cf-btns > button#cf-yes { border: 0; color: #241708; background: var(--gold); font-weight: 600; }
.tr-cf-btns > button:focus-visible { outline: 2px solid #ffd08a; outline-offset: 2px; }

/* 触控端：热区 ≥40px、行高 ≥44px（AC-22 / §4.7） */
html.touch .tr-chip { min-height: 34px; padding: 7px 11px; font-size: 13px; }
html.touch .tr-row { padding: 8px 9px; min-height: 44px; }
html.touch .tr-pick { width: 22px; height: 22px; border-radius: 6px; }
html.touch .tr-row.on .tr-pick::after { line-height: 22px; font-size: 13px; }
html.touch .tr-dec, html.touch .tr-inc { width: 40px; height: 40px; font-size: 17px; }
html.touch .tr-n { width: 44px; height: 40px; font-size: 13px; }
html.touch .tr-max { height: 40px; padding: 0 9px; font-size: 12px; }
html.touch .tr-dec, html.touch .tr-inc, html.touch .tr-max { touch-action: none; }
html.touch .tr-foot .bulk-go { width: 100%; margin-left: 0; }
html.touch .tr-total { flex-basis: 100%; }

/* ══════════════ r195（P0-3）寄东西给人 / 待领取面板 ══════════════
 * 复用 .tr-*（行 / 列表 / 脚注 / 确认层 / 回执）与 .tabs / .bulk-go / .p-x / .p-foot /
 * .mi-*（待领取行），只补寄出面板专有的少数几个（工具条 / 收件人行 / 金额行 / 页签徽记）。
 * ⚠️ 不新造视觉体系（对齐 trade.md §9.2「不复制、复用」）。 */
.tp-bar {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
  font-size: 11.5px; color: var(--dim);
}
.tp-bar > span:first-child { flex: 1; min-width: 0; }
.tp-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.tp-row > label { flex: none; min-width: 42px; font-size: 11px; color: var(--dim); }
.tp-row > input {
  flex: 1; min-width: 0; box-sizing: border-box; padding: 7px 9px;
  font: 13px/1.3 inherit; color: var(--txt); background: rgba(8, 16, 24, .55);
  border: 1px solid rgba(255, 255, 255, .14); border-radius: 7px; outline: none;
}
.tp-row > input:focus { border-color: rgba(255, 208, 138, .55); }
#tp-coin { flex: none; width: 96px; text-align: right; font-variant-numeric: tabular-nums; }
.tp-hint { flex: none; font-size: 11px; color: #6d8290; }
.tp-tabs { margin-bottom: 8px; }
.tp-badge {
  display: inline-block; margin-left: 5px; min-width: 15px; padding: 0 4px;
  border-radius: 8px; background: #ff5a5a; color: #fff;
  font-size: 10px; line-height: 15px; text-align: center; font-style: normal;
}
.tp-badge.hidden { display: none; }
.tp-inbar { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 12px; color: var(--dim); }
.tp-inbar > span { flex: 1; min-width: 0; }
.tp-in .mi-att i { font-style: normal; }
.tr-nm .tr-have { color: var(--dim); }
/* 入口红点：PC 右上「📮」与移动端抽屉「📮」各一颗（照 pcb-mail / tb-mail 的做法）。 */
#pcb-send { position: relative; }
#pcb-send.has-new::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 9px; height: 9px; border-radius: 50%;
  background: #ff5a5a; box-shadow: 0 0 0 2px rgba(10, 18, 26, .8);
}
/* r207d：⚠️⚠️ **补 background** —— 用户报的"菜单中的邮件小图标按钮底色是白色"就是这颗。
   📮 本身就是个信封图标，玩家看到它第一反应就是"邮件"，所以报的是"邮件按钮白底"；
   而真正叫「邮件」的 `tb-mail`（✉）早在第 53 轮就有深棕底了。
   这颗只在 r195 加了 `position: relative`（为了红点 `::after`），**底色一直空着**
   ⇒ 吃 `<button>` 的 UA 默认 `buttonface`（近白）+ `.tbtn` 的 `color:#eaf4ff`（近白）
   = 白字白底看不清。配色取和 `tb-mail` 同族的暗金（"寄出"是邮件的另一半语义），
   但明显更冷更灰一点，和「收到」（tb-mail）在键簇里能一眼分开。 */
.tbtn.tb-send {
  position: relative;
  border-color: rgba(214, 186, 130, .54);
  background: linear-gradient(180deg, rgba(56, 46, 30, .90), rgba(28, 23, 15, .94));
}
.tbtn.tb-send.has-new::after {
  content: ''; position: absolute; top: 3px; right: 3px;
  width: 8px; height: 8px; border-radius: 50%;
  background: #ff5a5a; box-shadow: 0 0 0 2px rgba(10, 18, 26, .8);
}
html.touch .tp-row > input { min-height: 40px; font-size: 14px; }
html.touch #tp-coin { width: 110px; }
html.touch .tp-tab { min-height: 38px; }

/* ══════════════ r195 · 兼容层（PC/移动端可按性）· design/ui/trade-p2p-compat.md ══════════════
 * ⚠️ 全部写在文件**末尾**（§2.1 规矩 2）：既有触控档同特异度时靠**源码顺序**取胜。
 * ⚠️ 一律带 `html.touch` 前缀（§2.1 规矩 3）：不写裸 `.tr-x` 去覆盖触控值（[0,1,0] 会被 [0,2,1] 反杀）。
 * ⚠️ 几何挂既有 `t-h-*` 三档，不新增 @media（PC 端 touch.js:1846 **不给** pcdesk 挂档 → PC 自动走不到）。
 */

/* ── D/E §2.2 §2.3：触摸目标 ≥44px + 按压反馈 ── */
html.touch .tp-tabs .tp-tab { min-height: 44px; font-size: 14px; }   /* D2/D15：须 [0,2,2] 压过 .tabs button */
html.touch #tp-to { min-height: 48px; }                             /* D3/F3 */
html.touch #tp-coin { min-height: 48px; }                           /* D4/F3 */
html.touch .tr-pick {                                                /* D1/D1b/D1d：视觉 26，热区 44 */
  width: 26px; height: 26px; border-radius: 7px; margin: 0 9px 0 0; position: relative;
}
html.touch .tr-pick::before { content: ''; position: absolute; inset: -9px; }   /* 26 + 9×2 = 44 */
html.touch .tr-row.on .tr-pick::after { line-height: 26px; font-size: 14px; }   /* D1c */
html.touch .tr-dec, html.touch .tr-inc { width: 44px; height: 44px; font-size: 18px; }  /* D5 */
html.touch .tr-n { width: 48px; height: 44px; font-size: 16px; }    /* D6/F2 */
html.touch .tr-max { min-height: 44px; padding: 0 10px; font-size: 13px; }       /* D7 */
html.touch #tp-go { min-height: 48px; }                             /* D8 */
html.touch #tp-x { width: 44px; height: 44px; top: 6px; right: 6px; }  /* D9：**只改本面板**（B1 拍板①） */
html.touch .mi-claim { min-height: 44px; padding: 6px 14px; }       /* D10 */
html.touch #tp-inall { min-height: 44px; padding: 6px 12px; }       /* D11 */
html.touch .tr-copy { min-height: 44px; padding: 6px 12px; }        /* D12 */
html.touch .tr-unequip { min-height: 44px; padding: 6px 12px; }     /* D13 */
html.touch .tr-cf-btns > button { min-height: 48px; }              /* D14 */

/* 按压反馈（E1~E7）：用**会改变视觉**的属性；触摸无 hover，必须靠 :active */
.tp-tab:active { background: rgba(255, 208, 138, .22); }           /* E1 */
.tr-pick:active { transform: scale(.90); }                          /* E2 */
.tr-max:active { background: rgba(255, 208, 138, .24); }           /* E3 */
.tr-copy:active { transform: scale(.94); }                         /* E4 */
.tr-unequip:active { background: rgba(255, 208, 138, .24); }       /* E5 */
html.touch .tr-dec:active, html.touch .tr-inc:active { background: rgba(255, 208, 138, .32); }  /* E7 */

/* ── F §2.5：消除 iOS 聚焦缩放（输入框字号 ≥16px）── */
html.touch .tp-row > input { font-size: 16px; }                    /* F1（同选择器、位置在后 → 取胜）*/
html.touch body { -webkit-text-size-adjust: 100%; }                /* F4：防横竖屏切换文字被放大 */

/* ── G §2.6：确认层真模态（发现 G）──
 * ⚠️ `.tr-cf` 是 `#tp` 子节点，而 `html.touch .panel` 带 transform → 祖先 transform 使
 *   `position:fixed` 的包含块变成**面板**（REFERENCE-模块.md:165-169 记过的坑）→ 面板上下
 *   各约 190px 裸露，PC 点那儿会落到 3D 画布 → tryAttack() 挥空刀。
 *   方案（B 拍板）：触控档把确认层改成**相对面板铺满**（`absolute; inset:0`），彻底绕开包含块坑；
 *   面板外那圈用 G7（`#app.cf-on` → pointer-events:none）挡住。
 *   ⚠️ G7 **更正（art-director v1.1）**：闸门要加在 **`#app`** 上，不是 `#tp` ——
 *      `#tp` 盒只有约 1420×554，盒外上下各 150~240px **从未进过 `#tp` 盒**，
 *      加在 `#tp` 上对那圈完全无效（命中直落 `#view`）。`#app` 是面板祖先、铺满视口，卡这里才盖得住。
 *   ⚠️ 另注：`tryAttack()` 已加 `panelOpen()` 守卫（main.js，T-P03-14 方案 A）——
 *      这是**根治**（任何面板外那圈点击都不再挥刀）；G7 是**几何兜底**（命中也别落到画布）。
 *      两条互为补充，都保留。 */
html.touch #tp-cf {
  position: absolute; inset: 0; border-radius: 14px;
  touch-action: none; overscroll-behavior: contain;
}
#tp-cf { touch-action: none; overscroll-behavior: contain; }        /* G5/G6（双端）*/
html.touch #app.cf-on { pointer-events: none; }                     /* G7：面板外不再落到 view */
html.touch #app.cf-on #tp-cf,
html.touch #app.cf-on #tp-cf * { pointer-events: auto; }            /* 子层写回 auto（REFERENCE-模块.md:222）*/

/* ── I/K/J §2.4：布局 —— 内容区限高自滚 / 安全区 / touch-action ── */
html.touch .panel {                                                /* L1/L2：补左右 + 上安全区 */
  padding-left: max(12px, env(safe-area-inset-left));
  padding-right: max(12px, env(safe-area-inset-right));
  padding-top: calc(12px + env(safe-area-inset-top, 0px));
}
#tp-slist {                                                        /* L3：照 #sp-body 范式限高自滚 */
  max-height: 44vh; overflow-y: auto; overscroll-behavior: contain;
}
#tp-inlist { max-height: 44vh; overflow-y: auto; overscroll-behavior: contain; }   /* L4 */
html.touch #tp { touch-action: pan-y; overflow: hidden; }          /* L5 + 滚动职责下沉到列表 */

/* ── §2.12（AC-87 真红修正）：给 footer 留位 —— 内容页纵向 flex，列表吃剩余、footer 钉住 ──
 * 根因：L3/L6 只给**列表**限高（44vh/30vh），**没给 footer 留位** ⇒ 列表 + 两行输入 + 标题
 *       + tabs + `#tp-foot` 的总高 > 面板 `max-height:74vh` ⇒ `#tp{overflow:hidden}` 把 footer
 *       **切出面板外**（实测 844×390 时 `#tp-go` 落在面板下方 157px，`elementFromPoint` 命中 `view`）。
 *       ⚠️ 这是**结构高度**问题，**与行数无关** —— 只 3 行也红（`goTop` 恒定）。
 * 做法（art-director §2.12 选项①，为使其**真正生效**做了必要强化）：
 *   · `#tp` 变**纵向 flex** 并作为收缩容器 —— ⚠️ 这一步必不可少：`#tp` 只有 `max-height`、无
 *     `height`，子项的 `height:100%` 会解析成 `auto`（百分比高度对 auto 父不解析）而**形同没写**；
 *     必须让 `#tp` 自己当 flex 容器，`#tp-send/#tp-inbox` 才能靠 `flex` 吃到"剩余高度"。
 *   · 两内容页 `flex:1 1 auto; min-height:0`（吃剩余高度并可**真正收缩**）。
 *   · 列表 `flex:1 1 auto; min-height:0`；footer `flex:none` **钉住**，永不参与收缩。
 * ⚠️ **`min-height:0` 是关键**：flex 子项默认 `min-height:auto` **不收缩**，缺它整个方案失效。
 * ⚠️ `.hidden{display:none!important}`（:11，带 `!important`）**优先于**此处的 `display:flex`
 *    （无 `!important`）⇒ 切页签 `#tp-send` ↔ `#tp-inbox` 互斥隐藏**不受影响**。
 * ⚠️ L3/L4 的 `max-height:44vh` **保留作上限**（矮屏仍由 L6 降到 30vh）；空间不足时 flex 再往下收缩。
 * ⚠️ `#tp-cf` 是 `#tp` 的子节点但 `position:absolute`（脱离文档流）⇒ 不受这套 flex 影响。
 * ⚠️⚠️ **实测补充（flex 单独不够）**：横屏 844×390 下 `#tp-send` 的**固定件**就已经高约 179px
 *      （两行输入 48×2 + `#tp-foot` 83）+ 面板骨架（标题/副条/页签/底注）约 94px ≈ 273px，
 *      超过 `html.touch .panel` 的 `max-height:74vh`（=288px，减去 padding 后仅约 265px 可用）。
 *      ⇒ 即便 flex 让列表收缩到 0，**footer 仍被裁**。故**必须同时把 `#tp` 放宽**。
 *      这里**只对 `#tp` 局部**放宽到 `92vh`（不改全局 `.panel` 的 74vh，其它面板不受影响）——
 *      也更贴 `.panel` 注释里"居中近全屏"的初衷。横屏实测：footer 从"溢出 34px"变为"稳定在盒内"。
 *      竖屏 390×844 本已足够（面板按内容收缩，放宽上限不改变其实际高度）。
 * ⚠️⚠️ **但只放宽 + flex 仍不够**（实测）：横屏把面板撑到 359px 后，**列表被压到只剩 1px**
 *      （修复前它是 172px）—— 即"footer 回来了，列表没了"，同样是不可用。根因是横屏的**固定件本身
 *      太高**（两行输入 + footer 就 ~179px）。故按 art-director §2.12 **选项③（与①叠加）**，
 *      在**矮屏两档（t-h-mid / t-h-lo）**再收一轮：压缩区块间距 + footer 单行化 + 收起 `.p-foot`。
 *      ⚠️ 这些压缩**只作用于矮屏两档**，`t-h-hi`（竖屏/大屏横屏）**完全不受影响**（实测竖屏几何零变化）。
 *      `.p-foot`（"Esc 关闭…"）在触摸矮屏本就无意义（手机无 Esc 键），且既有 L8 已在 `t-h-lo` 隐藏它，
 *      这里是把它**扩展到 `t-h-mid`**，与既有设计意图一致。 */
html.touch #tp { display: flex; flex-direction: column; max-height: 92vh; }
/* 面板骨架（标题 / 副条 / 页签 / 底注）一律**不参与收缩**，把"让位"全交给内容页 */
html.touch #tp > h2,
html.touch #tp > .tp-bar,
html.touch #tp > .tp-tabs,
html.touch #tp > .p-foot { flex: none; }
html.touch #tp-send,
html.touch #tp-inbox { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
/* 内容页里的"固定件"（输入行 / 提示 / footer / 回执）不收缩 —— 只留列表一个可伸缩项。
 * ⚠️ `> *` 是 (1,1,2)，会**压过**下面 (1,1,1) 的列表规则 —— 故列表必须写 `> #id` 提权到 (2,1,2)。 */
html.touch #tp-send > *,
html.touch #tp-inbox > * { flex: none; }
/* 列表：吃剩余高度，且**可真正收缩**（min-height:0 是关键）；空间不足时先于 footer 收缩。
 * ⚠️ `max-height: none` 必须写（art-director §2.12 v1.6）：L3/L4 的 `44vh`、L6 的 `30vh` 是
 *    `max-height`，**优先级高于 `flex` 的分配** —— 不显式清掉，flex 算出的高度会被 44vh 截短，
 *    矮屏下"="列表按视口百分比分高度"的老毛病复发。 */
html.touch #tp-send > #tp-slist,
html.touch #tp-inbox > #tp-inlist { flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; }

/* ── §2.12 选项③（矮屏叠加，art-director v1.8）：把横屏"固定块"瘦身，让列表拿回可用高度 ──
 * ⚠️ 为何① flex 单独不够：945×390 这类视口下，`#tp-send` 的固定块（两行输入 96 + footer 83）
 *    就吃满/超过面板内容盒，"剩余空间"为零甚至为负 —— flex 分不出空间。
 * ⚠️ 为何不能只靠"把 `#tp` 放宽到 92vh"：那只会把面板撑高，**固定块按视口百分比算高度**的问题
 *    依旧在（面板越高、列表越被挤成 0），实测横屏 844×390 列表仅 1px（修复前是 172px）——
 *    "按钮回盒内、列表没了"是同等级别的不可用（AC-87c 就是为这解药而来）。
 * ⇒ 按 art-director §2.12.3b + ③：矮屏两档**收紧间距 + footer 单行化（含复位 `tr-total` 的
 *    `flex-basis:100%`）+ 两行输入改并排（grid）**，把纵向让给列表。
 * ⚠️ 全部**只作用于 `t-h-mid` / `t-h-lo`**：`t-h-hi`（竖屏 / 大屏横屏）完全不受影响。 */
/* b1 收紧间距 + 藏非关键块（副说明条 / 面板底注；主按钮与输入框一个不藏）*/
html.touch.t-h-mid .tp-bar,
html.touch.t-h-lo .tp-bar { display: none; }
html.touch.t-h-mid #tp > .p-foot,
html.touch.t-h-lo #tp > .p-foot { display: none; }                /* 手机无 Esc 键，此提示无意义 */
html.touch.t-h-mid #tp > h2,
html.touch.t-h-lo #tp > h2 { margin-bottom: 2px; }
html.touch.t-h-mid #tp > .tp-tabs,
html.touch.t-h-lo #tp > .tp-tabs { margin-bottom: 4px; }
/* b2 footer 两行收单行：件高 83 → ~48。
 * ⚠️ 必须复位 `.tr-total` 的 `flex-basis:100%`（style.css:3520 的 `html.touch` 规则）——
 *    否则在 `nowrap` 下它仍独占整行宽，把按钮**顶出视口右边**（实测 360 宽时按钮 left=379）。
 * ⚠️ 不动 `#tp-go` 的 `min-height`（保留既有 48px，D8）。 */
html.touch.t-h-mid #tp-send .tr-foot,
html.touch.t-h-lo #tp-send .tr-foot { flex-wrap: nowrap; margin-top: 4px; padding-top: 4px; gap: 6px; }
html.touch.t-h-mid #tp-send .tr-foot .tr-total,
html.touch.t-h-lo #tp-send .tr-foot .tr-total {
  flex: 1 1 auto; flex-basis: auto; min-width: 0; font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.touch.t-h-mid #tp-send .tr-foot .bulk-go,
html.touch.t-h-lo #tp-send .tr-foot .bulk-go { width: auto; margin-left: auto; flex: 0 0 auto; padding: 0 14px; }
/* b3 两行输入**并排**（§2.12 ③「小屏把 .tp-row 收成一行」，省 48px 全给列表）。
 * ⚠️ 用 `minmax(0,1fr)` 而非 `1fr`：`1fr` 的最小尺寸是 `auto`（=内容宽），输入框长文案会把
 *    列撑宽、footer 随之溢出视口右边（实测 360 宽下按钮中心落在视口外、`elementFromPoint` 得 null）。 */
html.touch.t-h-mid #tp-send,
html.touch.t-h-lo #tp-send {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-rows: auto 1fr auto auto; column-gap: 8px;
}
html.touch.t-h-mid #tp-send > .tp-row,
html.touch.t-h-lo #tp-send > .tp-row { grid-row: 1; min-width: 0; }
html.touch.t-h-mid #tp-send > .tp-row:nth-of-type(1),
html.touch.t-h-lo #tp-send > .tp-row:nth-of-type(1) { grid-column: 1; }
html.touch.t-h-mid #tp-send > .tp-row:nth-of-type(2),
html.touch.t-h-lo #tp-send > .tp-row:nth-of-type(2) { grid-column: 2; }
html.touch.t-h-mid #tp-send > #tp-slist,
html.touch.t-h-lo #tp-send > #tp-slist { grid-column: 1 / -1; grid-row: 2; }
html.touch.t-h-mid #tp-send > #tp-warn,
html.touch.t-h-lo #tp-send > #tp-warn { grid-column: 1 / -1; grid-row: 3; }
html.touch.t-h-mid #tp-send > #tp-foot,
html.touch.t-h-lo #tp-send > #tp-foot { grid-column: 1 / -1; grid-row: 4; }

/* 矮屏三档（L6~L9）：挂既有 t-h-*，PC 不挂档故不受影响 */
html.touch.t-h-lo #tp-slist, html.touch.t-h-lo #tp-inlist { max-height: 30vh; }   /* L6 */
html.touch.t-h-lo .tp-row { margin-bottom: 6px; }                  /* L7 */
html.touch.t-h-lo #tp .p-foot { display: none; }                   /* L8 */
html.touch.t-h-lo #tp-cf .tr-cf-box { max-height: calc(100vh - 24px); overflow-y: auto; }  /* L9 */

/* ══════════════ r197：任务系统 L1（#quest 追踪条 + #questpanel）══════════════
 * 规格：design/gdd/quest.md §7。两个界面都在 **#app 内**（同 #card/#prompt/#talk）
 * ⇒ 不需要 z>65（那是**挂 body 的浮层**才需要的，见 style.css:1573-1581 的注释）。 */

/* U1 · 任务追踪条：**屏幕右侧**（规格 §7.2「不压 #hud（左上）/ #pcbtns（右上）/ #prompt（底部）」），
 * 最多 3 行小字。⚠️ 桌面小地图在**左上**（#minimap left:14）、#hud 也在左上 ⇒ 追踪条落**右侧**
 * 才不与二者相撞；触屏小地图在**右上**（style.css:1697）⇒ 触屏档下移到它下方（见 html.touch 段）。 */
#quest {
  position: absolute; right: 14px; top: 150px;
  width: 226px; z-index: 21;                 /* 同 #card(20)/#prompt(22) 一级，都在 #app 内 */
  display: flex; flex-direction: column; gap: 3px;
  font-size: 12px; line-height: 1.35; pointer-events: auto;
}
#quest.hidden { display: none; }
/* r207d：收起按钮（✕）。用户原话「游戏界面里的任务栏无法关闭」。
   ⚠️ 定位用 absolute 挂在 #quest 的**右上角外侧**（right:-8px）而不是塞进第一条里 ——
      塞进去会挤掉那条任务的文字宽度（追踪条只有 226px，"▸ 任务名 + 进度"已经贴边）。
   ⚠️ #quest 是 flex 容器，absolute 子元素**不参与** flex 布局，所以它不会把
      第一条任务挤下去；这里靠 flex 容器自身的 position:absolute 建立包含块。 */
#quest .qs-x {
  position: absolute; right: -6px; top: -7px;
  width: 19px; height: 19px; padding: 0; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; color: #cfe0ee; cursor: pointer;
  background: rgba(20, 34, 46, .92); border: 1px solid var(--line);
  border-radius: 50%; backdrop-filter: blur(6px);
  -webkit-tap-highlight-color: transparent;
}
#quest .qs-x:hover { background: rgba(255, 208, 138, .26); color: #fff; }
#quest .qs-x:active { transform: scale(.9); }
#quest .qs-row {
  display: flex; align-items: baseline; gap: 6px; text-align: left; width: 100%;
  padding: 4px 9px; border-radius: 9px; cursor: pointer; font: inherit; color: var(--txt);
  background: rgba(20, 34, 46, .82); border: 1px solid var(--line);
  backdrop-filter: blur(6px);
}
#quest button.qs-row { -webkit-tap-highlight-color: transparent; }
#quest .qs-row:active { background: rgba(255, 208, 138, .18); }
#quest .qs-done { border-color: rgba(255, 208, 138, .42); color: #ffe1a8; }
#quest .qs-claimed { color: #9ff0c0; }
#quest .qs-hint { color: var(--dim); font-size: 11px; }
#quest .qs-more { color: var(--dim); font-size: 11px; padding-left: 9px; }

/* U2 · 任务面板：四段式，逐字复用 .panel.wide（已适配手机），只补列内样式。 */
.qp-bar-wrap { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px; font-size: 12px; color: var(--dim); }
#qp-bar { color: var(--txt); }
.qp-sub { font-size: 11px; opacity: .85; }
#qp-body { display: flex; flex-direction: column; gap: 6px; max-height: 52vh; overflow-y: auto; }
.qp-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 7px 9px; border-radius: 9px; font-size: 12.5px;
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .09);
}
.qp-row .qp-hint { flex: 1 1 auto; min-width: 0; color: var(--dim); font-size: 11.5px; }
.qp-row .qp-badge { flex: none; color: #ffe1a8; font-size: 11.5px; }
.qp-claim {
  flex: none; cursor: pointer; font: inherit; font-size: 12px;
  padding: 5px 12px; border-radius: 7px; color: #241708; background: var(--gold);
  border: 1px solid rgba(255, 208, 138, .5);
}
.qp-claim:active { transform: scale(.95); }

/* html.touch 覆盖（§7.5：不写就会被手机上右上那圈功能键压住 / 触控目标太小） */
html.touch #quest {
  /* 触屏小地图在**右上**（style.css:1697，top≈8+84px）⇒ 追踪条下移到它下方，避免压住地图。 */
  right: max(10px, env(safe-area-inset-right));
  top: calc(env(safe-area-inset-top, 0px) + 100px);
  width: 200px; font-size: 11.5px;
}
html.touch #quest .qs-row { min-height: 44px; align-items: center; }   /* 每行触控热区 ≥44px（AC-129）*/
html.touch #quest .qs-hint { font-size: 10.5px; }
html.touch #questpanel { width: calc(100vw - 20px) !important; }
html.touch #questpanel .qp-claim { min-height: 44px; padding: 8px 16px; font-size: 13px; }
html.touch #qp-body { max-height: 44vh; }


/* ══════════════ r198：商店买入面板（#trade-buy）· 规格 design/gdd/shop.md §4.5 ══════════════
 * 遵守 design/ui/trade-p2p-compat.md v1.8 的四条硬约束：
 *   ① 触摸目标 ≥44px（iOS HIG）；② `html.touch` 压过 `html.pcdesk`（一律带 html.touch 前缀，
 *   几何挂既有 `t-h-*` 三档，不新增 @media）；③ `.hidden{display:none!important}` 不可覆盖
 *   （页签切换只用 classList.toggle('hidden')）；④ 矮屏固定块瘦身 + 列表吃剩余空间。
 * ⚠️ 全部写在文件**末尾**（同特异度靠源码顺序取胜，与既有触控档一致）。
 * ⚠️ 只对 `#trade-buy` / `#buy-*` 作用 —— 卖出面（#trade-sell / #trade-list / #trade-go）
 *    **零改动**，r193 的 155 条收购探针不会受这里影响。
 */

/* ── 页签间距（买入/卖出共用 #trade-tabs）：PC 默认由 .tabs 给，这里只压一下与副条的距离 ── */
#trade-tabs { margin-bottom: 8px; }
#trade-buy .tr-bar #buy-bal { flex: none; font-size: 12px; color: var(--gold); font-variant-numeric: tabular-nums; }
#trade-buy .tr-bar #buy-sub { flex: 1; min-width: 0; }

/* ── 买入列表：PC 端限高自滚（同 #tp-slist 范式，避免长列表把 footer 顶出面板） ── */
#buy-list { max-height: 44vh; overflow-y: auto; overscroll-behavior: contain; }
#buy-receipt { max-height: 30vh; overflow-y: auto; }

/* ── 买入确认层：与 #trade-cf 同几何（它是 #trade 子节点，天然随面板显示/隐藏） ── */
#trade-buy-cf { touch-action: none; overscroll-behavior: contain; }
#trade-buy-cf .tr-cf-box > b { letter-spacing: 3px; }
#trade-buy-cf .tr-cf-btns > button#buy-cf-yes { border: 0; color: #241708; background: var(--gold); font-weight: 600; }

/* ── 触摸端：买入面的触控档（≥44px）。页签复用 r195 的 .tp-tabs .tp-tab（已 44px） ── */
html.touch #buy-bal { font-size: 11.5px; }
html.touch #buy-list { max-height: 40vh; }
html.touch #trade-buy .tr-row { min-height: 44px; }
html.touch #buy-go { min-height: 48px; }

/* ══ 矮屏（横屏手机）：买入面比卖出面**多一个页签**（+44px），必须把固定块瘦身，
 *    并让 #buy-list 吃剩余高度、#buy-foot 钉住 —— 否则买入主按钮会被 #trade{overflow:auto}
 *    裁出面板外（AC-87 同类血案）。
 *    ⚠️ 只在 `#trade.buy-on`（= 当前停在「向他买」页签）时启用纵向 flex：
 *       卖出态不挂类 ⇒ 卖出面布局**逐像素不变**（零回归）。
 *    ⚠️ `#buy-list` 的限高比卖出面（#tp-slist 是 44vh→30vh）**再低一档**（38vh→26vh），
 *       因为买入面多一个页签、固定件更高。 */
html.touch #trade.buy-on { display: flex; flex-direction: column; max-height: 92vh; overflow: hidden; }
html.touch #trade.buy-on > h2,
html.touch #trade.buy-on > .p-x,
html.touch #trade.buy-on > #trade-tabs,
html.touch #trade.buy-on > #trade-sell { flex: none; }
html.touch #trade.buy-on > #trade-buy { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
html.touch #trade.buy-on > #trade-buy > * { flex: none; }
html.touch #trade.buy-on > #trade-buy > #buy-list { flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; }
/* 矮屏两档：收紧间距 + 藏副说明（买入面固定件更高，比卖出面多收一点） */
html.touch.t-h-mid #trade.buy-on > #trade-buy > .tr-bar,
html.touch.t-h-lo #trade.buy-on > #trade-buy > .tr-bar { display: none; }
html.touch.t-h-mid #trade.buy-on > #trade-buy > .p-foot,
html.touch.t-h-lo #trade.buy-on > #trade-buy > .p-foot { display: none; }
html.touch.t-h-mid #trade.buy-on > #trade-tabs,
html.touch.t-h-lo #trade.buy-on > #trade-tabs { margin-bottom: 4px; }
/* ⚠️ r198-fix：这两条原写 `max-height: 38vh`（照 shop.md §4.5 第 4 条的「38vh/26vh」），
 *    但它与紧邻的 `:3851`（`flex: 1 1 auto; min-height: 0; max-height: none`）**自相矛盾**：
 *    r197 修 AC-87 已实测出同一坑 —— **`max-height` 会盖过 `flex` 的分配**，把列表截短
 *    （`#tp-slist` 的 44vh/30vh 就这么把 flex 压没了）。故这里统一改成 `max-height: none`，
 *    让 `:3851` 的 `flex:1 1 auto` 真正吃到"剩余高度"。
 *    （注：`:3851` 的 `html.touch #trade.buy-on > #trade-buy > #buy-list` 是 (3,2,1)、
 *     这两条是 (2,3,1)——ID 数优先，`:3851` **本就**把这里覆盖成 `none`；改成 `none` 是
 *     消除「两处互相打架」的隐患，语义与最终生效值一致，不改任何实测几何。） */
html.touch.t-h-mid #trade.buy-on #buy-list,
html.touch.t-h-lo #trade.buy-on #buy-list { max-height: none; }
html.touch.t-h-lo #trade.buy-on #buy-filters { display: none; }        /* 最矮屏：筛选条先让位给列表 */

/* ══ 矮屏实测修正（r198-fix / AC-63·64）：`#buy-foot` 两行收单行 ══════════════════════
 * 为什么必须做（实测，非推断）：买入面的固定件比卖出面**多一个页签（+44px）**，
 *   矮屏下把 `#buy-list`（flex:1）压得不够高 —— 实测 `812×375` 仅 **69px** ⇒ AC-64 红
 *   （同档 `844×390` 83px 刚过线）。做法照搬 r195 修 AC-87 的 §2.12.3b（对 `#tp-foot` 的
 *   那一套），把 footer 从两行（83px）收成一行（~53px），把 30px 让给列表。
 *   把 30px 让给列表。
 *   4 档实测（t-h-mid：844×390 / 812×375；t-h-lo：780×330 / 360×300）：列表 118 / 104 / 145 / 117px，
 *   全部 ≥80（修前 `812×375` 仅 **69px**，是唯一不过线的一档）；主按钮 4 档全在盒内、中心命中自身。
 * ⚠️ 必须复位 `#buy-total` 的 `flex-basis:100%`（style.css:3520 的 `html.touch .tr-total`）——
 *   否则 `nowrap` 下它独占整行、把按钮**顶出视口右边**（AC-87 踩过的原话，此处照抄）。
 * ⚠️ 用 id 选择器（特异度 3,3,1）压过 `.tr-foot .bulk-go`（0,3,1）与 `html.touch .tr-total`（0,2,1）。
 * ⚠️ 只作用于 `#trade.buy-on` 的买入面；卖出面（#trade-sell）与 `t-h-hi` 完全不受影响。 */
html.touch.t-h-mid #trade.buy-on > #trade-buy > #buy-foot,
html.touch.t-h-lo #trade.buy-on > #trade-buy > #buy-foot {
  flex-wrap: nowrap; margin-top: 4px; padding-top: 4px; gap: 6px;
}
html.touch.t-h-mid #trade.buy-on > #trade-buy > #buy-foot > #buy-total,
html.touch.t-h-lo #trade.buy-on > #trade-buy > #buy-foot > #buy-total {
  flex: 1 1 auto; flex-basis: auto; min-width: 0; font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.touch.t-h-mid #trade.buy-on > #trade-buy > #buy-foot > #buy-go,
html.touch.t-h-lo #trade.buy-on > #trade-buy > #buy-foot > #buy-go {
  width: auto; margin-left: auto; flex: 0 0 auto; padding: 0 14px;
}

/* ══════════════════════════════════════════════════════════════════════
   r200 · S3 终局巨兽的血条（规格 design/gdd/boss-s3.md §4）
   ⚠️ 顶部居中、**独立于 #hud**（#hud 是左上角 194px 面板）。
   ⚠️ 只在「锁定的是巨兽」时显示 —— `hidden` 由 main.js 控制，
      初始就带 hidden，绝不能闪一下。
   ⚠️ 宽度 min(62vw, 420px)：窄屏不横穿全屏、宽屏不显得小气。
   ⚠️ 位置在屏幕上方 12%（不是贴顶）—— 贴顶会和加载条/公告撞。
   ⚠️ 矮屏（t_h_lo，如 360×300）要**缩高度**，否则会压到技能栏。
   ══════════════════════════════════════════════════════════════════════ */
.boss-bar {
  /* ⚠️⚠️ 顶部三个居中元素要**排队**，一个都不能撞：
       #target  top:16px  （目标条，高约 62px ⇒ 下沿 ~78）
       #toast   top:70px  （提示条，高约 34px ⇒ 下沿 ~104，**且它会换行变高**）
       本元素   ⇒ 必须从 **118px** 起，否则和 toast 叠（第一版 92px 就撞了）。
     用固定 px 而不是 %：矮屏下 % 会往上飘，正好飘进 toast 里。 */
  position: absolute; left: 50%; top: 118px; transform: translateX(-50%);
  width: min(62vw, 420px); z-index: 18;
  pointer-events: none;              /* ⚠️ 和 #hud 一样：不抢点击 */
  display: flex; flex-direction: column; gap: 4px;
}
.boss-bar.hidden { display: none; }
.boss-name {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; line-height: 1.2; color: #f0e6d8;
  text-shadow: 0 1px 3px rgba(0,0,0,.8);
  justify-content: center;
}
.boss-name > b { font-weight: 500; letter-spacing: .06em; }
.boss-track {
  position: relative; height: 9px; border-radius: 5px; overflow: hidden;
  background: rgba(12,10,10,.62);
  border: 1px solid rgba(255,255,255,.22);
  box-shadow: 0 2px 8px rgba(0,0,0,.5);
}
.boss-track > i {
  position: absolute; inset: 0; width: var(--w, 100%);
  border-radius: 4px;
  /* 巨兽血条用暗红→橙的渐变，和玩家那条青色血条明显不同色系 */
  background: linear-gradient(90deg, #7a1f22, #c8433a 62%, #e0774a);
  transition: width .18s linear;
}
.boss-hint {
  font-size: 11px; line-height: 1.3; text-align: center;
  color: #ffcf9a; text-shadow: 0 1px 3px rgba(0,0,0,.85);
  min-height: 14px;                  /* 占位，避免出现提示时整块跳一下 */
}
.boss-hint:empty { display: none; }

/* 移动端：稍微收紧（触屏屏占比小，标题不该占两行） */
html.touch .boss-bar { width: min(72vw, 420px); top: 114px; }
html.touch .boss-name { font-size: 12px; }
/* ⚠️ 矮屏三档：t-h-lo 最狠（360×300 那种），必须压到最小 */
html.touch.t-h-mid .boss-bar { top: 102px; width: min(80vw, 380px); }
html.touch.t-h-mid .boss-track { height: 8px; }
html.touch.t-h-lo .boss-bar { top: 90px; width: min(88vw, 340px); gap: 2px; }
html.touch.t-h-lo .boss-name { font-size: 11px; }
html.touch.t-h-lo .boss-track { height: 7px; }
html.touch.t-h-lo .boss-hint { font-size: 10px; min-height: 12px; }

/* ══════════════════════════════════════════════════════════════════════
   r202 · 成就面板（规格 design/gdd/achievement.md）
   ──────────────────────────────────────────────────────────────────────
   结构复用 #questpanel 的四段式与 .qp-bar-wrap / .qp-sub（见上），
   只补成就独有的三样：**分节标题** / **进度条** / **行内领奖按钮**。

   ⚠️ 与任务面板的两处**有意不同**：
     ① 任务行是"一句话 + 徽记"（`#q_first_words` 那种），成就行要**显示进度 3/10**
        ⇒ 多了 `.ap-bar` 进度条（纯 CSS 宽度，无 canvas，滚动时零开销）。
     ② 成就**分节**（成长/打斗/生活/探索）——任务只有一条线（'journey'），不需要分节。
   ⚠️ 颜色沿用既有变量（--gold / --dim / --txt），不新引入色板。
   ⚠️ 手机端：外层 .panel.wide 与 #ach-body 的 max-height 已经由 .panel 的
      `max-height: calc(100vh - 40px)` + `overflow:auto` 兜住，这里**不再单独适配**
      （照 §2.12 的教训：多处各自限高会互相打架，把列表压成 0）。 */
#ach-body { display: flex; flex-direction: column; gap: 6px; max-height: 52vh; overflow-y: auto; }
.ap-sec {
  font-size: 11px; letter-spacing: 2px; color: var(--gold);
  margin: 6px 0 2px; padding-left: 2px; opacity: .9;
}
.ap-sec:first-child { margin-top: 0; }
.ap-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: 9px; font-size: 12.5px;
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .09);
}
.ap-row.ap-done { border-color: rgba(255, 208, 138, .45); background: rgba(255, 208, 138, .07); }
.ap-row.ap-got { opacity: .5; }
.ap-main { flex: 1 1 auto; min-width: 0; }
.ap-title { color: var(--txt); }
.ap-desc { color: var(--dim); font-size: 11.5px; margin-top: 1px; }
.ap-right { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.ap-bar { width: 62px; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, .12); overflow: hidden; }
.ap-bar > i { display: block; height: 100%; background: var(--gold); }
.ap-num { font-size: 11px; color: var(--dim); }
.ap-row.ap-done .ap-num { color: #ffe1a8; }
.ap-go {
  cursor: pointer; font: inherit; font-size: 12px;
  padding: 4px 11px; border-radius: 7px; color: #241708; background: var(--gold);
  border: 1px solid rgba(255, 208, 138, .5);
}
.ap-go:active { transform: scale(.95); }

/* ══════════════════════════════════════════════════════════════════════
   r203 · 排行榜面板（规格 design/gdd/leaderboard.md）
   ──────────────────────────────────────────────────────────────────────
   结构复用 .panel.wide + .tabs + .qp-bar-wrap/.qp-sub（见成就面板那节），
   只补排行榜独有的三样：**名次列** / **自己那行高亮** / **前三名配色**。

   ⚠️ 与成就面板一样**不单独限高** —— 外层 .panel 的 max-height + overflow 已经兜住
      （多处各自限高会互相打架把列表压成 0，§2.12 的教训）。
   ⚠️ 颜色沿用既有变量（--gold / --dim / --txt），不新引入色板。
   ⚠️ 自己那行用**背景色**区分（不是边框）—— 边框在窄屏下容易被挤掉看不见。 */
#lb-body { display: flex; flex-direction: column; gap: 4px; max-height: 52vh; overflow-y: auto; }
.lb-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 9px; border-radius: 8px; font-size: 12.5px;
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .08);
}
.lb-rank { flex: none; width: 26px; text-align: right; color: var(--dim); font-variant-numeric: tabular-nums; }
.lb-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--txt); }
.lb-val { flex: none; color: #ffe1a8; font-variant-numeric: tabular-nums; }
.lb-top1 { border-color: rgba(255, 208, 138, .55); background: rgba(255, 208, 138, .12); }
.lb-top1 .lb-rank { color: var(--gold); }
.lb-top2, .lb-top3 { border-color: rgba(255, 208, 138, .3); }
.lb-top2 .lb-rank, .lb-top3 .lb-rank { color: #ffe1a8; }
.lb-me { background: rgba(120, 190, 255, .14); border-color: rgba(120, 190, 255, .4); }
.lb-me .lb-name { color: #cfe8ff; }
.lb-unranked .lb-val { color: var(--dim); font-size: 11.5px; }
.lb-sep { font-size: 11px; letter-spacing: 2px; color: var(--gold); margin: 7px 0 2px; padding-left: 2px; opacity: .9; }
.lb-empty { padding: 18px 6px; text-align: center; color: var(--dim); font-size: 12.5px; }
/* 面板底部的互跳按钮（成就 ⇄ 排行）。⚠️ 它是 **.p-jump 不是 .p-foot** ——
   是个真按钮，要能点、要有最小触控高度（手机端 ≥44px 的既有规矩）。 */
.p-jump {
  display: block; width: 100%; margin-top: 8px; cursor: pointer; font: inherit;
  font-size: 12.5px; padding: 9px 12px; min-height: 38px; border-radius: 8px;
  color: var(--txt); background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .16);
}
.p-jump:active { transform: scale(.98); }
html.touch .p-jump { min-height: 44px; }

/* ══════════════════════════════════════════════════════════════════════
   r204 · 公会面板（规格 design/gdd/guild.md）
   ──────────────────────────────────────────────────────────────────────
   结构同样复用 .panel.wide + .tabs + .qp-bar-wrap/.qp-sub，
   只补公会独有的几样：**等级进度条** / **成员行（带身份色）** / **建会表单**。
   ⚠️ 与成就/排行一样**不单独限高**（外层 .panel 的 max-height + overflow 兜住）。
   ⚠️ 颜色沿用既有变量（--gold / --dim / --txt），不新引入色板。
   ⚠️ 身份用**左侧小标签**区分（不是颜色高亮整行）—— 一眼看出谁是会长。 */
#gl-body { display: flex; flex-direction: column; gap: 4px; max-height: 52vh; overflow-y: auto; }
.gl-sec { font-size: 11px; letter-spacing: 2px; color: var(--gold); margin: 8px 0 2px; padding-left: 2px; opacity: .9; }
.gl-sec:first-child { margin-top: 0; }
.gl-empty { padding: 18px 6px; text-align: center; color: var(--dim); font-size: 12.5px; }
.gl-hint { margin-top: 6px; color: var(--dim); font-size: 11.5px; text-align: center; }
.gl-dim { color: var(--dim); font-weight: 400; }

.gl-notice {
  padding: 7px 9px; border-radius: 8px; font-size: 12.5px; color: var(--txt);
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .08);
  white-space: pre-wrap; word-break: break-word;
}
.gl-notice.gl-dim { color: var(--dim); font-style: italic; }

/* 等级进度 */
.gl-lv { display: flex; align-items: baseline; gap: 8px; font-size: 13px; color: #ffe1a8;
  font-variant-numeric: tabular-nums; margin-bottom: 4px; }
.gl-lv .gl-exp { font-size: 11.5px; color: var(--dim); }
.gl-bar { height: 7px; border-radius: 4px; background: rgba(255, 255, 255, .08); overflow: hidden; }
.gl-bar > i { display: block; height: 100%; background: linear-gradient(90deg, #ffd08a, #ffe1a8); }

/* 成员 / 找会行 */
.gl-row {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 8px; border-radius: 8px; font-size: 12.5px;
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .08);
}
.gl-rk { flex: none; font-size: 10.5px; padding: 1px 5px; border-radius: 5px;
  color: #cfe8ff; background: rgba(255, 255, 255, .08); }
.gl-rk.gl-r1 { color: #ffe1a8; background: rgba(255, 208, 138, .18); }
.gl-rk.gl-r2 { color: #1a1206; background: rgba(255, 208, 138, .75); font-weight: 600; }
.gl-nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--txt); }
.gl-cnt { flex: none; color: var(--dim); font-variant-numeric: tabular-nums; font-size: 11.5px; }
.gl-row button { flex: none; cursor: pointer; pointer-events: auto;
  background: rgba(120, 190, 255, .16); border: 1px solid rgba(120, 190, 255, .4); color: #cfe8ff;
  border-radius: 6px; padding: 2px 7px; font: 11px/1.5 system-ui, "PingFang SC", sans-serif; }
.gl-row button:hover { background: rgba(120, 190, 255, .28); }
.gl-row button.gl-ghost { background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .16); color: var(--txt); }

/* 底部按钮组 */
.gl-btns { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 7px; }
.gl-btns.gl-center { justify-content: center; }
.gl-btns button { cursor: pointer; pointer-events: auto;
  background: rgba(255, 208, 138, .18); border: 1px solid rgba(255, 208, 138, .45); color: #ffe6bd;
  border-radius: 8px; padding: 6px 14px; font: 12.5px/1.4 system-ui, "PingFang SC", sans-serif; min-height: 34px; }
.gl-btns button.gl-ghost { background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .16); color: var(--txt); }
.gl-btns button.gl-danger { background: rgba(255, 120, 120, .16); border-color: rgba(255, 120, 120, .45); color: #ffc4c4; }
.gl-btns button:disabled { opacity: .4; cursor: default; }
html.touch .gl-btns button { min-height: 44px; }

/* 建会 / 改公告表单 */
.gl-form { display: flex; flex-direction: column; gap: 7px; margin-top: 7px; }
.gl-form.gl-inline { margin-top: 4px; }
.gl-form label { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; color: var(--dim); }
.gl-form label.gl-ck { flex-direction: row; align-items: center; gap: 6px; color: var(--txt); font-size: 12px; }
.gl-form input[type=text], .gl-form input:not([type]), .gl-form textarea {
  width: 100%; box-sizing: border-box; padding: 7px 9px; border-radius: 8px;
  background: rgba(0, 0, 0, .28); border: 1px solid rgba(255, 255, 255, .16); color: var(--txt);
  font: 12.5px/1.4 system-ui, "PingFang SC", sans-serif; resize: vertical;
}
.gl-form textarea { min-height: 44px; }
.gl-cost { font-size: 12px; color: #ffe1a8; }


/* ══════════════════════════════════════════════════════════════════════
   r207 · 强化面板（enhance）
   ──────────────────────────────────────────────────────────────────────
   入口是背包工具条的「强化」按钮（不占新键位 —— 26 个字母键已占满）。
   版式：左列选装备（可滚动）、右列看成本 —— 但 .panel.wide 只有 372px，
   两列会挤，所以**默认是上下两段**（列表在上、详情在下），只在宽屏（≥560px）
   才并排。⚠️ 矮屏（360×300）必须不重叠且按钮 ≥44px（规格 AC-S4-11）。

   配色一律复用既有变量（--gold / --line / --glass / --txt / --dim），
   不新造色板；「不够」用红、「够了」用常规色，避免出现第二套视觉语言。
   ══════════════════════════════════════════════════════════════════════ */

.enh-list { display: flex; flex-direction: column; gap: 5px; max-height: 208px; overflow: auto; margin-bottom: 9px; }
.enh-empty { font-size: 12px; color: var(--dim); padding: 14px 4px; text-align: center; line-height: 1.7; }

.enh-row {
  display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;
  padding: 6px 8px; border-radius: 9px; cursor: pointer; text-align: left;
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .10);
  color: var(--txt); font: inherit;
}
.enh-row:hover { background: rgba(255, 255, 255, .08); }
.enh-row.on { border-color: var(--gold); background: rgba(255, 208, 138, .10); }
.enh-row.maxed .enh-lv { color: var(--gold); }

.enh-ic {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; border: 1px solid currentColor; background: rgba(0, 0, 0, .28);
}
.enh-nm { flex: 1 1 auto; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.enh-lv { flex: 0 0 auto; font-size: 12px; color: #9fd0e8; font-variant-numeric: tabular-nums; }
.enh-wear {
  flex: 0 0 auto; font-size: 10px; font-style: normal; padding: 1px 5px; border-radius: 5px;
  color: var(--gold); border: 1px solid rgba(255, 208, 138, .45); background: rgba(255, 208, 138, .10);
}

.enh-detail { min-height: 96px; padding: 9px 10px; border-radius: 10px;
  background: rgba(0, 0, 0, .22); border: 1px solid var(--line); font-size: 12.5px; }
.enh-hd { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.enh-hd b { font-size: 13px; }
.enh-hd p { font-size: 11px; color: var(--dim); margin-top: 2px; }

.enh-costs { list-style: none; padding: 0; margin: 0 0 8px; display: flex; flex-direction: column; gap: 4px; }
.enh-costs li { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--txt); }
.enh-costs li.lack { color: #ff8a8a; }

.enh-meta { display: flex; justify-content: space-between; align-items: center; font-size: 11.5px; color: var(--dim); }
.enh-seg { font-size: 10.5px; opacity: .8; }
.enh-hint { font-size: 11px; color: var(--dim); margin-top: 7px; line-height: 1.6; }

.enh-receipt { text-align: center; padding: 6px 2px; }
.enh-receipt b { font-size: 14px; }
.enh-receipt.good b { color: #8fd0a0; }
.enh-receipt.bad b { color: #ff8a8a; }
.enh-receipt p { font-size: 12px; color: var(--dim); margin-top: 5px; }
.enh-receipt .enh-cost { font-size: 11.5px; }
.enh-receipt .enh-hint { margin-top: 8px; font-size: 11px; }

.enh-go {
  display: block; width: 100%; box-sizing: border-box; margin-top: 9px;
  min-height: 44px;                       /* 规格 AC-S4-11：按钮 ≥44px */
  padding: 10px 12px; border-radius: 10px; cursor: pointer;
  background: rgba(255, 208, 138, .16); border: 1px solid rgba(255, 208, 138, .45);
  color: var(--gold); font: 600 13px/1.2 inherit;
}
.enh-go:hover:not(:disabled) { background: rgba(255, 208, 138, .26); }
.enh-go:disabled { opacity: .45; cursor: not-allowed; }

/* 宽屏才并排（.panel.wide 是 372px，两列会挤） */
@media (min-width: 560px) {
  #enh-panel { width: 520px; }
  #enh-panel .enh-list { max-height: 260px; }
}
/* 矮屏（规格 AC-S4-11 明确要求）：压扁间距，但不小于 44px 的点击区 */
@media (max-height: 420px) {
  .enh-list { max-height: 96px; }
  .enh-detail { min-height: 74px; }
  .enh-go { min-height: 44px; }
}

/* r207b：强化回执里的「下一次要多少」。
   ⚠️ 用户报障：连续强化时，"上一笔的花费"和"下一笔会扣的钱"是两个数，第一版只显示了前者，
   而按钮写着「再来一次」⇒ 玩家以为还是那个价，实际被扣更多（成本随等级单调上涨）。
   ⇒ 现在回执卡片里**两个数都写**，且按钮文案也带上下一次的价钱。
   样式上刻意做出层次：上一笔（既成事实）压暗，下一次（即将发生）提亮 + 金色，一眼分得开。 */
.enh-receipt .enh-next {
  font-size: 12px; margin-top: 6px; color: var(--txt);
  padding: 5px 8px; border-radius: 7px;
  background: rgba(255, 208, 138, .10); border: 1px solid rgba(255, 208, 138, .28);
}
.enh-receipt .enh-next b { color: var(--gold); font-size: 13px; }
.enh-receipt .enh-next .lack { color: #ff8a8a; }
.enh-receipt .enh-cost { color: var(--dim); }
.enh-receipt .enh-cost b { color: var(--txt); }

/* ══════════════════════════════════════════════════════════════════════
   r207c · 「验证邮箱可领奖励」的中性样式
   ──────────────────────────────────────────────────────────────────────
   用户拍板「邮箱验证不再限制任何功能，改用额外奖励激励」。
   于是右上角那颗 chip 的语义从「有个东西被锁住了，点此解锁」变成
   「有个可选的好处，想领就验证」—— 视觉上必须跟着变，否则玩家还是会焦虑：
     · 旧：.acc-mail.warn（琥珀 #ffd08a + 琥珀描边）= "需要注意的问题"
     · 新：.acc-mail.tip（青绿 #9fd0e8 + 弱描边）= "这里有个机会"
   ⚠️ 用**青绿**而不是金色：金色在本项目里已经是「奖励/金币」的专用色
     （`--gold` 用于金币、稀有度 R5、强化按钮），用在这里会和"钱"抢语义。
     青绿在本项目里表示"信息/水/加成"（hud-net、属性值），更贴"这只是个提示"。
   ⚠️ 不要改 .acc-mail.warn —— 它还被别处用（离线等真的需要提示的情况）。
   ══════════════════════════════════════════════════════════════════════ */
.acc-mail.tip { color: #9fd0e8; border-color: rgba(159, 208, 232, .30); }
.acc-mail.tip:hover { border-color: rgba(159, 208, 232, .55); }
/* 账号菜单里的「未验证（不影响功能）」那一行 —— 走和 .warn 一样的"弱提示"口径，
   但用同一套青绿，避免玩家把它读成"我少了什么"。 */
.acm-rows .v.tip { color: #9fd0e8; }

/* ══════════════════════════════════════════════════════════════════════
   r207d · 补上「强化」键的底色（用户报障：「强化按钮底色是白色、而且还没有图标」）
   ──────────────────────────────────────────────────────────────────────
   ⚠️⚠️ 根因是**第 37 轮就记录过的那个坑，只是新键又漏了一次**：
     `.tbtn` 自身**没有 background**，于是新加的键会吃到 `<button>` 的 UA 默认底色
     （buttonface，浅灰近白）。在深色游戏 UI 上就是"一个白圆盘 + 浅色字" ⇒ 糊成一摊看不清。
     第 37 轮给 tb-bag/tb-stat/tb-first 补过、第 53 轮给 tb-mail 补过，
     **r207 新增的 tb-enhance 又漏了**（那一轮的注释甚至写着"新键一律配底色"）。
   ⇒ 规矩（写在这里，加新键时照做）：**`touch.js` 的 `BTNS` 里加任何一项，
     都要在 style.css 里配一条 `.tbtn.<它的 cls> { background: ... }`**。
     没有兜底规则可以依赖 —— `.tbtn` 是透明的，不给底色就是白盘。
     色板沿用"同族深色底 + 各自一抹强调色"（和 tb-bag 金 / tb-stat 蓝 / tb-first 紫 一套）。
   ── 强化用**铁灰 + 赭石**：它是锻造/加工类的功能，和"金(背包)/蓝(属性)/紫(视角)/
      青(画质)"都错开，一眼能分辨。 */
.tbtn.tb-enhance {
  border-color: rgba(214, 168, 122, .56);
  background: linear-gradient(180deg, rgba(68, 52, 38, .90), rgba(34, 26, 20, .94));
}
/* r207d：任务键。**羊皮纸暖黄** —— 和「背包(暗金)/邮件(暗金)/强化(铁灰赭石)」都错开：
   那两颗是"物品/信件"，用的是同一族金属暗金；任务用偏纸的暖黄（明度更高一档），
   在抽屉里一眼能分出"这是看东西的，不是翻东西的"。
   ⚠️ 底色按基类兜底其实也够（不写就吃 `.tbtn` 的深灰），但抽屉里 9 颗都一样深灰
      会失去"扫一眼找功能"的效率，所以显式给一支。 */
.tbtn.tb-quest {
  border-color: rgba(230, 206, 150, .52);
  background: linear-gradient(180deg, rgba(74, 62, 34, .90), rgba(38, 32, 18, .94));
}
/* 顺带：抽屉开关（☰）也没写底色 —— 它一直在吃白底。
   用户这轮只报了邮件和强化，但既然扫到就一起补（同一类问题，留着就是下一次报障）。 */
.tbtn.tb-drawer {
  border-color: rgba(190, 205, 220, .50);
  background: linear-gradient(180deg, rgba(42, 54, 66, .90), rgba(20, 27, 34, .94));
}

/* ══════════════════════════════════════════════════════════════════════
   r207d · **白底按钮的总闸** —— 把 `.tbtn` 基类的 background 补上。
   ══════════════════════════════════════════════════════════════════════

   用户报障：「菜单中的**邮件小图标按钮底色是白色**，和白色文字混为一摊看不清，
   同样，**强化按钮也是这样而且还没有图标**」。

   真因（实测计算样式确认，不是猜）：`.tbtn` 基类（上面的 2385 行）只写了
   `border-radius/border/color/...`，**没有 `background`** ⇒ 没被单独指定底色的
   按钮会落到浏览器 `<button>` 的 UA 默认值 `buttonface`（近白/浅灰）——
   而 `.tbtn` 的文字色是 `#eaf4ff`（近白）⇒ **白字白底**，正是用户看到的样子。
   本轮漏网的两颗：
     · `#tb-send`（📮 寄东西）—— 用户口中的"邮件小图标"其实是它（📮 就是个信封），
       和 `#tb-mail`（✉）不是同一个：`tb-mail` 早就有深棕底，`tb-send` 只写了
       `position: relative`（为了红点 `::after`），底色一直是 UA 默认。
     · `#tb-enhance`（🔨 强化）—— r207b 新加的抽屉项，同样没写底色；且 icon 用了
       `'⚒'`（U+2692），**多数系统字体没有这个字形** ⇒ 渲染成空白 ⇒ "还没有图标"。

   ⚠️⚠️ 这里**改基类**而不是再逐个补：这类问题已经连报两轮（r207b 漏了 enhance、
   r207d 漏了 send），根因是"新增一颗按钮 = 必须记得同时写一条 CSS"，而漏了**不会报错**、
   只在截图里表现为"某颗键发白"。补在基类上之后，新键**默认就是深底**，方向反过来了
   （要浅色得显式写），这类 bug 结构性消失。
   ⚠️ 只给**兜底**（不透明深色），具体键的 `border-color + background` 梯度
   （金/蓝/紫/青…）照旧覆盖它 —— 那些都是 `.tbtn.tb-xxx` 两条类选择器，优先级高于
   `.tbtn`，所以观感一个都不会变。 */
.tbtn {
  background: linear-gradient(180deg, rgba(38, 46, 56, .88), rgba(18, 24, 30, .94));
}
