/* ==========================================================================
   Campfire — 全局样式
   --------------------------------------------------------------------------
   配色：主色 #3a82f3（品牌蓝）｜火光 #ffb347｜深夜 #0b0e14
   移动端优先：全屏不滚动，虚拟摇杆区禁掉浏览器手势与滚动回弹。
   author: DuJiang
   ========================================================================== */

:root {
  --brand: #3a82f3;
  --brand-dim: #2c66c4;
  --fire: #ffb347;
  --ink: #0b0e14;
  --ink-2: #12161f;
  --ink-3: #1a2029;
  --line: rgba(255, 255, 255, 0.10);
  --text: #e6ebf2;
  --text-dim: #93a1b5;
  --danger: #e5484d;
  --ok: #46a758;
  --seat-0: #3a82f3;
  --seat-1: #ff8fab;
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  /* 背包格子边长。
     之前是 clamp(22px, 7.4vw, 34px) —— 12 格挤一行，手机上只有 27~32px，
     远小于可点区域下限，点不准。改成 6 列 × 2 行后格子能放到 44px 以上。 */
  --slot: clamp(44px, 11vw, 50px);
  /* 右侧按钮组的总占位高度。Phase 7 改成 3 列 × 2 行、58px、底部偏移 16px。
     真实值由 layoutHud() 实测写入 --inv-bottom，这里只是首帧的兜底。 */
  --btns-h: calc(58px * 2 + 8px + 16px);
  /* 物品栏是几行格子。由 main.js 按**实际格子数**写入（12 格 = 2 行，
     拾荒者 15 格 / 带搭档加成 16 格 = 3 行）。 */
  --inv-rows: 2;
  /* 顶部那一整块 HUD（左：三个状态徽章；右：时钟 + 房间码）的底边，
     由 layoutHud() 实测写入 —— 它的高度会随内容变（钟、Boss 血条、队友行）。 */
  --hud-top: 150px;
  /* 横幅 / 调试面板从顶部 HUD 下面开始排。 */
  --below-inv: calc(var(--safe-t) + var(--hud-top) + 10px);
  /* 物品栏块：底边离视口底部多远、它自己多高。两个都由 layoutHud() 实测写入。
     toast 和交互提示排在它**上面** —— 它现在在屏幕底部（饥荒的摆法）。 */
  --inv-bottom: 140px;
  --inv-h: 150px;
  /* 大厅的整张背景（渐变 + 底色）。
     写成一个变量是为了能**同时**铺在 :root 与 body 上 —— 见 html.lobby-shell。 */
  --lobby-bg:
    radial-gradient(1100px 520px at 50% -8%, rgba(58, 130, 243, 0.20), transparent 62%),
    radial-gradient(760px 420px at 50% 108%, rgba(255, 179, 71, 0.13), transparent 60%),
    var(--ink);
  /* 大厅版式的四个「瘦身」开关。
     默认值就是各元素本来该有的 display；装不下时按 1→4 一档一档改成 none，
     每关一档重量一次，关到装得下为止 —— 见 index.html 的 fitLobby()。
     为什么用变量而不是直接写 display:none：开关有两个来源
     （JS 按**真实可用高度**逐档关、@media 兜底），
     只让两边都拨开关、不重复写 display，就不会出现「谁压过谁」的扯皮。
     ⚠ 元素的 display 只能通过这些变量控制，别在别处写死 none，否则就放不回来了。 */
  /* 顶部**最小**留白（状态栏 / 灵动岛）。
     正常机器上 env(safe-area-inset-top) 已经把它算进去了，这里就是 0；
     但个别 WebView 会报 0（实测那台 iPhone 16 Pro Max：页面拿到了 894 高，
     比屏幕矮 62，而这个 62 就是刘海）—— 此时页面**完全不知道**上面被挡住，
     内容一旦填满，标题和火苗就跑到状态栏底下了。
     56px 是「灵动岛机型 + 一点余量」的下限；比它小的机器只会觉得上边松一点，
     不会有事。用 max() 参与运算，所以它只在 safe-t < 44px 时才起作用。 */
  --min-top: 0px;
  --fit-1: block;   /* 1 → 角色手感描述 + 搭档加成说明（标签行里已经说过一遍） */
  --fit-2: grid;    /* 2 → 底部功能清单 */
  --fit-3: block;   /* 3 → 火苗 */
  --fit-4: block;   /* 4 → 副标题 */
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ink);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei",
               "Helvetica Neue", Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

body {
  overscroll-behavior: none;
  -webkit-user-select: none;
  user-select: none;
}

/* ---------- 游戏页：锁死滚动与回弹 ---------- */
body.game-page {
  position: fixed;
  inset: 0;
  height: 100dvh;
  overflow: hidden;
  touch-action: none;
}

#stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: var(--ink);
}

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

/* ---------- 通用 ---------- */
.hidden { display: none !important; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(11, 14, 20, 0.66);
  border: 1px solid var(--line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
/* 音效开关：沿用胶囊的视觉语言，但它是个按钮，命中区要够大 */
.pill.icon {
  appearance: none;
  font-family: inherit;
  font-size: 15px;
  line-height: 1;
  padding: 7px 11px;
  min-width: 44px;
  /* 44 是可点区域下限。原来写 36，实测不达标 —— 单手握着手机时点不中 */
  min-height: 44px;
  justify-content: center;
  cursor: pointer;
  color: var(--text);
  transition: transform 0.08s ease, opacity 0.15s ease;
}
.pill.icon:active { transform: scale(0.94); }
.pill.icon.off { opacity: 0.45; }

/* 受击红屏。用 box-shadow inset 而不是背景色：
   inset 阴影只在边缘出现，中间的战斗区域完全不受影响 —— 不挡视线。 */
#hurtFx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 24;
  box-shadow: inset 0 0 64px 22px rgba(229, 72, 77, 0);
  transition: box-shadow 0.30s ease-out;
}
#hurtFx.on {
  box-shadow: inset 0 0 64px 22px rgba(229, 72, 77, 0.55);
  transition: none;
}

/* 木甲耐久行（在血条下面） */
#armorLine {
  margin-top: 5px;
  font-size: 11px;
  color: #9fd0ff;
  text-shadow: 0 1px 3px #000;
}
#armorLine[data-low="1"] { color: #ff9ea1; }

/* ================================================================
   Phase 3：理智 / 季节 / 结算 / 成就
   ================================================================ */

/* 理智条。紫色 —— 和生命红、饱食黄区分开，一眼能认出是哪一条 */
#sanityBar { background: linear-gradient(90deg, #7a5ad6, #b58cff); }
#sanityBar[data-low="1"] { background: linear-gradient(90deg, #5a3fb0, #8a6ce0); }

/* 季节胶囊 */
#hudSeason {
  font-size: 12px;
  line-height: 1;
  filter: drop-shadow(0 1px 2px #000);
}

/* 低理智的画面反馈。
   用 inset box-shadow 而不是整屏遮罩：只在边缘出现，
   中间的战斗区域完全不受影响 —— 和受击红屏同一个理由。
   will-change 是为了让这个一直存在的元素不要每帧重绘。 */
#sanFx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 23;
  box-shadow: inset 0 0 0 0 rgba(92, 46, 138, 0);
  will-change: box-shadow;
}
#sanFx[data-tier="uneasy"] {
  box-shadow: inset 0 0 90px 14px rgba(92, 46, 138, 0.20);
}
#sanFx[data-tier="low"] {
  box-shadow: inset 0 0 120px 26px rgba(92, 46, 138, 0.42);
  animation: sanBreathe 4.2s ease-in-out infinite;
}
#sanFx[data-tier="critical"] {
  box-shadow: inset 0 0 150px 44px rgba(120, 52, 170, 0.66);
  animation: sanBreathe 1.9s ease-in-out infinite;
}
#sanFx[data-tier="broken"] {
  box-shadow: inset 0 0 170px 62px rgba(150, 40, 190, 0.78);
  animation: sanBreathe 1.1s ease-in-out infinite;
}
@keyframes sanBreathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

/* 天亮结算。不是模态弹窗：6 秒后自动淡出，不强制点击 ——
   每天弹一次的东西，如果必须点掉就会变成负担。 */
#daySummary {
  position: absolute;
  left: 50%;
  /* 排在 dayBox 之下（底边 127），否则会盖住血条与理智条。
     它是 6 秒就自动消失的临时卡片，压住背包栏是可以接受的。 */
  top: calc(var(--safe-t) + 140px);
  transform: translateX(-50%);
  z-index: 26;
  width: min(360px, calc(100vw - 28px));
  pointer-events: none;
  animation: sumIn 0.32s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
#daySummary.out { opacity: 0; transform: translateX(-50%) translateY(-10px); transition: all 0.5s ease; }
.sum-card {
  pointer-events: auto;
  padding: 14px 16px 12px;
  border-radius: 16px;
  background: rgba(13, 17, 24, 0.94);
  border: 1px solid var(--line);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
}
.sum-head { font-size: 15px; font-weight: 800; letter-spacing: 0.02em; }
.sum-season { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.sum-rows { margin: 11px 0 9px; display: grid; gap: 5px; }
.sum-row { display: flex; justify-content: space-between; font-size: 13px; }
.sum-row .k { color: var(--text-dim); }
.sum-row .v { font-variant-numeric: tabular-nums; font-weight: 600; }
.sum-total {
  padding-top: 9px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.6;
}
.btn.sm { padding: 7px 14px; font-size: 13px; margin-top: 10px; }
@keyframes sumIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-16px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* 成就提示。
   位置放在**下方通知区**（toast 上方），不是顶部：
   顶部已经被 dayBox（左）、房间码/网络/按钮（右）、背包栏（中）占满了，
   放上去必然压在某个东西上面 —— 实测就是这么被 layout 断言抓到的。 */
#achToast {
  position: absolute;
  left: 50%;
  bottom: calc(var(--safe-b) + var(--btns-h) + 54px);
  transform: translateX(-50%);
  z-index: 27;
  display: grid;
  gap: 7px;
  pointer-events: none;
  width: max-content;
  max-width: calc(100vw - 24px);
}
.ach-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 15px 9px 12px;
  border-radius: 13px;
  background: linear-gradient(135deg, rgba(58, 130, 243, 0.96), rgba(122, 90, 214, 0.94));
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  animation: achIn 0.34s cubic-bezier(0.2, 0.9, 0.3, 1.3);
}
.ach-item.out { opacity: 0; transform: translateY(-14px); transition: all 0.45s ease; }
.ach-item .ico { font-size: 22px; line-height: 1; }
.ach-item .txt { min-width: 0; }
.ach-item .t1 { font-size: 10px; opacity: 0.8; letter-spacing: 0.14em; }
.ach-item .t2 { font-size: 14px; font-weight: 800; }
.ach-item .t3 { font-size: 11px; opacity: 0.85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@keyframes achIn {
  from { opacity: 0; transform: translateY(-18px) scale(0.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* 统计面板 */
#statsBody { padding: 4px 16px 20px; overflow-y: auto; }
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
.stat-cell {
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.045);
  border: 1px solid var(--line);
}
.stat-cell .k { font-size: 11px; color: var(--text-dim); }
.stat-cell .v { font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums; }
.stat-sub { font-size: 12px; color: var(--text-dim); margin: 14px 0 8px; letter-spacing: 0.06em; }
.ach-grid { display: grid; gap: 6px; }
.ach-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 11px;
  border-radius: 11px;
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--line);
}
.ach-row.locked { opacity: 0.42; }
.ach-row .ico { font-size: 18px; filter: grayscale(1); }
.ach-row.done .ico { filter: none; }
.ach-row .nm { font-size: 13px; font-weight: 600; }
.ach-row .ds { font-size: 11px; color: var(--text-dim); }
.ach-row .tick { margin-left: auto; font-size: 13px; color: var(--ok); }

/* 邀请二维码 */
#qrWrap {
  display: flex;
  justify-content: center;
  padding: 10px;
  background: #fff;
  border-radius: 12px;
  margin: 0 auto 10px;
  width: fit-content;
  max-width: 100%;
}
#qrCanvas {
  display: block;
  background: #fff;
  /* 二维码是整数倍缩放的方块，必须关掉平滑，否则模块边缘发虚扫不出来 */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.btn {
  appearance: none;
  border: 0;
  border-radius: 12px;
  padding: 14px 20px;
  font-size: 16px;
  font-weight: 600;
  font-family: inherit;
  color: #fff;
  background: var(--brand);
  cursor: pointer;
  transition: transform 0.08s ease, background 0.15s ease;
}
.btn:active { transform: scale(0.97); background: var(--brand-dim); }
.btn.ghost {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text);
}
.btn.ghost:active { background: rgba(255,255,255,0.06); }
/* 纯文字按钮：用于「先一个人试玩」这类次要入口，不抢主按钮的注意力。
   min-height 44px 是必须的 —— 它也是要用手点的。 */
.btn.link {
  background: transparent;
  min-height: 44px;
  padding: 12px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-dim);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(255, 255, 255, 0.22);
}
.btn.link:active { background: transparent; color: var(--text); }
.btn:disabled { opacity: 0.45; pointer-events: none; }

input[type="text"] {
  width: 100%;
  padding: 14px 16px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-size: 16px;          /* 16px 以下 iOS 会自动放大页面 */
  font-family: inherit;
  outline: none;
  -webkit-user-select: text;
  user-select: text;
}
input[type="text"]:focus { border-color: var(--brand); }
input.code-input {
  letter-spacing: 0.5em;
  text-align: center;
  font-size: 26px;
  font-weight: 700;
  padding-left: calc(16px + 0.5em);
}

/* ==========================================================================
   游戏内 HUD
   ========================================================================== */

#hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 10;
}
/*
 * 只有这两个子元素需要交互，其余（天数、提示、横幅、toast）一律不吃点击。
 *
 * ⚠ 不要改回 `#hud > * { pointer-events: auto }`：
 *   它的优先级是 (1,0,0)，而 `.banner` 这类 class 选择器只有 (0,1,0)，
 *   **永远压不过它** —— 于是「给提示条加 pointer-events:none」会静默失效。
 *   实测踩过：横幅在摇杆附近弹出时会吃掉摇杆的触摸，表现为「摇杆莫名失灵」。
 *   白名单写法下，新加的提示元素默认就是不吃点击的。
 */
#hud > #topRight,
#hud > #invBar {
  pointer-events: auto;
}

/* 左上：天数 / 阶段 / 生存条 */
#dayBox {
  position: absolute;
  top: calc(var(--safe-t) + 8px);
  left: calc(var(--safe-l) + 10px);
  /* 不要写死宽度：倒计时文案一变长，「第 1 天」就会被挤成两行。
     宽度交给内容决定，只锁一个下限。 */
  width: auto;
  min-width: 148px;
}
#dayTop {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
  flex-wrap: wrap;
}
/* 天数和阶段永远不折行；倒计时单独占一行（flex-basis 100%）。
   之前三者挤一行，倒计时一长就把「第 1 天」挤成了「第」+「天」。 */
#hudDay { font-size: 15px; font-weight: 700; text-shadow: 0 1px 3px #000; white-space: nowrap; }
#hudPhase { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
#hudTime {
  flex-basis: 100%;
  margin-left: 0;
  font-size: 10px;
  color: var(--text-dim);
  white-space: nowrap;
}

#dayBarWrap {
  height: 5px;
  width: 148px;
  border-radius: 99px;
  background: rgba(0, 0, 0, 0.45);
  overflow: hidden;
}
#dayBar {
  height: 100%;
  width: 0%;
  border-radius: 99px;
  background: var(--fire);
  transition: width 0.18s linear, background 0.5s ease;
}
#dayBarWrap[data-phase="dusk"] #dayBar { background: #c96a3a; }
#dayBarWrap[data-phase="night"] #dayBar { background: #4a6ea8; }

/* ---------------------------------------------------------------
   三个状态徽章（Phase 6 换成饥荒式圆环）
   ---------------------------------------------------------------
   圆环比三条细横条好读：一眼看的是「还剩多少弧度」，而不是去比对两条长度。
   用 SVG 的 stroke-dasharray 画，不用 conic-gradient ——
   后者在低端安卓上掉帧，而这里是每帧都可能刷新的地方。
   圆周 = 2πr = 2π×16 ≈ 100.53，改 r 的话这个数要跟着改。
   --------------------------------------------------------------- */
/* ⚠ 这里必须是 **#vitals**（id），不是 .vitals —— HTML 里写的是 id。
   写成 class 的后果不是「没生效」，而是**静默退回 display:block**：
   三个 40px 的圆环竖着排成 120px，把 dayBox 顶到 194 高，
   然后压在 196 处的背包上。实测就是这么发现的。 */
#vitals { margin-top: 9px; display: flex; gap: 7px; }
.vital {
  position: relative;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
}
.vital .ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);        /* 从 12 点开始掉 */
}
.vital .ring .vt { fill: rgba(12, 9, 6, 0.74); stroke: rgba(0, 0, 0, 0.6); stroke-width: 6; }
.vital .ring .vf {
  fill: none;
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 100.53;
  stroke-dashoffset: calc(100.53 * (1 - var(--fill, 1)));
  transition: stroke-dashoffset 0.22s linear, stroke 0.3s ease;
}
.vital .vi {
  position: relative;
  font-size: 15px;
  line-height: 1;
  filter: drop-shadow(0 1px 2px #000);
}
.vital .vn {
  position: absolute;
  bottom: -2px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 9px;
  font-weight: 700;
  color: var(--parch, #e8d5a9);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 2px #000, 0 0 3px #000;
}
#vSanity .vf { stroke: #9a7bd6; }
#vHunger .vf { stroke: #e0a33f; }
#vHp .vf { stroke: #e5484d; }
/* 见底时呼吸：不看数字也能察觉 */
.vital.low .vf { animation: ringPulse 0.9s ease-in-out infinite; }
@keyframes ringPulse { 50% { filter: brightness(2.1); } }

/* 右上：房间码 / 网络 */
#topRight {
  position: absolute;
  top: calc(var(--safe-t) + 8px);
  right: calc(var(--safe-r) + 10px);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
#codePill {
  cursor: pointer;
  border-color: rgba(58, 130, 243, 0.5);
  background: rgba(58, 130, 243, 0.2);
}
#codePill:active { background: rgba(58, 130, 243, 0.34); }
#roomCode { font-weight: 700; letter-spacing: 0.16em; }
#codePill .hint { font-size: 11px; color: var(--text-dim); }

/* 顶部中间：背包 + 装备槽（Phase 6 合成同一块木牌）。
   6 列 × 2~3 行 —— 12~18 格。放一行的话每格只有 30px 上下，手机上点不准；
   两行之后每格能到 44px 以上（苹果的可点区域下限）。
   纵向位置：dayBox 底边约 120，所以背包从 196 起；
   它下面的 Banner / 调试面板用 --below-inv 统一排，避免各自手调 top。 */
#invBar {
  position: absolute;
  /* --inv-top 由 layoutHud() 按**dayBox 的实测底边**写进来。
     原来写死 196px，而 dayBox 的高度会随内容变（状态圆环、Boss 血条、
     预警行），加一样东西就会被压住 —— 实测踩过两次。 */
  top: calc(var(--safe-t) + var(--inv-top, 196px));
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: 3px;
  padding: 4px 5px 5px;
  border-radius: 10px;
  /* 木牌：饥荒的界面几乎都是「深色木头 + 手绘描边」。
     用两层渐变假装木纹，比纯色多一点点温度，且不需要贴图。 */
  background: linear-gradient(180deg, rgba(76, 60, 43, 0.94), rgba(41, 31, 23, 0.96));
  border: 2px solid #14100c;
  box-shadow: inset 0 1px 0 rgba(255, 214, 150, 0.14), 0 2px 12px rgba(0, 0, 0, 0.55);
}
.inv-grid {
  display: grid;
  grid-template-columns: repeat(6, auto);
  gap: 2px;
}
/* 装备槽单独一行，上面压一条更亮的分隔线 ——
   「手/身/头」和「背包里」是两种东西，视觉上必须能分开 */
.eq-row {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 4px;
  padding-bottom: 4px;
  margin-bottom: 1px;
  border-bottom: 1px solid rgba(255, 214, 150, 0.16);
}
.slot {
  width: var(--slot);
  height: var(--slot);
  border-radius: 7px;
  border: 2px solid #14100c;
  background: radial-gradient(circle at 50% 32%, rgba(60, 47, 34, 0.92), rgba(24, 18, 13, 0.96));
  box-shadow: inset 0 1px 0 rgba(255, 224, 170, 0.10);
  position: relative;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  color: var(--text);
  cursor: pointer;
  transition: transform 0.08s ease, border-color 0.16s ease;
}
.slot:active { transform: scale(0.9); }
.slot.filled { border-color: #6b5537; }
/* 装备槽：空的时候留一个很淡的水印，告诉玩家「这里可以放东西」。
   没有它的话，三个空槽看起来像装饰。 */
.slot.eq { border-color: #3b2d20; }
.slot.eq::before {
  content: attr(data-watermark);
  position: absolute;
  font-size: calc(var(--slot) * 0.46);
  /* 不压灰度、不压太暗：空槽的水印要「看得出是什么、但明显是空的」。
     实测 0.22 灰度版看起来像一块脏印子。 */
  opacity: 0.34;
}
.slot.eq.filled::before { display: none; }
.slot.food { border-color: rgba(120, 180, 110, 0.6); }
.slot.place { border-color: rgba(255, 179, 71, 0.55); }
.slot.torch { border-color: rgba(255, 140, 60, 0.65); }
.slot.equipped { box-shadow: inset 0 0 0 2px rgba(255, 196, 92, 0.55); }
.slot .ic { font-size: calc(var(--slot) * 0.56); line-height: 1; }
.slot .n {
  position: absolute;
  right: 1px;
  bottom: 0;
  font-size: 9px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 2px #000;
}

/* 顶部横幅（排在背包块下面，用 --below-inv 跟着背包走） */
.banner {
  position: absolute;
  top: var(--below-inv);
  left: 50%;
  transform: translateX(-50%);
  padding: 7px 15px;
  border-radius: 999px;
  background: rgba(11, 14, 20, 0.82);
  border: 1px solid var(--line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-size: 13px;
  white-space: nowrap;
  max-width: 88vw;
  overflow: hidden;
  text-overflow: ellipsis;
  /* 纯提示，绝不能吃掉点击：否则在摇杆区附近弹出的横幅会让摇杆「莫名失灵」 */
  pointer-events: none;
}
.banner.warn { border-color: rgba(255, 179, 71, 0.5); color: var(--fire); }
.banner.bad { border-color: rgba(229, 72, 77, 0.55); color: #ff9ea1; }

/* 交互提示 */
#interactHint {
  position: absolute;
  left: 50%;
  /* 排在按钮组上方：按钮占满了右下角，贴在底部的提示会被压住 */
  bottom: calc(var(--safe-b) + min(var(--btns-h), 190px) - 10px);
  transform: translateX(-50%);
  padding: 7px 14px;
  border-radius: 999px;
  background: rgba(58, 130, 243, 0.88);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
}
/* 缺工具时换个颜色，一眼就知道现在按了也没用 */
#interactHint[data-locked="1"] { background: rgba(90, 98, 112, 0.9); }

/* 左下：队友状态 */
#peerLine {
  position: absolute;
  left: calc(var(--safe-l) + 12px);
  bottom: calc(var(--safe-b) + 150px);
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-dim);
  text-shadow: 0 1px 3px #000;
}
#peerLine .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--seat-1);
  box-shadow: 0 0 10px var(--seat-1);
}
#peerLine.alert { color: #ff9ea1; }
#peerLine.alert .dot {
  background: var(--danger);
  box-shadow: 0 0 12px var(--danger);
  animation: pulseRed 0.7s ease-in-out infinite;
}
#peerName { color: #ffc3d4; font-weight: 600; }

/* 调试面板 */
#debug {
  position: absolute;
  left: calc(var(--safe-l) + 10px);
  top: calc(var(--below-inv) + 46px);
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(11, 14, 20, 0.8);
  border: 1px solid var(--line);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  min-width: 186px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.dbg-row { display: flex; justify-content: space-between; gap: 12px; }
.dbg-row span { color: var(--text-dim); }
.dbg-row b { font-weight: 600; }

/* 提示气泡 */
#toast {
  position: absolute;
  left: 50%;
  /* --toast-off 由 layoutHud() 按**按钮组的实测高度**写进来：
     写死 --btns-h 的话，按钮行数一变（加了技能键）、或者矮屏档位改了按钮尺寸，
     toast 就会压到按钮上 —— 实测在 375x667 上重叠了 12px。 */
  bottom: calc(var(--safe-b) + var(--toast-off, var(--btns-h)));
  transform: translate(-50%, 12px);
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(58, 130, 243, 0.94);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  max-width: 86vw;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
#toast.on { opacity: 1; transform: translate(-50%, 0); }

/* ==========================================================================
   触屏控制
   ========================================================================== */

#joyZone {
  position: absolute;
  left: 0;
  bottom: 0;
  /* 不能只按比例：在 360px 宽的安卓小屏上，58% = 209px 会伸进右侧按钮组
     （按钮组左边缘 = 360 - 16 - 142 = 202），玩家按按钮边缘会被摇杆抢走。
     所以取「58% 和 视口宽 - 按钮组占位 的较小值」。 */
  width: min(58%, calc(100% - 172px));
  /*
   * 高度：**从物品栏的上沿往上留 34px 开始**，跟着 --inv-top-off 走。
   *
   * 这里原来写的是 `100% - var(--below-inv) - 34px`，而 --below-inv 量的是
   * **顶部 HUD**（dayBox 的底边）—— 注释说的明明是「背包块下沿」，代码量的却是
   * 顶部，两回事。结果摇杆区一路盖到屏幕底部，**物品栏整个落在它里面**：
   * 点背包格子会先把摇杆的基点挪过去，那一下点击就没了。
   * （摇杆区的 z-index 是 12，压在 #hud 之上，所以不是「谁在上层」的问题。）
   *
   * --inv-top-off 由 syncInvBox() 实测写入（物品栏上沿离视口底多远），
   * 展开物品栏时它会跟着变大 → 摇杆区自动让开。
   */
  height: calc(var(--inv-top-off, 300px) + 34px);
  z-index: 12;
  touch-action: none;
}

#joyBase {
  position: fixed;
  left: -999px;
  top: -999px;
  width: 120px;
  height: 120px;
  margin: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.06);
  transform: translate(-50%, -50%);
  opacity: 0;
  transition: opacity 0.12s ease;
  pointer-events: none;
  z-index: 13;
}
#joyBase.on { opacity: 1; }
#joyKnob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: rgba(58, 130, 243, 0.85);
  box-shadow: 0 0 18px rgba(58, 130, 243, 0.55);
  transform: translate(-50%, -50%);
}

/* 动作按钮：2×2 网格，横屏也不会溢出 */
#buttons {
  position: absolute;
  right: calc(var(--safe-r) + 16px);
  bottom: calc(var(--safe-b) + 22px);
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 10px;
  z-index: 14;
}

.act-btn {
  width: 66px;
  height: 66px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.22);
  background: rgba(18, 22, 31, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--text);
  font-size: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 0;
  font-family: inherit;
  touch-action: none;
  user-select: none;
  transition: transform 0.08s ease, background 0.12s ease;
}
.act-btn small { font-size: 10px; color: var(--text-dim); line-height: 1.2; }
.act-btn.pressed { transform: scale(0.92); background: rgba(58, 130, 243, 0.55); }
.act-btn.primary {
  border-color: rgba(58, 130, 243, 0.75);
  background: rgba(58, 130, 243, 0.34);
}
.act-btn.primary.pressed { background: rgba(58, 130, 243, 0.7); }
/* 攻击：用暖红让它从一排按钮里第一眼就被找到。
   战斗时是肌肉记忆在按，不能让它和「挥手」长得一样。 */
.act-btn.danger {
  border-color: rgba(229, 72, 77, 0.72);
  background: rgba(229, 72, 77, 0.28);
}
.act-btn.danger.pressed { background: rgba(229, 72, 77, 0.62); }

/* 桌面端提示 */
#kbdHint {
  position: absolute;
  left: 50%;
  bottom: calc(var(--safe-b) + 8px);
  transform: translateX(-50%);
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 1px 3px #000;
}
/* 键盘提示只在真的用键盘时有意义。
   窄屏上它会被右下角的按钮组压住（实测重叠 3px），而且窄屏用户用的是触屏按钮，
   那上面已经写着「交互 / 火把 / 合成」了 —— 这条提示纯属噪音。 */
@media (max-width: 720px) {
  #kbdHint { display: none; }
}

/* ==========================================================================
   合成面板（底部抽屉）
   ========================================================================== */

.sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  max-height: 68%;
  display: flex;
  flex-direction: column;
  background: rgba(13, 17, 24, 0.96);
  border-top: 1px solid var(--line);
  border-radius: 18px 18px 0 0;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  padding-bottom: var(--safe-b);
  animation: sheetUp 0.18s ease;
}
@keyframes sheetUp { from { transform: translateY(16px); opacity: 0; } }

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px 8px;
  font-size: 15px;
  font-weight: 700;
}
.sheet-close {
  border: 0;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
}

.craft-list {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 12px 14px;
  display: grid;
  gap: 8px;
}
.craft-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 11px 13px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: transform 0.08s ease, background 0.15s ease;
}
.craft-row:active { transform: scale(0.985); }
.craft-row.ok { border-color: rgba(58, 130, 243, 0.55); background: rgba(58, 130, 243, 0.12); }
.craft-row .ci { font-size: 24px; }
.craft-row .cbody { flex: 1; display: grid; gap: 1px; }
.craft-row .cbody b { font-size: 14px; }
.craft-row .cbody em { font-style: normal; font-size: 11px; color: var(--text-dim); }
.craft-row .cneed { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.craft-row.ok .cneed { color: #8fd39a; }
.craft-row .cgo {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-dim);
}
.craft-row.ok .cgo { background: var(--brand); color: #fff; }

/* ==========================================================================
   覆盖层（连接中 / 等待队友 / 倒下 / 错误）
   ========================================================================== */

.overlay {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: radial-gradient(circle at 50% 40%, #16202f 0%, #0b0e14 70%);
}
.overlay .card {
  width: 100%;
  max-width: 340px;
  text-align: center;
}

/* 「可以穿透」的覆盖层：房主在等队友时仍能走动看地图 */
.overlay.pass {
  background: none;
  pointer-events: none;
  align-items: flex-end;
  padding-bottom: calc(var(--safe-b) + 26px);
}
.overlay.pass .card {
  pointer-events: auto;
  background: rgba(18, 22, 31, 0.9);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 18px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  max-width: 320px;
}
.overlay.pass .flame { font-size: 30px; margin-bottom: 4px; }

/* 倒下遮罩：半透明，能看见队友跑过来 */
#downed { background: rgba(11, 14, 20, 0.72); pointer-events: none; }
#downed .card { pointer-events: auto; }

.spinner {
  width: 34px;
  height: 34px;
  margin: 0 auto 16px;
  border: 3px solid rgba(255, 255, 255, 0.14);
  border-top-color: var(--brand);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.flame {
  font-size: 46px;
  line-height: 1;
  margin-bottom: 10px;
  filter: drop-shadow(0 0 18px rgba(255, 179, 71, 0.55));
}

.big-code {
  font-size: 38px;
  font-weight: 800;
  letter-spacing: 0.22em;
  color: var(--brand);
  margin: 4px 0 2px;
  padding-left: 0.22em;
  font-variant-numeric: tabular-nums;
}

.err {
  margin-top: 14px;
  padding: 10px 14px;
  border-radius: 10px;
  background: rgba(229, 72, 77, 0.14);
  border: 1px solid rgba(229, 72, 77, 0.4);
  color: #ffb4b6;
  font-size: 13px;
}

.muted { color: var(--text-dim); font-size: 13px; }

/* ==========================================================================
   大厅
   ========================================================================== */

/* 根元素的背景会**传播到整个视口画布**，是唯一一张保证铺满屏幕的背景。
   大厅的渐变同时铺在 html 上：即使 body 因为某种原因比屏幕矮
   （老浏览器不认 dvh、或者高度算出来差几个像素），
   屏幕底部也不会露出一条颜色不一样的空白带。

   ⚠ 这两条不是可选项：

   1) background-attachment: fixed
      根背景传播到画布之后，**图片（渐变）仍然按根元素自己的盒子定位**，
      而画布可能比这个盒子更高 —— 实测 iPhone 16 Pro Max 的 WebView 里
      100dvh = 894，画布 956，差的 62 正好是顶部刘海。fixed 把定位区域
      从「根盒子」换成「视口」，渐变于是铺满整张画布，暖色的火光也就
      真的落在屏幕底部，而不是停在半空中。

   2) background-repeat: no-repeat
      WebKit 不会给画布重复铺根背景（盒子之外留 background-color），
      Blink 会**重复**（盒子之外是渐变的第二份，从顶部那一格重新开始）——
      两边都错，只是错得不一样。显式关掉重复，两种引擎的行为才一致：
      盒子之内一次渐变，盒子之外一律是基色。 */
html.lobby-shell {
  background: var(--lobby-bg);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/*
 * 大厅：整页铺满，内容在**安全区之内**居中。
 *
 * 这里有两个数字是从「手机上看着不对」倒推出来的，别随手改：
 *
 * 1) padding 必须把 --safe-t 也加进去。
 *    以前只在底部加了 --safe-b，于是刘海屏上整块内容往上偏了恰好一个
 *    safe-t（实测 iPhone 47px / 灵动岛 59px）：顶部挤到状态栏底下，
 *    底部多出一整条空白 —— 「App 打开时底部空白」的根因就是这一行。
 *    实测量到的上下留白差：390x844 = 47px，480x960 = 47px，430x932 的内容
 *    顶端甚至是 -10px（直接顶进灵动岛）。
 *
 * 2) 上下各 12px 的兜底边距就够，别写 24。
 *    真正把内容从边缘撑开的是安全区本身，这 12px 只是给「无安全区」
 *    的机器留一口气。写 24 会让 430x932（可用 839）多出 9px，
 *    整页就得滚动了 —— 而「大厅一屏放得下」是这个页面从第一版起的硬规矩。
 *    注意：这 12px 对**居中的留白**没有影响（留白 = 可用高度 - 内容高度的一半），
 *    它只决定「内容几乎填满时会不会贴边 / 溢出」。
 *
 * 3) --vp-gap 是**第三层**：上面两条都修不好的那种机器（100dvh 比画布矮）。
 *    页面按 894 排版、画布 956，内容在 894 里居中 → 屏幕上「上留白 120 /
 *    下留白 182」，看起来还是底部空一块。把 --vp-gap（差额）加到**上**内边距，
 *    居中点就整体下移 gap/2，正好落在真正可见区域的中心。
 *    值由 index.html 的 syncViewportGap() 实测写入；普通浏览器里恒为 0px，
 *    所以这一条在任何设备上都不会有副作用。
 */
body.lobby-page {
  min-height: 100vh;          /* 老 Safari 不认 dvh：先给 vh 兜底 */
  min-height: 100dvh;         /* 现代浏览器：真实可见高度 */
  display: flex;
  align-items: center;
  justify-content: center;
  padding:
    max(calc(12px + var(--safe-t) + var(--vp-gap, 0px)), var(--min-top, 0px))
    20px calc(12px + var(--safe-b));
  background: var(--lobby-bg);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/*
 * --min-top 只在「手机」且「够高」时给值。
 *
 * 为什么带 (pointer: coarse), (max-width: 900px) 两条：
 * 无头浏览器**不模拟指针类型**（实测 430x860 上报的仍是 fine），
 * 只写 coarse 的话巡检永远走不到这一支 —— 一条测不到的规则等于没有。
 * max-width 是它在这种环境下的等价替身；真机上 coarse 会命中。
 * 桌面（1280 宽、细指针）两条都不命中，版式一行不动。
 *
 * 为什么还要 min-height: 700px：
 * 这 56px 是从**本来就紧**的竖向空间里抠出来的（+44px）。667 / 640 高的机器
 * 四个瘦身开关已经被 @media 全关光了，没有档位可退 —— 实测直接溢出 39px。
 * 而真正需要这条兜底的（灵动岛 + 误报 safe-t）都是 812 以上的机型，
 * 短屏机器要么没刘海，要么 env() 报得准。所以这里按高度再设一道门。
 */
@media (min-height: 700px) and (pointer: coarse),
       (min-height: 700px) and (max-width: 900px) {
  :root { --min-top: 56px; }
}

.lobby { width: 100%; max-width: 420px; }

.brand { text-align: center; margin-bottom: 26px; }
.brand h1 { margin: 0; font-size: 32px; letter-spacing: 0.04em; font-weight: 800; }
/* ⚠ 下面这四个的 display 都用 --fit-* 开关控制（见 :root 的说明）。
   想「显示」就把开关留空，想「收起」就设成 none —— 别在这里写死。 */
.brand .sub { margin-top: 6px; font-size: 13px; color: var(--text-dim); display: var(--fit-4, block); }
.brand .fire {
  font-size: 42px;
  display: var(--fit-3, block);
  margin-bottom: 8px;
  filter: drop-shadow(0 0 22px rgba(255, 179, 71, 0.6));
}

.panel {
  background: rgba(18, 22, 31, 0.82);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 20px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.field { margin-bottom: 16px; }
.field label {
  display: block;
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 7px;
  letter-spacing: 0.04em;
}

.row { display: flex; gap: 10px; }
.row > * { flex: 1; }

.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0;
  color: var(--text-dim);
  font-size: 12px;
}
.divider::before, .divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}

.feature-list {
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
  font-size: 13px;
  color: var(--text-dim);
  display: var(--fit-2, grid);
  gap: 8px;
}
.feature-list li { display: flex; gap: 9px; align-items: flex-start; }
.feature-list b { color: var(--text); font-weight: 600; }

/* 房间码 + 「加入」同一行：它们本来就是同一个动作，分两行既费高度又不直观 */
.join-row { display: flex; gap: 8px; align-items: stretch; }
.join-row .code-input { flex: 1; min-width: 0; }
.join-row .btn { flex: none; padding-left: 18px; padding-right: 18px; }

/* 面板里的页脚（并进面板之后的样子）。
   行内链接也要够大：实测这一条在手机上只有 48x16，很难点中。
   inline-block + padding 撑起命中区，用负 margin 把多出来的高度「还」回去 ——
   命中区是 44px 高，但它在布局里只占一行文字的高度。 */
.panel-foot {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  color: var(--text-dim);
}
.panel-foot a {
  color: var(--brand);
  text-decoration: none;
  display: inline-block;
  flex: none;
  padding: 14px 10px;
  margin: -14px -2px;
}

/* 矮屏微调。
   大厅整页必须**一屏放得下**（手机上不该需要滚动才能看到「创建新世界」）。
   Phase 5 往面板里加了角色选择器与世界列表，所以断点上移了一整档，
   并且把「回流玩家」当成默认形态来调 —— 有世界列表的页面比空页面
   高出 90 多像素（实测：430x860 上 888 vs 795）。

   ⚠ 改这里之后一定要跑 `node tools/inspect_ui.mjs`：
     它会**先注入一个世界**再量，量的就是最难的那个形态，
     并且会带上安全区（刘海/灵动岛）再量一遍留白。
     ⚠ 下面只拨 --fit-* 开关，不要直接写 display: none —— 见 :root 的说明。 */

/* 兜底档：JS 没跑起来时，光凭屏高也得先把最不值钱的两行收掉。
   JS 正常时 fitLobby() 会更精确 —— 它按「扣掉安全区之后的真实高度」
   一档一档地关，而媒体查询读不到 env()，只能按屏高猜。

   ⚠ 档位为什么是「一个高度对应一个开关」而不是「一档全关」：
   实测 iPhone 16 Pro Max 的 WebView 里 100dvh 只有 894（屏幕 956）。
   原来 ≤900 那一档把四个开关**一起**关了，内容于是从 780 掉到 653，
   在一个 894 高的盒子里上下各空 120px —— 大厅看起来「空了一半」，
   这正是「底部一大片空白」的第二个来源（第一个是根盒子比画布矮，
   见 --lobby-bg 的第一层）。现在每一档只收一样东西：
   先压排版（字号/间距），再依次收掉功能清单 → 火苗 → 副标题，
   每收一样都要掉 20~60px，足够把任何高度挤进一屏，但不必一次全扔。 */
@media (max-height: 1000px) {
  :root { --fit-1: none; }
}
/* ≤920：只压排版，四个板块**一个都不收** */
@media (max-height: 920px) {
  .brand { margin-bottom: 16px; }
  .brand h1 { font-size: 26px; }
  .panel { padding: 17px; }
  .field { margin-bottom: 13px; }
  .divider { margin: 13px 0; }
  /* 房间码输入框原来 26px 字 + 14px 内边距 = 65px 高，
     是页面里最高的单个元素。缩到 24px 仍然远远大于 44px 的点击下限。 */
  input.code-input { font-size: 24px; padding-top: 12px; padding-bottom: 12px; }
}
/* 应用名在桌面图标和标题栏里都已经有了，在大厅里它是最不值钱的那一块 */
@media (max-height: 840px) { :root { --fit-2: none; } }
@media (max-height: 800px) { :root { --fit-3: none; } }
@media (max-height: 760px) {
  :root { --fit-4: none; }
  .panel { padding: 14px 15px; }
  .field { margin-bottom: 10px; }
  .field label { margin-bottom: 5px; }
  .brand { margin-bottom: 8px; }
  .brand h1 { font-size: 20px; }
  /*
   * 加成明细压到**一行**。
   * 拾荒者那五条加起来约 340px，在 335px 宽的面板里正好多出一点点，
   * 会折成两行 —— 而这两行恰好是 667 高的机器上最缺的那 15 像素。
   * 字号从 11 降到 10.5、间距从 10 收到 7，就能塞进一行。
   */
  .cd-perks { margin-top: 4px; gap: 2px 7px; }
  .cd-p { font-size: 10.5px; }
  .panel-foot { margin-top: 7px; padding-top: 6px; }
  .divider { margin: 9px 0; }
}
@media (max-height: 660px) {
  .panel { padding: 12px 14px; }
  .field { margin-bottom: 8px; }
  input.code-input { font-size: 20px; }
  /* 注意保持 >= 44px：这里是唯一的主行动按钮，点不准比难看严重得多 */
  .btn { padding: 12px 16px; }
  .btn.link { padding: 8px 12px; }
  .cd-perks { gap: 2px 6px; }
  .panel-foot { margin-top: 6px; padding-top: 5px; }
}
/* 矮屏：按钮从 2×2 变成 2×3（加了「攻击」），高度从 142 涨到 218。
   在 640px 高的屏幕上不缩一下会占到三分之一屏幕，所以分两档收。 */
@media (max-height: 720px) {
  :root { --btns-h: calc(58px * 3 + 8px * 2 + 22px + 12px); }
  .act-btn { width: 58px; height: 58px; font-size: 19px; }
  #buttons { gap: 8px; }
}
@media (max-height: 560px) {
  :root { --btns-h: calc(50px * 3 + 7px * 2 + 22px + 10px); }
  .act-btn { width: 50px; height: 50px; font-size: 17px; }
  #buttons { gap: 7px; }
  #interactHint { bottom: calc(var(--safe-b) + 96px); }
  #peerLine { bottom: calc(var(--safe-b) + 120px); }
  #toast { bottom: calc(var(--safe-b) + 150px); }
}

/* ==========================================================================
   Phase 5 — 角色专精
   ========================================================================== */

/* 大厅里的角色选择：一行芯片 + 选中那个的明细。
   为什么不画成两张长卡片：大厅整页必须一屏放得下（手机不滚动），
   两张各带五条明细的卡片要多占 100 多像素，直接把它顶出屏幕。
   芯片只占一行，明细只渲染当前选中的那一个 —— 信息一条没少。 */
/* 角色芯片：**三列**，不是两列。
   角色从 2 个扩到 6 个之后，两列会变成三行（3×44 + 间距 ≈ 144px），
   而大厅的硬规矩是「一屏放得下」。三列刚好两行（94px），省下 50px ——
   正好是小屏溢出掉的那几十像素。 */
.char-pick { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.char-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 44px;
  padding: 0 6px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  transition: border-color 0.16s, background 0.16s, transform 0.1s;
}
.char-chip:active { transform: scale(0.98); }
.char-chip.on {
  border-color: var(--cc);
  background: color-mix(in srgb, var(--cc) 20%, transparent);
}
/* color-mix 在旧 WebKit 上不支持，退回一个中性高亮 ——
   没有这一条时选中态在老 iPhone 上只有边框变色，很弱。 */
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .char-chip.on { background: rgba(58, 130, 243, 0.16); }
}
.char-chip .cc-ico { font-size: 18px; line-height: 1; }
.char-chip .cc-name { font-size: 14px; font-weight: 700; }

.char-detail { margin-top: 8px; }
.cd-desc {
  display: var(--fit-1, block);
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.45;
}
.cd-perks {
  margin-top: 5px;
  display: flex;
  flex-wrap: wrap;
  gap: 3px 10px;
}
.cd-p {
  font-size: 11px;
  color: #a9d6b3;
  font-variant-numeric: tabular-nums;
}
/* 代价用暖色单独标出来：混在加成里会被忽略，而它是这个角色「取舍」的全部 */
.cd-p.minus { color: #f0b47a; }
.cd-note {
  display: var(--fit-1, block);
  margin-top: 6px;
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.4;
}
.lbl-hint { font-size: 11px; color: var(--text-dim); font-weight: 400; }

/* 统计面板里的角色卡片 */
.char-stat {
  margin-bottom: 14px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
}
.cs-row { display: flex; align-items: center; gap: 7px; }
.cs-row + .cs-row { margin-top: 6px; }
.cs-ico { font-size: 17px; line-height: 1; }
.cs-name { font-size: 13px; font-weight: 700; }
.cs-tag {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-dim);
}
.cs-list {
  margin: 6px 0 0;
  padding: 0 0 0 2px;
  list-style: none;
  display: grid;
  gap: 2px;
}
.cs-list li {
  font-size: 11px;
  color: #a9d6b3;
  font-variant-numeric: tabular-nums;
}
.cs-bonus {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
  font-size: 11px;
  color: var(--fire);
}

/* ==========================================================================
   Phase 5 — 长期存档世界（大厅列表）
   ========================================================================== */
.world-list { display: grid; gap: 6px; }
.world-row { display: flex; align-items: stretch; gap: 6px; }
.wr-go {
  flex: 1;
  min-width: 0;
  text-align: left;
  padding: 9px 12px;
  border-radius: 12px;
  border: 1px solid rgba(58, 130, 243, 0.35);
  background: rgba(58, 130, 243, 0.10);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.wr-go:active { background: rgba(58, 130, 243, 0.26); transform: scale(0.99); }
.wr-top { display: flex; align-items: baseline; gap: 8px; }
.wr-name { font-size: 14px; font-weight: 700; }
.wr-day {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
}
.wr-sub { margin-top: 2px; font-size: 11px; color: var(--text-dim); }
/* 删除键要 44x44（手机上点得准的下限）。原来是 40 宽，巡检直接标红。
   宽度从 40 提到 44、去掉 flex:none 之外还要保证它不把整行撑高 ——
   行高由 .wr-go 的内容决定（约 46px），删除键 44 落在里面。 */
.wr-del {
  width: 44px;
  min-height: 44px;
  flex: none;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-dim);
  font-size: 14px;
  cursor: pointer;
}
.wr-del:active { background: rgba(229, 72, 77, 0.22); color: #ff9ea1; }

/* ==========================================================================
   Phase 5 — 季节 Boss
   ========================================================================== */

/* Boss 血条。在 #dayBox 内部（正常流），所以不会和顶部任何东西重叠。
   用深红描边而不是品牌蓝：它不是「信息」，是「威胁」。 */
#bossBar { margin-top: 7px; width: 148px; }
.bb-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 10px;
  line-height: 1.3;
}
#bossName {
  font-weight: 800;
  color: #ffb0b3;
  letter-spacing: 0.06em;
  text-shadow: 0 1px 3px #000;
}
#bossHpText { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.bb-track {
  margin-top: 3px;
  height: 7px;
  border-radius: 99px;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(229, 72, 77, 0.4);
  overflow: hidden;
}
#bossHpFill {
  height: 100%;
  width: 100%;
  border-radius: 99px;
  background: linear-gradient(90deg, #8f2429, #e5484d);
  transition: width 0.2s linear;
}

/* 「今晚它要来」的预告，和火把/木甲同一条竖线 */
#bossHint {
  margin-top: 6px;
  font-size: 11px;
  color: #ffb0b3;
  text-shadow: 0 1px 3px #000;
  white-space: nowrap;
}

/* 队友的角色图标。放在名字左边，一眼知道对方带的是什么 */
#peerChar { font-size: 12px; line-height: 1; }
#peerChar:empty { display: none; }

/* ==========================================================================
   Phase 6：往《饥荒》靠的界面风格
   --------------------------------------------------------------------------
   一段集中在这里，而不是散回上面各处。理由：这是一次**纯视觉**的改动，
   把它圈在一起，将来想再换一次皮（或者只改木头的色）只需要动这一块。

   饥荒的界面语言其实只有三条，照抄这三条就够了：
     1. **深色木头打底 + 手绘感的粗描边**（不是细线、不是玻璃拟态）
     2. **羊皮纸黄的字**，字重偏粗、带一点投影
     3. 可点的东西**永远有明确的边框**，选中/可用的用金色描边点亮
   ========================================================================== */
:root {
  --wood: #4c3c2b;
  --wood-2: #291f17;
  --wood-edge: #14100c;
  --parch: #e8d5a9;
  --parch-dim: #a89877;
  --gold: #ffc45c;
  --gold-dim: #8a6a2f;
}

/* ---------------- 木牌：dayBox / 胶囊 / 面板共用一套底 ---------------- */
#dayBox {
  padding: 7px 9px 8px;
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(70, 55, 40, 0.80), rgba(36, 27, 20, 0.86));
  border: 2px solid var(--wood-edge);
  box-shadow: inset 0 1px 0 rgba(255, 214, 150, 0.12), 0 2px 10px rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
#hudDay { color: var(--parch); letter-spacing: 0.02em; }
#hudPhase { color: var(--parch-dim); }
#hudTime { color: var(--parch-dim); }
#dayBarWrap {
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(0, 0, 0, 0.6);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.pill {
  background: linear-gradient(180deg, rgba(70, 55, 40, 0.88), rgba(36, 27, 20, 0.9));
  border: 2px solid var(--wood-edge);
  color: var(--parch);
  font-weight: 600;
}
.pill.icon { font-size: 16px; }

/* ---------------- 动作按钮：三行两列，木底金边 ---------------- */
.act-btn {
  width: 66px;
  height: 66px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(80, 63, 45, 0.94), rgba(38, 28, 20, 0.96));
  border: 2px solid var(--wood-edge);
  box-shadow: inset 0 1px 0 rgba(255, 214, 150, 0.16), 0 2px 8px rgba(0, 0, 0, 0.5);
  color: var(--parch);
  font-size: 23px;
  position: relative;
  overflow: hidden;
}
.act-btn small { color: var(--parch-dim); font-weight: 600; }
.act-btn.pressed { transform: scale(0.92); background: linear-gradient(180deg, #6d5636, #4a381f); }
.act-btn.primary { border-color: #7a5a1e; box-shadow: inset 0 0 0 1px rgba(255, 196, 92, 0.35); }
.act-btn.primary.pressed { background: linear-gradient(180deg, #8a6a2f, #5e4718); }
.act-btn.danger { border-color: #6d2a2a; }
.act-btn.danger.pressed { background: linear-gradient(180deg, #8c3a3a, #5c2222); }
/* 技能键：可用时金色描边会呼吸，冷却时被一层扇形遮罩盖住 */
.act-btn.skill { border-color: #7a5a1e; }
.act-btn.skill.ready {
  animation: skillReady 1.6s ease-in-out infinite;
}
@keyframes skillReady {
  50% { box-shadow: inset 0 1px 0 rgba(255, 214, 150, 0.2), 0 0 12px rgba(255, 196, 92, 0.55); }
}
#skillCdMask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* 用 conic-gradient 画冷却扇形：这里只在冷却变化时重绘一次，
     不像状态环那样每帧都可能动，所以掉帧风险可以接受 */
  background: conic-gradient(rgba(0, 0, 0, 0.66) calc(var(--cd, 0) * 1turn), transparent 0);
}

/* ---------------- 合成面板：分类标签 + 配方列表 ---------------- */
.craft-tabs {
  display: flex;
  gap: 4px;
  padding: 0 0 8px;
  overflow-x: auto;
  scrollbar-width: none;
}
.craft-tabs::-webkit-scrollbar { display: none; }
.craft-tab {
  flex: none;
  min-width: 46px;
  min-height: 46px;
  border-radius: 9px;
  border: 2px solid var(--wood-edge);
  background: linear-gradient(180deg, rgba(64, 50, 36, 0.9), rgba(30, 23, 17, 0.94));
  color: var(--parch);
  font-family: inherit;
  font-size: 19px;
  line-height: 1;
  display: grid;
  place-items: center;
  gap: 1px;
  cursor: pointer;
  position: relative;
}
.craft-tab small { font-size: 9px; color: var(--parch-dim); }
.craft-tab.on {
  border-color: var(--gold);
  background: linear-gradient(180deg, rgba(122, 90, 30, 0.9), rgba(70, 50, 18, 0.94));
}
.craft-tab .badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 15px;
  height: 15px;
  border-radius: 99px;
  background: var(--gold);
  color: #2a1d08;
  font-size: 10px;
  font-weight: 800;
  display: grid;
  place-items: center;
}
.sheet {
  background: linear-gradient(180deg, rgba(48, 38, 28, 0.98), rgba(26, 20, 15, 0.99));
  border: 2px solid var(--wood-edge);
  color: var(--parch);
}
.sheet-head { border-bottom: 1px solid rgba(255, 214, 150, 0.18); font-weight: 700; }
.sheet-close {
  border: 2px solid var(--wood-edge);
  background: rgba(0, 0, 0, 0.35);
  color: var(--parch);
  border-radius: 8px;
}
.sheet-sub { font-size: 11px; color: var(--parch-dim); font-weight: 500; margin-left: auto; }
.sheet-sub.on { color: var(--gold); }
.craft-row {
  border: 2px solid var(--wood-edge);
  background: linear-gradient(180deg, rgba(62, 49, 35, 0.72), rgba(32, 25, 18, 0.8));
  color: var(--parch);
}
.craft-row.ok {
  border-color: var(--gold-dim);
  background: linear-gradient(180deg, rgba(96, 74, 28, 0.7), rgba(52, 40, 16, 0.8));
}
.craft-row .cbody b { color: var(--parch); }
.craft-row .cbody em, .craft-row .cneed { color: var(--parch-dim); }
.craft-row.ok .cneed { color: #cfe3a8; }
.craft-row .cgo {
  background: linear-gradient(180deg, #8a6a2f, #5e4718);
  border: 2px solid var(--wood-edge);
  color: #fff6e0;
  font-weight: 700;
}
.craft-row.ok .cgo { background: linear-gradient(180deg, #d9a63f, #a87a22); color: #2a1d08; }
.craft-row.locked { opacity: 0.62; }
.craft-row .cfire { font-size: 10px; color: #e8a33d; }

/* 很矮的屏幕（≤660）上，物品栏 + 按钮组已经把纵向空间占满了，
   toast 再挤在物品栏上方会顶到顶部 HUD —— 这种尺寸下把它压扁一点。 */
@media (max-height: 660px) {
  #toast {
    padding: 7px 14px;
    font-size: 13px;
  }
}

/* ==========================================================================
   Phase 7：整体换成《饥荒》的界面语言
   --------------------------------------------------------------------------
   和 Phase 6 那次「局部贴皮」不同，这一次动的是**布局本身**：

     饥荒的 HUD 只有四块，位置几十年没变过 ——
       1. **圆圈时钟**（右上）：一天被切成 昼 / 昏 / 夜 三段，指针走一圈就过一天
       2. **三个状态徽章**（这里是左上）：圆形木牌，图标 + 数字
       3. **物品栏**（底部中间）：一整条木槽，左边挂着手 / 身 / 头三个装备槽
       4. **合成菜单**（左侧）：一竖列分类，点开才展开配方

   之前的版本把物品栏摆在屏幕顶部中间 —— 那是顺手，不是饥荒。
   这次把它挪到底部，代价是「上面空出来的地方」要重新分配：
   于是天数/阶段搬进了时钟、队友状态搬进了徽章那块木牌。

   视觉上照抄饥荒的三条：
     1. **深色木头打底 + 硬描边**（不是细线、不是玻璃拟态）
     2. **羊皮纸黄的字，带一圈黑色硬描边**（不是模糊投影）
     3. 可点的东西**永远有明确的边框**，可用/选中的用金色点亮
   ========================================================================== */

/* canvas 是「替换元素」：绝对定位 + inset:0 时 width:auto 会解析成它的
   **固有宽度**（width 属性），而不是撑满容器。所以要显式给 100%。
   这样画布的**布局尺寸**永远等于 #stage，和 resize 事件有没有来无关 ——
   后备缓冲的对齐交给 JS（见 main.js 的 resize / frame）。 */
#game {
  width: 100%;
  height: 100%;
}

:root {
  /* 木头三档：面板底、木头暗面、描边 */
  --wood: #5a452f;
  --wood-2: #33261a;
  --wood-3: #241a11;
  --wood-edge: #140f0a;
  --parch: #f0ddb4;
  --parch-dim: #b09a76;
  --gold: #ffc45c;
  --gold-dim: #8a6a2f;
  /* 文字硬描边。饥荒的字不是「投影模糊」，是**一圈实心黑边** ——
     八个方向的 0 模糊偏移拼出来，比 box-shadow 便宜也比模糊干净。 */
  --ds-line: #150e07;
  --ds-ink: #150e07;
}
/* 一整套「黑描边 + 落影」的文字阴影，所有 HUD 文字共用 */
#hud {
  --outline:
    1.4px 0 0 var(--ds-line), -1.4px 0 0 var(--ds-line),
    0 1.4px 0 var(--ds-line), 0 -1.4px 0 var(--ds-line),
    1.4px 1.4px 0 var(--ds-line), -1.4px -1.4px 0 var(--ds-line),
    1.4px -1.4px 0 var(--ds-line), -1.4px 1.4px 0 var(--ds-line),
    0 3px 6px rgba(0, 0, 0, 0.65);
}

/* ---------------- 暗角 ----------------
   整个画面压一圈暗角。饥荒的画面是「中间亮、四角沉」，
   没有它，UI 会像贴在截图上的浮层，而不是长在画面里。 */
#stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background:
    radial-gradient(120% 82% at 50% 46%, transparent 42%, rgba(8, 6, 4, 0.34) 78%, rgba(6, 4, 3, 0.62) 100%);
}

/* ==========================================================================
   1. 圆盘时钟
   ========================================================================== */
#topRight {
  align-items: center;
  gap: 5px;
}
/* 两行小木牌。竖着一列摆四个会把右上角拉到 245px 高（实测），
   而横幅是从这一块的底边往下排的 —— 右边多高，横幅就被压多低。 */
.top-pills {
  display: flex;
  align-items: center;
  gap: 5px;
}
#clock {
  position: relative;
  width: 76px;
  height: 76px;
  flex: none;
  /* 外面的黑描边靠 drop-shadow：SVG 自己画不出「轮廓外扩」 */
  filter: drop-shadow(0 3px 7px rgba(0, 0, 0, 0.62));
}
#clock .clk { display: block; width: 100%; height: 100%; }
/* 木框：外圈厚、暗，里面收一圈亮线，看着才像刻出来的一块木头 */
.clk-rim { fill: #2e2115; stroke: #0e0a06; stroke-width: 3.4; }
.clk-season-ring {
  fill: none;
  stroke: var(--tint, #e8d5a9);
  stroke-width: 2.6;
  opacity: 0.8;
  transition: stroke 0.6s ease;
}
/* 牌面：比木框更深，让三段弧浮起来 */
.clk-face { fill: #150f09; stroke: rgba(0, 0, 0, 0.66); stroke-width: 1.6; }
/* 三段相位弧。r=38 → 周长 2π×38 ≈ 238.76，JS 按比例切 */
.seg { fill: none; stroke-width: 12.5; }
.s-day { stroke: #e9b64c; }
.s-dusk { stroke: #cf6c33; }
.s-night { stroke: #37527e; }
/* 「已经过去」的暗弧：压在三段弧上面，指针走到哪暗到哪 */
.clk-shade { fill: none; stroke: rgba(8, 5, 3, 0.74); stroke-width: 12.5; }
/* 指针：圆周上的一个亮点 */
.clk-hand {
  fill: #fff3d0;
  stroke: #0e0a06;
  stroke-width: 1.8;
  filter: drop-shadow(0 0 3px rgba(255, 232, 170, 0.9));
}
/* 中间的天数 */
.clk-day {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 22px;
  font-weight: 800;
  color: var(--parch);
  font-variant-numeric: tabular-nums;
  text-shadow: var(--outline);
  pointer-events: none;
}
/* 季节图标挂在钟的右上角 */
.clk-season {
  position: absolute;
  right: -3px;
  top: -4px;
  font-size: 15px;
  line-height: 1;
  filter: drop-shadow(0 1px 2px #000);
}
#clkMeta {
  text-align: center;
  font-size: 10px;
  line-height: 1.3;
  color: var(--parch-dim);
  text-shadow: 0 1px 3px #000;
  white-space: nowrap;
}
#clkMeta #hudPhase { display: block; color: var(--parch); font-weight: 700; }

/* ==========================================================================
   2. 三个状态徽章
   ========================================================================== */
#dayBox {
  padding: 6px 8px 7px;
  border-radius: 13px;
  /* 木牌：木纹用 repeating-gradient 假装，零贴图 */
  background:
    repeating-linear-gradient(94deg, rgba(255, 226, 178, 0.035) 0 1px, transparent 1px 4px),
    linear-gradient(180deg, #55402c, #33261a 58%, #241a11);
  border: 3px solid var(--wood-edge);
  box-shadow:
    inset 0 2px 0 rgba(255, 216, 150, 0.16),
    inset 0 -3px 7px rgba(0, 0, 0, 0.5),
    0 4px 14px rgba(0, 0, 0, 0.5);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#vitals { margin-top: 0; display: flex; gap: 6px; }
.vital { width: 48px; height: 48px; }
/* 木框 + 暗牌面。stroke-width 从 6 加到 7.5：徽章要「厚」才像刻出来的 */
.vital .ring .vt { fill: #1b130c; stroke: #0e0a06; stroke-width: 7.5; }
.vital .ring .vf { stroke-width: 7.5; }
.vital .vi {
  position: relative;
  font-size: 16px;
  transform: translateY(-6px);
  filter: drop-shadow(0 1px 2px #000);
}
.vital .vn {
  position: absolute;
  bottom: 3px;
  font-size: 12px;
  font-weight: 800;
  color: var(--parch);
  text-shadow: var(--outline);
}
/* 见底：整块徽章抖一下 + 数字变红，比只让环闪更抓眼睛 */
.vital.low .vf { animation: ringPulse 0.85s ease-in-out infinite; }
.vital.low .vn { color: #ffd0d2; }
.vital.low { animation: badgeShake 1.1s ease-in-out infinite; }
@keyframes badgeShake {
  0%, 88%, 100% { transform: translateX(0); }
  91% { transform: translateX(-2px); }
  94% { transform: translateX(2px); }
  97% { transform: translateX(-1px); }
}

/* 队友状态：现在在 dayBox 里走正常流 */
#peerLine {
  position: static;
  margin-top: 6px;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--parch-dim);
  text-shadow: 0 1px 3px #000;
  white-space: nowrap;
}
#peerLine .dot { width: 7px; height: 7px; }
#peerName { color: #ffd2de; font-weight: 700; text-shadow: var(--outline); }

/* Boss 血条与预警：同一块木牌里，宽度跟着徽章走 */
#bossBar { width: 100%; min-width: 148px; margin-top: 7px; }
#bossHint { white-space: normal; line-height: 1.25; text-shadow: var(--outline); }
#bossName { text-shadow: var(--outline); }

/* ==========================================================================
   3. 顶部胶囊（房间码 / 网络 / 音效）—— 缩成小木牌，别抢时钟的位置
   ========================================================================== */
.pill {
  padding: 5px 9px;
  border-radius: 9px;
  border-width: 2.5px;
  font-size: 12px;
  background:
    linear-gradient(180deg, #4d3a28, #2b2015 62%, #1d150e);
  border-color: var(--wood-edge);
  color: var(--parch);
  box-shadow: inset 0 1px 0 rgba(255, 216, 150, 0.16), 0 2px 7px rgba(0, 0, 0, 0.5);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.pill.icon { font-size: 15px; padding: 5px 7px; }
.pill-ico { font-size: 11px; opacity: 0.75; }
#codePill {
  border-color: var(--gold-dim);
  background: linear-gradient(180deg, #6d5426, #40301a 64%, #2b2012);
}
#codePill:active { background: linear-gradient(180deg, #8a6a2f, #55401f); }
#roomCode { font-weight: 800; letter-spacing: 0.14em; color: #ffeec2; text-shadow: 0 1px 3px #000; }
/* 网络状态用一个小圆点表达：比「网络 在线」四个字省一半宽度 */
.net-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #6b7684;
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.6);
}
#netPill[data-net="ok"] .net-dot { background: #57c46b; box-shadow: 0 0 7px rgba(87, 196, 107, 0.8); }
#netPill[data-net="warn"] .net-dot { background: #e0a33f; box-shadow: 0 0 7px rgba(224, 163, 63, 0.8); }
#netPill[data-net="bad"] .net-dot {
  background: #e5484d; box-shadow: 0 0 8px rgba(229, 72, 77, 0.85);
  animation: pulseRed 0.9s ease-in-out infinite;
}

/* ==========================================================================
   4. 物品栏：底部中间的一整条木槽
   ========================================================================== */
#invBar {
  top: auto;
  bottom: var(--inv-bottom);
  left: 50%;
  transform: translateX(-50%);
  gap: 4px;
  padding: 6px 7px 7px;
  border-radius: 14px;
  background:
    repeating-linear-gradient(92deg, rgba(255, 226, 178, 0.03) 0 1px, transparent 1px 5px),
    linear-gradient(180deg, #5a452f, #33261a 60%, #241a11);
  border: 3px solid var(--wood-edge);
  box-shadow:
    inset 0 2px 0 rgba(255, 216, 150, 0.18),
    inset 0 -4px 9px rgba(0, 0, 0, 0.5),
    0 6px 20px rgba(0, 0, 0, 0.55);
}
/* 装备槽那一行：和背包格子用**同一块木槽**，中间只压一条浅刻线 ——
   饥荒也是把它们摆在一起的（左边装备、下面是背包） */
.eq-row {
  gap: 5px;
  padding-bottom: 5px;
  margin-bottom: 2px;
  border-bottom: 2px solid rgba(20, 15, 10, 0.75);
  box-shadow: 0 1px 0 rgba(255, 216, 150, 0.10);
}
/* 格子：**刻出来的凹槽** —— 上沿压暗、下沿提亮，一眼就是「凹进去的」。
   对比度刻意拉开：木头底和格子底太接近的话，整条木槽会糊成一块。 */
.slot {
  border-radius: 9px;
  border: 2px solid #0d0906;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.62) 0 16%, rgba(0, 0, 0, 0.06) 54%, rgba(255, 220, 170, 0.05) 100%),
    radial-gradient(circle at 50% 30%, #3f3122, #18110b 84%);
  box-shadow:
    inset 0 4px 7px rgba(0, 0, 0, 0.72),
    inset 0 -1px 0 rgba(255, 220, 170, 0.12);
  color: var(--parch);
}
.slot.filled {
  border-color: #7a6237;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.4) 0 12%, rgba(0, 0, 0, 0) 48%),
    radial-gradient(circle at 50% 32%, #6a5537, #302416 80%);
  box-shadow:
    inset 0 2px 5px rgba(0, 0, 0, 0.55),
    inset 0 -1px 0 rgba(255, 220, 170, 0.18);
}
.slot.eq { border-color: #0f0b07; }
.slot.eq::before { opacity: 0.28; filter: grayscale(0.7); }
.slot.food { border-color: #4c6b3f; }
.slot.place { border-color: #7a5a1e; }
.slot.torch { border-color: #8a5320; }
.slot.equipped { box-shadow: inset 0 0 0 2px rgba(255, 196, 92, 0.6); }
.slot.low { border-color: #8f2f2f; }
.slot.low::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 7px;
  box-shadow: inset 0 0 8px rgba(229, 72, 77, 0.75);
  pointer-events: none;
}

/* ---- Phase 12 第 5 步：腐坏 ----
   食物在**背包里**也会变坏，所以格子必须自己说出「它有多不新鲜」。
   三档用「边框色 + 图标压色 + 角标」三个信号，不靠单一颜色 ——
   色觉差异的玩家仍然能靠角标 ⚠ / 💀 读到。 */
.slot.spoil1 { border-color: #8a7434; }
.slot.spoil1 .ic { filter: saturate(0.72) sepia(0.22); }
.slot.spoil2 { border-color: #9c6a24; }
.slot.spoil2 .ic { filter: saturate(0.5) sepia(0.45) brightness(0.92); }
.slot.rot { border-color: #5d5030; }
.slot.rot .ic { opacity: 0.45; filter: grayscale(0.9) brightness(0.7); }
.slot .spoilmark {
  position: absolute;
  left: 1px;
  top: 1px;
  font-size: 9px;
  line-height: 1;
  pointer-events: none;
}
/* 冰窖里的格子：边框发冷光，和背包里「正在变坏」的黄框正好相反。 */
.slot.frozen {
  border-color: #5fa8c4;
  box-shadow: inset 0 0 6px rgba(143, 216, 240, 0.28);
}
.slot .n {
  right: 2px;
  bottom: 1px;
  font-size: 10px;
  text-shadow: var(--outline);
}

/* ==========================================================================
   5. 动作按钮：3 列 × 2 行的圆木按钮
   --------------------------------------------------------------------------
   为什么从 2×3 改成 3×2：物品栏挪到底部之后，纵向空间变得很贵。
   3 列只占两行（116px 而不是 216px），省出来的正好够物品栏。
   位置**显式指定**，不靠自动排布：技能键在没技能的角色那里是隐藏的，
   自动排布会让剩下五个整体往上挪一格 —— 那是按错技能键的经典原因。
   ========================================================================== */
#buttons {
  right: calc(var(--safe-r) + 12px);
  bottom: calc(var(--safe-b) + 16px);
  grid-template-columns: repeat(3, auto);
  gap: 8px;
}
#btnSkill    { grid-column: 1; grid-row: 1; }
#btnTorch    { grid-column: 2; grid-row: 1; }
#btnCraft    { grid-column: 3; grid-row: 1; }
#btnWave     { grid-column: 1; grid-row: 2; }
#btnInteract { grid-column: 2; grid-row: 2; }
#btnAttack   { grid-column: 3; grid-row: 2; }

.act-btn {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 3px solid var(--wood-edge);
  background: radial-gradient(circle at 50% 26%, #6d5533, #3a2a19 68%, #241a10);
  box-shadow:
    inset 0 4px 0 rgba(255, 216, 150, 0.18),
    inset 0 -5px 8px rgba(0, 0, 0, 0.55),
    0 4px 10px rgba(0, 0, 0, 0.5);
  color: var(--parch);
  font-size: 21px;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.act-btn small {
  font-size: 9px;
  color: var(--parch-dim);
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-top: 1px;
}
.act-btn.pressed {
  transform: scale(0.93);
  background: radial-gradient(circle at 50% 30%, #8a6a2f, #55401f 70%, #33261a);
}
/* 交互：最常用，金边点出来 */
.act-btn.primary {
  border-color: #7a5a1e;
  background: radial-gradient(circle at 50% 26%, #7c6236, #46341d 68%, #2a1f12);
}
.act-btn.primary.pressed { background: radial-gradient(circle at 50% 30%, #a37f38, #5e4718 72%, #3a2c16); }
/* 攻击：暗红木，战斗时靠肌肉记忆按，不能和「挥手」长得一样 */
.act-btn.danger {
  border-color: #5e1f1f;
  background: radial-gradient(circle at 50% 26%, #6d3130, #3c1c1c 68%, #241111);
}
.act-btn.danger.pressed { background: radial-gradient(circle at 50% 30%, #94413c, #4d2222 72%, #2c1212); }
/* 技能：可用时金边呼吸，冷却时被一层扇形遮罩盖住 */
.act-btn.skill {
  border-color: #7a5a1e;
  background: radial-gradient(circle at 50% 26%, #5c5233, #37301c 68%, #211c10);
}
.act-btn.skill.ready { animation: skillReady 1.6s ease-in-out infinite; }
@keyframes skillReady {
  50% { box-shadow: inset 0 4px 0 rgba(255, 216, 150, 0.2), 0 0 16px rgba(255, 196, 92, 0.7); }
}
#skillCdMask {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(rgba(4, 3, 2, 0.74) calc(var(--cd, 0) * 1turn), transparent 0);
}

/* ==========================================================================
   6. 合成菜单：左边一竖列分类（饥荒的摆法）
   ========================================================================== */
#craftPanel {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto 1fr;
  max-height: 76%;
  border-radius: 16px 16px 0 0;
  border-top-width: 3px;
  background:
    repeating-linear-gradient(90deg, rgba(255, 226, 178, 0.028) 0 1px, transparent 1px 6px),
    linear-gradient(180deg, #4a3927, #2a2016 56%, #1d150e);
  box-shadow: 0 -6px 26px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 216, 150, 0.14);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--parch);
}
#craftPanel > .sheet-head { grid-column: 1 / -1; }
.sheet-head {
  border-bottom: 2px solid rgba(20, 15, 10, 0.7);
  box-shadow: 0 1px 0 rgba(255, 216, 150, 0.10);
  font-size: 14px;
  letter-spacing: 0.06em;
  text-shadow: var(--outline);
}
.sheet-close {
  border: 2px solid var(--wood-edge);
  border-radius: 9px;
  background: linear-gradient(180deg, #5a452f, #2b2015);
  color: var(--parch);
  box-shadow: inset 0 1px 0 rgba(255, 216, 150, 0.16);
}
/* 分类：竖着排，选中的用金底 —— 和饥荒左边那一列是一个意思 */
#craftTabs {
  flex-direction: column;
  width: 58px;
  padding: 0 8px 12px 12px;
  gap: 5px;
  overflow-x: hidden;
  overflow-y: auto;
}
.craft-tab {
  min-width: 0;
  width: 46px;
  min-height: 46px;
  border-radius: 10px;
  border: 2.5px solid var(--wood-edge);
  background: linear-gradient(180deg, #4f3c28, #291f14 66%, #1c140d);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.14), inset 0 -3px 6px rgba(0, 0, 0, 0.45);
  color: var(--parch);
}
.craft-tab.on {
  border-color: var(--gold);
  background: linear-gradient(180deg, #96702a, #5b4318 70%, #3d2c10);
  box-shadow: inset 0 2px 0 rgba(255, 236, 190, 0.35), 0 0 12px rgba(255, 196, 92, 0.35);
}
.craft-tab small { font-size: 8.5px; }
.craft-tab .badge { border: 1.5px solid var(--wood-edge); }
.craft-list {
  padding: 0 12px 16px 4px;
  gap: 7px;
}
.craft-row {
  border: 2.5px solid var(--wood-edge);
  border-radius: 11px;
  background: linear-gradient(180deg, rgba(74, 57, 39, 0.86), rgba(38, 29, 20, 0.9));
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.10), 0 2px 6px rgba(0, 0, 0, 0.35);
  color: var(--parch);
}
.craft-row.ok {
  border-color: var(--gold-dim);
  background: linear-gradient(180deg, rgba(110, 84, 30, 0.84), rgba(58, 44, 16, 0.9));
}
.craft-row .cgo {
  border: 2px solid var(--wood-edge);
  background: linear-gradient(180deg, #5a452f, #2b2015);
  color: var(--parch-dim);
}
.craft-row.ok .cgo {
  background: radial-gradient(circle at 50% 30%, #ffd479, #c8942c);
  color: #2a1d08;
}
.craft-row.locked { opacity: 0.6; }
.craft-row .cbody b { font-size: 13.5px; text-shadow: 0 1px 3px #000; }

/* 统计面板同一套木头 */
#statsPanel {
  background:
    repeating-linear-gradient(90deg, rgba(255, 226, 178, 0.028) 0 1px, transparent 1px 6px),
    linear-gradient(180deg, #4a3927, #2a2016 56%, #1d150e);
  border-top-width: 3px;
  box-shadow: 0 -6px 26px rgba(0, 0, 0, 0.6);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.stat-cell {
  background: rgba(0, 0, 0, 0.28);
  border: 2px solid var(--wood-edge);
  border-radius: 10px;
}
.stat-cell .v { color: var(--parch); }
.stat-cell .k { color: var(--parch-dim); }
.sum-head { color: var(--parch); }

/* ==========================================================================
   7. 横幅 / 提示 / 调试 —— 位置全部改成跟着新几何走
   ========================================================================== */
.banner {
  padding: 7px 15px;
  border-radius: 10px;
  border: 2.5px solid var(--wood-edge);
  background: linear-gradient(180deg, rgba(74, 57, 39, 0.95), rgba(32, 24, 16, 0.96));
  color: var(--parch);
  font-weight: 700;
  text-shadow: var(--outline);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.14), 0 3px 12px rgba(0, 0, 0, 0.5);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.banner.warn { border-color: #7a5a1e; color: #ffd88a; }
.banner.bad { border-color: #7a2a2a; color: #ffb0b3; }

#interactHint {
  /* 物品栏现在在底部，提示排在它上面。
     和 #toast 一样只用一个事实，别写成两个近似值相加（见那里的说明）。 */
  bottom: calc(var(--inv-top-off, 371px) + 6px);
  padding: 7px 15px;
  border-radius: 11px;
  border: 2.5px solid var(--wood-edge);
  background: linear-gradient(180deg, #6d5533, #3a2a19);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.2), 0 4px 14px rgba(0, 0, 0, 0.5);
  color: var(--parch);
  font-weight: 700;
  text-shadow: var(--outline);
}
#interactHint[data-locked="1"] {
  background: linear-gradient(180deg, #4c4a45, #2a2926);
  color: var(--parch-dim);
}

#toast {
  /* ⚠ 用**一个事实**（物品栏上沿的实测值），不要写成 --inv-bottom + --inv-h：
     那两个里的任何一个还停在上一档，toast 就会压进物品栏 6px。
     实测在 1280x800 上就是这么压的（巡检的 invBar × toast 断言抓到的），
     和箱子/锅面板当年同一个坑，见 --inv-top-off 的说明。 */
  bottom: calc(var(--inv-top-off, 371px) + 6px);
  border: 2.5px solid var(--wood-edge);
  border-radius: 11px;
  background: linear-gradient(180deg, #8a6a2f, #4d3a18);
  box-shadow: inset 0 2px 0 rgba(255, 232, 180, 0.28), 0 5px 18px rgba(0, 0, 0, 0.55);
  color: #fff3d4;
  font-weight: 700;
  text-shadow: var(--outline);
}

#debug {
  top: calc(var(--below-inv) + 40px);
  border: 2px solid var(--wood-edge);
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(58, 45, 32, 0.9), rgba(24, 18, 12, 0.92));
  color: var(--parch-dim);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.dbg-row b { color: var(--parch); }

/* 桌面键位提示：底部中间现在是物品栏，挪到左下角。
   断点从 720 提到 900 —— 按钮组从 2 列变 3 列之后宽了 48px，
   原来那条断点在 800px 宽的小窗口上会和按钮组撞上。 */
#kbdHint {
  left: calc(var(--safe-l) + 12px);
  bottom: calc(var(--safe-b) + 16px);
  transform: none;
  color: var(--parch-dim);
  text-shadow: var(--outline);
}
@media (max-width: 900px) {
  #kbdHint { display: none; }
}

/* 成就条 / 结算卡也换成木头 */
.ach-item {
  border: 2.5px solid var(--wood-edge);
  border-radius: 11px;
  background: linear-gradient(180deg, #5a452f, #2b2015);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.18), 0 4px 14px rgba(0, 0, 0, 0.5);
  color: var(--parch);
  text-shadow: 0 1px 3px #000;
}
.sum-card {
  border: 3px solid var(--wood-edge);
  border-radius: 15px;
  background:
    repeating-linear-gradient(92deg, rgba(255, 226, 178, 0.03) 0 1px, transparent 1px 5px),
    linear-gradient(180deg, #55402c, #2a2016 62%, #1d150e);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.16), 0 10px 30px rgba(0, 0, 0, 0.6);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  text-shadow: 0 1px 3px #000;
}
.sum-row .k, .sum-season { color: var(--parch-dim); }
.sum-row .v, .sum-total { color: var(--parch); }

/* ==========================================================================
   8. 摇杆：让开底部那条物品栏
   --------------------------------------------------------------------------
   物品栏从顶部挪到底部之后，摇杆捕获层必须**缩短**到物品栏的下沿，
   否则点物品栏格子会被摇杆抢走触摸（inspect_ui 会实测这类冲突）。
   留 6px 余量：两个矩形正好贴边时，浮点误差会让重叠断言偶尔变红。
   ========================================================================== */
#joyZone {
  width: min(58%, calc(100% - 212px));
  height: calc(var(--inv-bottom) - 6px);
}
#joyBase {
  border: 3px solid var(--wood-edge);
  background: radial-gradient(circle at 50% 40%, rgba(90, 69, 47, 0.35), rgba(24, 18, 12, 0.45));
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.14);
}
#joyKnob {
  border: 2.5px solid var(--wood-edge);
  background: radial-gradient(circle at 50% 32%, #ffd98e, #c08a30);
  box-shadow: 0 0 14px rgba(255, 196, 92, 0.5);
}

/* ==========================================================================
   9. 覆盖层（连接中 / 等待 / 倒下 / 二维码）也换成同一套木头
   ========================================================================== */
.overlay { background: radial-gradient(circle at 50% 40%, #1d1710 0%, #0a0806 72%); }
.overlay.pass .card,
#downed .card {
  border: 3px solid var(--wood-edge);
  border-radius: 15px;
  background:
    repeating-linear-gradient(92deg, rgba(255, 226, 178, 0.03) 0 1px, transparent 1px 5px),
    linear-gradient(180deg, #55402c, #2a2016 62%, #1d150e);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.16), 0 10px 30px rgba(0, 0, 0, 0.6);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--parch);
}
.muted { color: var(--parch-dim); }
.big-code {
  color: var(--parch);
  text-shadow: var(--outline);
}
.flame { filter: drop-shadow(0 0 18px rgba(255, 179, 71, 0.55)); }
.spinner {
  border-color: rgba(255, 196, 92, 0.25);
  border-top-color: var(--gold);
}

/* ==========================================================================
   Phase 7·B：大厅也换成同一套木头
   --------------------------------------------------------------------------
   只改**外观**，一行版式逻辑都没动 —— fitLobby() 的逐档瘦身、
   安全区补偿、一屏放得下这些规矩全部照旧。
   改了 index.html 之后仍然必须跑 inspect_ui.mjs。
   ========================================================================== */
:root {
  /* 大厅的底色也换成暖调：原来那版偏蓝，和木头面板放一起会「脏」。
     换成同一族暖色、保留一点火光在下方。
     
     ⚠ 第一层是**收尾层**，别删。它解决的是「底部一大片空白」的根因：
     有些 WebView 里 100dvh 比真正的画布矮一截（实测 iPhone 16 Pro Max：
     dvh = 894 而屏幕 956，差的 62 正好是顶部刘海）。渐变的定位区域是
     根元素的盒子（894），盒子之外只剩 background-color —— 而渐变的最后
     几行正好是暖色（实测 #1f130b / 中心 #28200f），于是屏幕底部会出现
     一条**硬边**，看起来就是「页面到这儿就没了，下面一片空白」。
     让渐变在最后 90px 自己淡回基色，边界就无从谈起：
     无论盒子在哪里结束，结束处的颜色都等于盒子外那一层。 */
  --lobby-bg:
    linear-gradient(180deg, rgba(23, 17, 11, 0) calc(100% - 90px), #17110b 100%),
    radial-gradient(1100px 520px at 50% -8%, rgba(255, 196, 92, 0.10), transparent 62%),
    radial-gradient(760px 420px at 50% 108%, rgba(255, 130, 50, 0.12), transparent 60%),
    radial-gradient(120% 80% at 50% 50%, transparent 46%, rgba(6, 4, 3, 0.5) 100%),
    #17110b;
}

.brand h1 {
  color: var(--parch);
  text-shadow:
    2.4px 0 0 var(--ds-line), -2.4px 0 0 var(--ds-line),
    0 2.4px 0 var(--ds-line), 0 -2.4px 0 var(--ds-line),
    2.4px 2.4px 0 var(--ds-line), -2.4px -2.4px 0 var(--ds-line),
    2.4px -2.4px 0 var(--ds-line), -2.4px 2.4px 0 var(--ds-line),
    0 6px 14px rgba(0, 0, 0, 0.7);
  letter-spacing: 0.06em;
}
.brand .sub { color: var(--parch-dim); text-shadow: 0 1px 3px #000; }
.brand .fire { filter: drop-shadow(0 0 26px rgba(255, 179, 71, 0.75)); }

/* 面板 = 一块木牌 */
.panel {
  background:
    repeating-linear-gradient(92deg, rgba(255, 226, 178, 0.03) 0 1px, transparent 1px 5px),
    linear-gradient(180deg, #55402c, #2a2016 58%, #1d150e);
  border: 3px solid var(--wood-edge);
  border-radius: 16px;
  box-shadow:
    inset 0 2px 0 rgba(255, 216, 150, 0.18),
    inset 0 -5px 12px rgba(0, 0, 0, 0.45),
    0 12px 34px rgba(0, 0, 0, 0.6);
}
.field label {
  color: var(--parch-dim);
  letter-spacing: 0.08em;
  text-shadow: 0 1px 3px #000;
}
.lbl-hint { color: #8d7a5c; }

/* 输入框 = 刻进木头里的一道槽 */
input[type="text"], input:not([type]) {
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.55) 0 22%, rgba(0, 0, 0, 0.12) 70%),
    #241a11;
  border: 2.5px solid var(--wood-edge);
  border-radius: 11px;
  color: var(--parch);
  box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.6);
}
input::placeholder { color: #7b6a4e; }

/* 按钮 = 小木牌。主按钮（创建/加入/继续）用金边点出来 */
.btn {
  background: linear-gradient(180deg, #7a5f38, #3f2f1c 62%, #2b2012);
  border: 2.5px solid var(--wood-edge);
  border-radius: 12px;
  color: #fff2d4;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-shadow: 0 2px 3px rgba(0, 0, 0, 0.7);
  box-shadow:
    inset 0 2px 0 rgba(255, 226, 170, 0.24),
    inset 0 -3px 7px rgba(0, 0, 0, 0.45),
    0 4px 12px rgba(0, 0, 0, 0.45);
}
.btn:active {
  background: linear-gradient(180deg, #a3803f, #5a441f 62%, #3a2c16);
  transform: translateY(1px);
}
.btn.ghost {
  background: linear-gradient(180deg, #4a3927, #291f14 64%, #1c140d);
  border-color: var(--wood-edge);
  color: var(--parch);
  text-shadow: 0 1px 3px #000;
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.14), inset 0 -3px 6px rgba(0, 0, 0, 0.4);
}
.btn.ghost:active { background: linear-gradient(180deg, #6a5232, #3a2c1a); }
.btn.link {
  background: none;
  border: 0;
  box-shadow: none;
  color: var(--parch-dim);
  font-weight: 600;
  text-shadow: 0 1px 3px #000;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.btn.link:active { color: var(--gold); }

.divider { color: #8d7a5c; }
.divider::before, .divider::after { background: rgba(255, 216, 150, 0.16); }

/* 角色芯片 = 小木牌；选中的用角色自己的颜色点亮 */
.char-chip {
  background: linear-gradient(180deg, #4a3927, #291f14 66%, #1c140d);
  border: 2.5px solid var(--wood-edge);
  border-radius: 11px;
  color: var(--parch);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.14), inset 0 -3px 6px rgba(0, 0, 0, 0.4);
}
.char-chip.on {
  border-color: var(--cc);
  background: linear-gradient(180deg, color-mix(in srgb, var(--cc) 42%, #3a2c1a), #241a11);
  box-shadow: inset 0 2px 0 rgba(255, 240, 210, 0.28), 0 0 14px color-mix(in srgb, var(--cc) 55%, transparent);
}
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .char-chip.on { background: linear-gradient(180deg, #8a6a2f, #3a2c1a); }
}
.cd-desc { color: var(--parch-dim); }
.cd-p { color: #b6d9a8; }
.cd-p.minus { color: #f0b47a; }
.cd-note { color: #8d7a5c; }

/* 世界列表 = 一排木牌 */
.wr-go {
  background: linear-gradient(180deg, #55402c, #2c2014 64%, #1d150e);
  border: 2.5px solid var(--wood-edge);
  color: var(--parch);
  box-shadow: inset 0 2px 0 rgba(255, 216, 150, 0.14), inset 0 -3px 6px rgba(0, 0, 0, 0.4);
}
.wr-go:active { background: linear-gradient(180deg, #8a6a2f, #4a3818); }
.wr-name { text-shadow: 0 1px 3px #000; }
.wr-day, .wr-sub { color: var(--parch-dim); }
.wr-del {
  background: linear-gradient(180deg, #4a3927, #291f14);
  border: 2.5px solid var(--wood-edge);
  color: var(--parch-dim);
}
.wr-del:active { background: linear-gradient(180deg, #8c3a3a, #4d1f1f); color: #ffc9cb; }

.feature-list { color: var(--parch-dim); }
.panel-foot { color: #8d7a5c; }
.panel-foot a { color: var(--gold); text-shadow: 0 1px 3px #000; }
.err {
  background: linear-gradient(180deg, #6d2a2a, #3a1414);
  border: 2.5px solid var(--wood-edge);
  border-radius: 11px;
  color: #ffd6d7;
  text-shadow: 0 1px 3px #000;
}

/* --------------------------------------------------------------------------
   矮屏上把「木头」收薄一点。
   --------------------------------------------------------------------------
   给面板和按钮加 2.5~3px 的木框描边是有代价的 —— 每个带框的元素都长高
   4~5px，而大厅面板里有 3 个按钮 + 面板自己 + 输入框 + 角色芯片……
   加起来正好把 667 和 640 高的机器顶出视口（实测 3px / 12px）。

   这里不是「把字调小」，而是**把描边和留白收薄**：
   框还在、木头感还在，只是矮屏上不该有 3px 那么厚的一条边。
   ≥700 的尺寸完全不受影响（上面那套值继续用）。
   -------------------------------------------------------------------------- */
@media (max-height: 700px) {
  .panel { border-width: 2px; padding: 10px 13px; }
  /* padding 10px 时按钮总高 = 10*2 + 24 + 4 = 48px，仍在 44px 的可点下限之上 */
  .btn { border-width: 2px; padding: 10px 18px; }
  .btn.link { padding: 5px 10px; }
  .field { margin-bottom: 7px; }
  .field label { margin-bottom: 3px; }
  .divider { margin: 7px 0; }
  .panel-foot { margin-top: 7px; padding-top: 6px; }
  input.code-input { font-size: 21px; }
  .char-chip { border-width: 2px; }
  .wr-go, .wr-del { border-width: 2px; }
}

/* ==========================================================================
   Phase 8：科技与建筑的界面
   ========================================================================== */

/* ---------------- 合成面板里的科技标签 ----------------
   三种状态必须一眼分开：锁着（够不着工作台）、可原型（现在能做，而且会学会）、
   已学会（随便在哪都能做）。
   放成**行内小牌**而不是单独一列：它只在少数几条配方上出现，
   单独一列会让整张列表的行高不齐。 */
.ctech {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 6px;
  border: 1.5px solid var(--wood-edge);
  font-size: 10px;
  font-weight: 700;
  white-space: nowrap;
  text-shadow: 0 1px 2px #000;
}
.ctech.lock { background: linear-gradient(180deg, #5a3a3a, #33201f); color: #e6b0b0; }
.ctech.new {
  background: linear-gradient(180deg, #8a6a2f, #4d3a18);
  color: #ffe9b8;
  animation: ctechPulse 1.8s ease-in-out infinite;
}
.ctech.known { background: linear-gradient(180deg, #3f5a3a, #22301f); color: #bcd9b4; }
@keyframes ctechPulse { 50% { box-shadow: 0 0 8px rgba(255, 196, 92, 0.75); } }
/* 锁着的行压暗一点，但**不要压太狠** —— 玩家需要读清它缺什么 */
.craft-row.locked { opacity: 0.72; }
.craft-tab.locked { opacity: 0.75; }

/* ---------------- 箱子面板 ---------------- */
#boxPanel, #potPanel { max-height: 66%; }
.box-grid {
  display: grid;
  grid-template-columns: repeat(6, auto);
  gap: 3px;
  justify-content: center;
  padding: 0 12px 4px;
}
.sheet-tip {
  padding: 4px 14px calc(12px + var(--safe-b));
  font-size: 11px;
  color: var(--parch-dim);
  text-align: center;
  text-shadow: 0 1px 3px #000;
}

/* ---------------- 锅面板 ---------------- */
.pot-slots {
  display: grid;
  grid-template-columns: repeat(4, auto);
  gap: 6px;
  justify-content: center;
  padding: 4px 12px 6px;
}
.pot-preview {
  min-height: 22px;
  padding: 0 14px;
  text-align: center;
  font-size: 13px;
  color: var(--parch);
  text-shadow: 0 1px 3px #000;
}
.pot-preview[data-state="done"] { color: #ffe08a; font-weight: 800; }
.pot-preview[data-state="cooking"] { color: var(--parch-dim); }
.pot-preview[data-state="empty"] { color: #8d7a5c; }
.pot-actions { padding: 8px 14px calc(14px + var(--safe-b)); display: flex; }
.pot-actions .btn { width: 100%; }
.pot-actions .btn:disabled { opacity: 0.45; pointer-events: none; }

/* 合成列表：**别让网格把两行拉满整屏**。
   .craft-list 是面板的第二行（1fr），grid 的 align-content 默认是 stretch ——
   于是「只有两条配方」时会各撑一半高度，行高离谱且按钮跑到中间。
   内容不满时应当从顶部开始排。 */
.craft-list { align-content: start; }

/* --------------------------------------------------------------------------
   箱子 / 锅：**浮在物品栏上方**，不盖住它。
   --------------------------------------------------------------------------
   理由很实际：往箱子或锅里放东西，靠的就是点**背包格子** ——
   面板把背包盖住，这件事就做不成了（实测第一版就是这样，最下面
   一整行格子点不到）。

   所以它们不是「贴底的抽屉」，而是一块浮在中间的木牌：
   下沿对齐物品栏的上沿，高度用「顶部 HUD 底边 → 物品栏顶边」这段空间算。
   `max(160px, …)` 是为了极矮的屏上不至于被算成 0 高。
   -------------------------------------------------------------------------- */
#boxPanel, #potPanel {
  left: 8px;
  right: 8px;
  bottom: calc(var(--inv-bottom) + var(--inv-h) + 6px);
  max-height: max(160px, calc(100% - var(--below-inv) - var(--inv-bottom) - var(--inv-h) - 18px));
  border-radius: 15px;
  border-width: 3px;
  padding-bottom: 0;
  animation: panelUp 0.16s ease;
}
@keyframes panelUp { from { transform: translateY(10px); opacity: 0; } }
/* 它不贴底了，所以两处内边距里的 safe-b 要去掉 —— 安全区已经由物品栏负责 */
#boxPanel .sheet-tip { padding: 4px 14px 12px; }
#potPanel .pot-actions { padding: 8px 14px 14px; }

/* 容器面板改用**物品栏上沿的实测值**（--inv-top-off）来定位。
   原来写的是 `--inv-bottom + --inv-h`（两个近似值相加），
   只要有一个是上一档的旧值就会压进物品栏几个像素。 */
#boxPanel, #potPanel {
  bottom: calc(var(--inv-top-off, 371px) + 6px);
  max-height: max(160px, calc(100% - var(--below-inv) - var(--inv-top-off, 371px) - 12px));
}

/* ==========================================================================
   Phase 9：物品栏瘦身 + 地图
   --------------------------------------------------------------------------
   改这一段之前先看清问题长什么样（430×860 实测）：
     · #invBar 是一块 6 列 × 2~3 行的大木牌，314×225，落在 y=487~712 ——
       **正好是屏幕的纵向中间**。玩家反馈「背包太挡视线」。
     · 装备三槽和背包格子挤在同一块牌子里，于是连「我现在手上拿着什么」
       也要占掉 225px。
   饥荒的摆法是：物品栏是贴着屏幕最底的**一条**，很薄；想看全部就点一下，
   它才往上长出来。装备槽则摆在另一块 HUD 木牌上。
   本项目左上角那块木牌（#dayBox）本来就装着状态徽章 / Boss 血条 / 队友行 ——
   装备放进去是**同一类东西**，也不需要再量一次位置。
   ========================================================================== */

/* ---------- 装备槽：在 dayBox 里走正常流 ---------- */
/*
 * 为什么不写成 `position: absolute` 自己定位：
 * dayBox 的高度会随内容变（状态徽章 / Boss 血条 / 队友行 / 装备行），
 * 写死 top 就变成「加一样东西就被压住一次」—— 这个坑上面已经踩过两回。
 * 走正常流 + 一个 margin-top，它自己会排在最后面。
 */
#eqBar {
  display: flex;
  gap: 5px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 2px solid rgba(20, 15, 10, 0.6);
  box-shadow: 0 1px 0 rgba(255, 216, 150, 0.10);
}
/* dayBox 是 pointer-events:none（不在 #hud 的白名单里）。
   子元素写 auto 就能重新吃到点击 —— 这是标准的做法，
   写成 `#hud > #dayBox { pointer-events: auto }` 会让整块牌子吃掉触摸。 */
#hud > #dayBox #eqBar { pointer-events: auto; }
#eqBar .eq-row {
  display: flex;
  gap: 5px;
  padding: 0; margin: 0; border: 0; box-shadow: none;
}
/* 44px：可点区域的下限，一像素都不能让。
   「装备」和「包里的东西」的视觉区分改用**描边更深 + 图标小一圈**，
   而不是把槽做小 —— 巡检会实测每一个 button 的命中区，小于 44 就报。 */
#eqBar .slot { width: 44px; height: 44px; border-radius: 8px; }
#eqBar .slot .ic { font-size: 22px; }

/* ---------- 物品栏：贴着底部的一条 ---------- */
#invBar {
  top: auto;                       /* 覆盖上面那版的 top: calc(--inv-top) */
  bottom: var(--inv-bottom);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: flex-start;
  gap: 6px;
  padding: 5px 6px;
  border-radius: 12px;
}
#invBar .inv-grid {
  display: grid;
  grid-template-columns: repeat(6, auto);
  gap: 3px;
  /*
   * 收起态只留**第一行**。
   * 用 max-height 裁而不是隐藏后面几行的 DOM：格子本来就是按顺序排的，
   * 「第一行 = 你最先捡到的六件」正好是快捷栏该有的语义。
   * 展开（#invBar.expanded）时把上限放开。
   */
  max-height: var(--slot);
  overflow: hidden;
  transition: max-height 0.18s ease;
}
#invBar.expanded .inv-grid { max-height: none; }
#invBar.expanded { box-shadow:
    inset 0 2px 0 rgba(255, 216, 150, 0.18),
    inset 0 -4px 9px rgba(0, 0, 0, 0.5),
    0 10px 30px rgba(0, 0, 0, 0.62); }

/* 「更多」按钮：和格子同高，上面写着还剩多少件 —— 不写的话
   玩家根本不知道下面还有东西（那正是「背包挡视线」换来的代价）。 */
.inv-more {
  width: 46px;
  height: var(--slot);
  flex: none;
  border-radius: 9px;
  border: 2px solid #0d0906;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.45) 0 14%, rgba(0, 0, 0, 0) 52%),
    radial-gradient(circle at 50% 30%, #4a3927, #1c140d 84%);
  color: var(--parch);
  font-size: 15px;
  font-weight: 800;
  line-height: 1;
  text-shadow: var(--outline);
  box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
.inv-more small { font-size: 10px; opacity: 0.75; font-weight: 700; }
.inv-more:active { transform: translateY(1px); }

/* ---------- 地图（全屏浮层）---------- */
.map {
  position: absolute;
  inset: 0;
  z-index: 70;
  display: flex;
  flex-direction: column;
  background: #14100c;
  touch-action: none;              /* 自己处理拖动/捏合，别让浏览器抢 */
  user-select: none;
}
.map-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(var(--safe-t) + 8px) 12px 8px;
  font-size: 15px;
  font-weight: 800;
  color: var(--parch);
  text-shadow: var(--outline);
  background: linear-gradient(180deg, #4a3927, #241a11);
  border-bottom: 3px solid var(--wood-edge);
}
.map-sub { flex: 1; font-size: 11px; font-weight: 600; color: #b6a488; }
#mapCanvas { flex: 1; display: block; width: 100%; }

.map-tools {
  position: absolute;
  right: 12px;
  bottom: calc(var(--safe-b) + 12px);
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: stretch;
}
.map-btn {
  width: 48px;
  height: 48px;                    /* 可点下限，别为了排版缩到 44 以下 */
  border-radius: 12px;
  border: 2.5px solid var(--wood-edge);
  background: linear-gradient(180deg, #6a5232, #33261a 70%, #241a11);
  color: #fff2d4;
  font-size: 19px;
  font-weight: 800;
  text-shadow: var(--outline);
  box-shadow: inset 0 2px 0 rgba(255, 226, 170, 0.22), 0 4px 12px rgba(0, 0, 0, 0.5);
}
.map-btn.wide { width: 66px; height: 40px; font-size: 13px; }
.map-btn:active { transform: translateY(1px); }

#mapLegend {
  position: absolute;
  left: 10px;
  bottom: calc(var(--safe-b) + 10px);
  right: 86px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 9px;
  padding: 6px 8px;
  border-radius: 10px;
  background: rgba(20, 15, 10, 0.72);
  border: 1.5px solid rgba(120, 98, 55, 0.5);
  font-size: 10.5px;
  color: #cbb894;
}
.map-lg { display: inline-flex; align-items: center; gap: 4px; }
.map-lg i {
  width: 9px; height: 9px; border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6);
}
.map-lg i.sq { border-radius: 2px; }
.map-lg i.tri {
  border-radius: 0; width: 0; height: 0; background: none !important;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-bottom: 9px solid #fff4d6;
}

/* ==========================================================================
   Phase 10·A：装备耐久条（护甲与工具共用）
   --------------------------------------------------------------------------
   为什么画在槽**里面**而不是 tooltip 里：
   tooltip 要点下去才看得到，而「斧头快断了」这件事必须在
   准备砍树之前就知道 —— 它决定的是「现在去不去砍」。
   断了的槽加一道斜杠：断了的东西点下去没有任何反应（handDef 当它不存在），
   没有这道标记，玩家会以为是自己手机坏了。
   ========================================================================== */
#eqBar .slot .dbar,
#boxPanel .slot .dbar {
  position: absolute;
  left: 4px; right: 4px; bottom: 3px;
  height: 3px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.55);
  box-shadow: 0 1px 0 rgba(255, 216, 150, 0.10);
  overflow: hidden;
}
#eqBar .slot .dbar i,
#boxPanel .slot .dbar i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: linear-gradient(180deg, #a8dc72, #5d9a3c);
}
#eqBar .slot .dbar i { min-width: 3px; }
.slot.low .dbar i { background: linear-gradient(180deg, #e8b24a, #a86c18); }
.slot.broken .dbar i { background: #8f2f2f; }
.slot.broken .ic { opacity: 0.32; filter: grayscale(1); }
.slot.broken { border-color: #8f2f2f; }
/* 断了的那道斜杠 */
.slot.broken::after {
  content: "";
  position: absolute;
  left: -10%; right: -10%;
  top: 50%;
  height: 2px;
  background: rgba(180, 40, 40, 0.9);
  transform: rotate(-32deg);
  pointer-events: none;
}
