/* 语音输入与游戏页 HUD 布局层，与 demo 的 style.css 分开放（style.css 保持和 demo 一致好对 diff）。
   本文件在 style.css 之后加载：等特异性下后到者胜，游戏页新 HUD 的覆盖全部收在这里。 */

#wallet-badge {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #e8eaf0;
  font-size: 12px;
  white-space: nowrap;
}
#wallet-badge.low { background: rgba(240, 82, 82, 0.25); color: #ffb4b4; }

#input-mode {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border: none;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.08);
  color: #e8eaf0;
  font-size: 16px;
  cursor: pointer;
}
#input-mode:hover { background: rgba(255, 255, 255, 0.14); }

/* display:flex 会压过 hidden 属性的 UA 样式，必须显式补一条 */
#voice-hold[hidden] { display: none; }

#voice-hold {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 38px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.08);
  color: #e8eaf0;
  font-size: 14px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none; /* 按住期间不触发滚动/下拉刷新 */
}
#voice-hold:hover { background: rgba(255, 255, 255, 0.12); }
#voice-hold.recording {
  background: rgba(240, 82, 82, 0.28);
  box-shadow: 0 0 0 2px rgba(240, 82, 82, 0.45) inset;
  animation: voice-pulse 1.2s ease-in-out infinite;
}
@keyframes voice-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(240, 82, 82, 0.45) inset; }
  50% { box-shadow: 0 0 0 4px rgba(240, 82, 82, 0.7) inset; }
}

/* ═══════════ 游戏页新 HUD（Figma 155:16197 默认 / 170:16741 开面板，2026-08-07 稿）═══════════
   画面上只留四件事：左上回首页、右上小地图、底部居中语音药丸、右下对话气泡（红点）。
   编辑模式常开（开关下线）、天气/时间全自动（药丸隐藏）、性能条/撤销板/地图切换入口都不再露出。 */

/* —— 左上：退出（252:19582，2026-09-16 改稿，原来是「首页」小屋）——
   钮面全是 CSS（0.8px 白 .4 描边 + rgba(38,46,62,.7) 底 + 外发光 + 内高光），
   只有钮里那枚白色图形走切图，稿面逐条都对得上，这次只换图和文案。 */
#home-entry {
  position: fixed;
  top: calc(16px + env(safe-area-inset-top, 0px));
  left: max(16px, env(safe-area-inset-left, 0px));
  z-index: 21;
  display: flex; align-items: center; gap: 10px;
}
#go-home {
  position: static;            /* 盖掉旧的 fixed 定位（挪进 #home-entry 排版） */
  width: 36px; height: 36px; min-width: 0; min-height: 0;
  display: grid; place-items: center;
  border: 0.8px solid rgba(255, 255, 255, 0.4);
  border-radius: 8px;
  background: rgba(38, 46, 62, 0.7);
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.25), inset 0 0 4px rgba(255, 255, 255, 0.25);
  cursor: pointer; padding: 0;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
/* 退出图标（927:7175）稿面是 22×22，比原来那枚小屋（18）大一圈。
   拆解态换成 shop/ic_back.svg —— 那张净图本身就是 18×18，别跟着放大到 22，
   放大等于把 18 的线稿拉粗，跟 HUD 里其它 18 的图标就不是一套了。 */
#go-home img { width: 22px; height: 22px; display: block; }
#home-entry.is-back #go-home img { width: 18px; height: 18px; }
#go-home:active { transform: scale(0.94); }
.home-label { font: 600 15px/1 "PingFang SC", system-ui, sans-serif; color: #fff; }

/* —— 右上：小地图（120×120，圆角 18；canvas 内部仍 336 保 retina）—— */
#minimap {
  left: auto !important;
  bottom: auto !important;
  right: var(--map-right) !important;
  top: var(--map-top) !important;
  width: var(--map-size) !important; height: var(--map-size) !important;
  /* 圆形小地图（2026-09-15 拍板：方块不优雅，做成 PUBG 那种圆盘）。
     canvas 内容画满整块，border-radius:50% 直接把它裁成圆。
     不用黑边——细亮环（白 0.5）+ 内圈高光 + 柔和外发光起「浮在画面上的圆盘」的感觉。 */
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.5);
  box-shadow:
    0 4px 18px rgba(0, 0, 0, 0.32),        /* 柔和落影托起圆盘，非硬边框 */
    inset 0 0 0 1px rgba(255, 255, 255, 0.14),  /* 内圈一道细高光，边缘更精致 */
    inset 0 2px 6px rgba(255, 255, 255, 0.10);
}
#sheet-handle { display: none !important; } /* 旧的移动端浮标被面板按钮取代 */

/* —— 下线的旧 HUD：编辑开关/性能条已从 DOM 移除；这里兜住 JS 还会创建的 —— */
#undo-bar { display: none !important; }                    /* 桌面 ⌘Z 仍可用，浮板不再露出 */
#weather-time-toggle, #weather-toggle,
#weather-time-menu, #weather-menu { display: none !important; }  /* 天气/时间全自动（2026-08-07 拍板） */

/* —— 底部居中（2026-09-17 用户拍板「把两个合并成同一个东西」）——
   气泡并进药丸当左帽之后，底部只剩【一件】，老老实实 translateX(-50%) 居中即可。
   随之删掉的三样：--pill-shift / --toggle-tuck（两件分-合的位移）、--pill-w（main.js 量药丸宽
   给气泡定位）。生成中原来要把气泡缩到药丸底下、药丸回正中，现在没有两件要对齐，全不需要了。 */

/* —— 语音药丸（170:16582）—— */
#mic-pill {
  position: fixed;
  left: 50%;
  bottom: var(--dock-bottom);
  transform: translateX(-50%);
  z-index: 30;
  height: 46px;
  /* gap 14 = 左帽气泡 ↔ 文案；文案 ↔ 麦克风仍是稿面的 24（#big-mic 自己补 10 的左外边距），
     两段间距不同，所以不能只靠一个 gap。 */
  display: flex; align-items: center; gap: 14px;
  padding: 4px 3px 4px 4px;
  border-radius: 100px;
  background: rgba(32, 36, 34, 0.85);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); /* Figma BACKGROUND_BLUR=10（生成代码给的 5 是半倍缩略值） */
  border: 0.8px solid rgba(255, 255, 255, 0.05);
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.4), inset 0 0 4px rgba(255, 255, 255, 0.25);
  transition: opacity 0.25s ease, transform var(--dur-3, 0.38s) var(--ease-expo, ease);
}
/* 整条药丸就是「打开搭建面板」那颗按钮（index.html 上的 role="button"，main.js micPillEl 监听）。
   按下音由 src/audio.js 统一给所有 button / [role=button]，这里只补视觉：底色提亮一档。
   里头的麦克风 / ✕ 按下时不跟着亮——那两颗有自己的按下效果，整条再亮一次像点错了地方。
   :has 不支持的引擎上退化成"整条也亮"，不影响功能。
   录音态整条不是按钮（左帽藏起、点击不接管、data-nosfx 停按下音），所以也不给按下反馈。 */
#mic-pill { cursor: pointer; }
#mic-pill:active { background: rgba(52, 58, 55, 0.92); }
#mic-pill:active:has(#big-mic:active, #mic-cancel:active),
#mic-pill.rec:active { background: rgba(32, 36, 34, 0.85); }
/* 状态文案的换字：上一条向上滚出、下一条从下方滚入，同时容器宽度做过渡
   （2026-08-18 用户报障「状态生硬切换、宽度也生硬变化」）。
   动效参数直接复用对话面板那套 token（--dur-3 / --ease-expo），两处观感一致。

   【为什么要显式宽度】药丸宽度是内容撑出来的，auto → auto 无法过渡；所以
   setLive 每次量出新文案的自然宽度写到这里，由下面这条 transition 接管。
   配合 flex: none —— 否则 flex 会把这个显式宽度再压缩掉，过渡就走样。 */
#mic-live {
  position: relative;
  flex: none;
  color: rgba(255, 255, 255, 0.5);   /* 兜底：万一有人往这层塞裸文本，也别继承成页面深色 ink（"已取消"黑字那次） */
  height: 20px;                 /* 字号 14 + 上下余量：emoji 墨迹盒 ~1.3em，14px 高会裁"✅"顶脚（2026-08-19 报障） */
  max-width: 300px;
  overflow: hidden;
  transition: width var(--dur-3) var(--ease-expo);
}
#mic-live .live-line {
  position: absolute; left: 0; top: 0;
  width: max-content; max-width: 300px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 800 14px/20px "PingFang SC", system-ui, sans-serif;   /* 行高=容器高，emoji 不再被裁 */
  color: rgba(255, 255, 255, 0.5);
  /* 换字过渡不用 ease-expo（2026-08-19 报障"退场瞬间不可见、跳动感强"）：
     视窗只有一行高，expo 前段太快，旧行一动就冲出裁剪窗=肉眼看是瞬间消失。
     缓起缓收曲线 + 行程缩到 80%，进出都能看清滑动与淡入淡出。 */
  transition: transform var(--dur-3) cubic-bezier(0.4, 0.1, 0.25, 1),
              opacity   var(--dur-3) cubic-bezier(0.4, 0.1, 0.25, 1);
}
#mic-live .live-line.enter { transform: translateY(80%); opacity: 0; }    /* 待入场：在下方 */
#mic-live .live-line.leave { transform: translateY(-80%); opacity: 0; }   /* 出场：向上滑走 */
/* 角色配色（2026-08-19 二修：只染「设计师/建筑工/质检员」几个字，其余文案保持原灰白）
   ——角色名由 rollLive 包进 <b>，颜色按行上的 role-* 类给 */
#mic-live .live-line b { font: inherit; }
#mic-live .live-line.role-designer b { color: #ffb15e; }
#mic-live .live-line.role-builder  b { color: #8fd672; }
#mic-live .live-line.role-qc       b { color: #7ec8ff; }
/* 两级滚动（2026-08-19 拍板）：同角色只换右半段——前缀「设计师 · 」钉住，
   文案在行内自己的小视窗（.lv-rest-wrap）里上下滚；角色切换才整行滚。
   vertical-align:top 必须给：inline-block+overflow:hidden 的基线在盒底，不校正会整体下坠。 */
#mic-live .lv-rest-wrap {
  position: relative; display: inline-block; vertical-align: top;
  height: 20px; overflow: hidden;
  transition: width var(--dur-3) var(--ease-expo);
}
#mic-live .lv-rest {
  position: absolute; left: 0; top: 0; width: max-content; white-space: nowrap;
  transition: transform var(--dur-3) cubic-bezier(0.4, 0.1, 0.25, 1),
              opacity   var(--dur-3) cubic-bezier(0.4, 0.1, 0.25, 1);
}
#mic-live .lv-rest.enter { transform: translateY(80%); opacity: 0; }
#mic-live .lv-rest.leave { transform: translateY(-80%); opacity: 0; }
#big-mic {
  margin-left: 10px;    /* 与 #mic-pill 的 gap:14 相加 = 稿面的 24 */
  position: relative;   /* 涟漪 ::before/::after 的锚（否则会锚到整条药丸变大椭圆） */
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center; padding: 0;
  border: 0.8px solid rgba(255, 229, 133, 0.7);
  border-radius: 20px;
  background: #f25b17;
  box-shadow: 0 0 4px rgba(255, 81, 0, 0.9), inset 0 0 4px rgba(255, 171, 92, 0.7);
  cursor: pointer;
  user-select: none; -webkit-user-select: none; touch-action: none;
}
#big-mic img { width: 24px; height: 24px; display: block; position: relative; z-index: 1; }
#big-mic.recording {
  animation: mic-glow 1.2s ease-in-out infinite;
}
@keyframes mic-glow {
  0%, 100% { box-shadow: 0 0 4px rgba(255, 81, 0, 0.9), inset 0 0 4px rgba(255, 171, 92, 0.7); }
  50% { box-shadow: 0 0 14px rgba(255, 81, 0, 1), inset 0 0 6px rgba(255, 199, 130, 0.9); }
}
/* 录音中：两圈光波从按钮散开（错峰 0.9s 循环） */
#big-mic.recording::before, #big-mic.recording::after {
  content: ""; position: absolute; inset: -1px; border-radius: 50%;
  border: 2px solid rgba(255, 130, 50, 0.65);
  animation: mic-ripple 1.8s ease-out infinite;
  pointer-events: none;
}
#big-mic.recording::after { animation-delay: 0.9s; }
@keyframes mic-ripple {
  0% { transform: scale(1); opacity: 0.85; }
  100% { transform: scale(2.1); opacity: 0; }
}
#big-mic:disabled { opacity: 0.55; }

/* ── 录音态布局（2026-08-07 效果稿）：✕ | 波形 | 在听… | 实时转写 | 🎤 ──
   默认态只显示引导文案；.rec 时换成录音件套，容器宽度随内容自然过渡 */
#mic-cancel, #mic-wave, #mic-listen, #mic-partial, #mic-tips, #mic-type { display: none; }
/* 「改用打字」：录音态才出现。【挂在药丸外面、右侧 12px】（2026-09-18 用户：放胶囊外面更好点到）——
   仍是药丸的子元素、绝对定位，药丸怎么进退场/变宽它都跟着；46 圆、与药丸同一块深色玻璃底，
   不放进药丸里挤成 36 的小圆。同 #mic-tips 一样，药丸不裁溢出，浮在外面没问题。 */
#mic-pill.rec #mic-type {
  display: grid; place-items: center;
  position: absolute; left: calc(100% + 12px); top: 50%; transform: translateY(-50%);
  width: 46px; height: 46px; padding: 0;
  border: 0.8px solid rgba(255, 255, 255, 0.05); border-radius: 50%;
  background: rgba(32, 36, 34, 0.85);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  cursor: pointer;
}
#mic-pill.rec #mic-type img { width: 24px; height: 24px; display: block; opacity: .92; }
#mic-pill.rec #mic-type:active { transform: translateY(-50%) scale(0.92); }
/* 选中建筑时药丸本来藏着（下面 body.has-selection #mic-pill），但「调整」现在直接进录音态，
   录音中必须露出来（2026-09-18 用户：点调整直接弹麦克风，不默认弹键盘）。 */
body.has-selection #mic-pill.rec { opacity: 1; pointer-events: auto; transform: translateX(-50%); }
#mic-pill.rec #mic-live { display: none; }
#mic-pill.rec { gap: 12px; padding-left: 5px; }
#mic-pill.rec #big-mic { margin-left: 0; }   /* 录音态自己有一套间距（12），别再加那 10 */
#mic-pill.rec #mic-cancel {
  display: grid; place-items: center;
  width: 36px; height: 36px; flex: none; padding: 0;
  border: none; border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.8);
  font-size: 15px; line-height: 1;
  cursor: pointer;
}
#mic-pill.rec #mic-cancel:active { transform: scale(0.92); }
#mic-pill.rec #mic-wave {
  display: flex; align-items: center; gap: 2.5px; height: 24px; flex: none;
}
#mic-wave i {
  width: 3px; height: 4px; border-radius: 2px;
  background: linear-gradient(180deg, #ffb066, #ff5f1f);
  box-shadow: 0 0 5px rgba(255, 105, 30, 0.35);
  opacity: 0.75;                    /* 光谱弱一档，别抢文字 */
  transition: height 0.09s ease;
}
#mic-pill.rec #mic-listen {
  display: block; flex: none;
  font: 800 15px/1 "PingFang SC", system-ui, sans-serif; color: #fff;
}
/* 识别到内容：真实文本顶掉「在听…」 */
#mic-pill.rec.has-partial #mic-listen { display: none; }
#mic-pill.rec #mic-partial {
  display: block; max-width: 200px;
  font: 500 14px/1.3 "PingFang SC", system-ui, sans-serif;
  color: rgba(255, 255, 255, 0.55);
  /* 超宽不截省略号：overflow 藏 + JS 每次更新 scrollLeft=scrollWidth，始终看到最新的尾部 */
  white-space: nowrap; overflow: hidden;
}
#mic-pill.rec #mic-partial:empty { display: none; }
/* tips 挂在药丸正下方（再点一下发送） */
/* tips 挂在药丸【上方】（2026-08-19 拍板：点录音不许挪动药丸整体位置——
   原来 tips 放下方、录音态整个药丸抬高 30px 腾位，视觉上就是"按钮跳了一下"）。
   上方是开阔场景区，浮着不占位，药丸的 bottom 全程不变。 */
#mic-pill.rec #mic-tips {
  display: block; position: absolute; bottom: calc(100% + 8px); left: 50%;
  transform: translateX(-50%);
  padding: 5px 12px; border-radius: 100px;
  background: rgba(32, 36, 34, 0.85);
  border: 0.8px solid rgba(255, 255, 255, 0.05);
  font: 500 12px/1 "PingFang SC", system-ui, sans-serif; font-style: normal;
  color: rgba(255, 255, 255, 0.75);
  white-space: nowrap;
}
/* 打开面板 = 打字模式：语音药丸过渡淡出（不瞬移），关面板回来 */
body:not(.chat-hidden) #mic-pill { opacity: 0; transform: translateX(-50%) translateY(10px); pointer-events: none; }

/* —— 对话气泡（170:16536）：药丸的【左帽】——
   2026-09-17 用户拍板「把左边的气泡删掉，两个合并成同一个东西」。原来是贴在药丸左边的独立
   fixed 圆钮（46 切图底 + 一整套跟着药丸宽度滑的定位），现在就是药丸里的第一个 flex 子元素：
   位置由 flex 排，药丸换字变宽/整条进退场，它自然跟着走，那套 right / --pill-w / --toggle-tuck
   的对齐机关全部作废。
   底色改用药丸内部的玻璃圆（同 #mic-cancel 的口径），不再用首页那块 46×46 切图——切图是给
   【场景上的浮钮】用的，摆进深色药丸里会多一圈突兀的实心底。 */
#panel-toggle {
  position: relative;          /* 红点的锚 */
  width: 38px; height: 38px; flex: none; padding: 0;
  display: grid; place-items: center;   /* 图标/emoji 都落在同一个格子里，换的时候不跳 */
  border: none; border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  font-size: 0;                /* 兜底：里头只有图标 */
}
#panel-toggle > img { width: 22px; height: 21px; display: block; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35)); }
/* 角色态：角色头像顶掉气泡切图。口径同对话面板的流程卡头像（style.css .flow-av img）——
   圆形裁切 + object-position 顶部对齐，让露出来的是脑袋不是胸口；撑满整颗左帽。 */
#panel-toggle > .role-ico {
  display: none; width: 38px; height: 38px;
  object-fit: cover; object-position: top center; border-radius: 50%;
}
#panel-toggle.as-role > img { display: none; }
#panel-toggle.as-role > .role-ico { display: block; }
/* 录音态：左帽整颗让位给 ✕（2026-09-17 用户：不该让关闭按钮跟气泡一起出来） */
#mic-pill.rec #panel-toggle { display: none; }
#panel-toggle .dot {
  position: absolute; right: 1px; top: 0;   /* 与首页红点同位同环 */
  width: 8px; height: 8px; border-radius: 4px;
  background: #ff3232; box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.25);
  display: none;
}
#panel-toggle.unread .dot { display: block; }

/* —— 对话面板（170:16779 浮窗化：301 宽、顶到 16 刚好盖住小地图顶）—— */
/* 面板几何只写一处：收起把手挂在面板【外侧】，得按同样的边距和宽度对齐；
   两边各抄一份数字的话，改一个忘一个就会错位。 */
:root {
  --chat-top: calc(16px + env(safe-area-inset-top, 0px));
  --chat-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  --chat-right: max(24px, env(safe-area-inset-right, 0px));
  --chat-w: 301px;
}
#chat {
  top: var(--chat-top);
  right: var(--chat-right);
  bottom: var(--chat-bottom);
  width: var(--chat-w);
  padding: 12px 0;
  gap: 10px;
  background: rgba(31, 36, 34, 0.85);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); /* Figma BACKGROUND_BLUR=10（生成代码给的 5 是半倍缩略值） */
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 20px;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.4), inset 0 0 4px rgba(255, 255, 255, 0.25);
  color: #fff;
}
/* 开/收都走过渡：右滑出屏 + 淡出（visibility 延迟到动画完，收起后不挡点击） */
body #chat {
  display: flex;
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.35, 1), opacity 0.28s ease, visibility 0s linear 0s;
}
body.chat-hidden #chat {
  display: flex;               /* 盖掉旧的 display:none，改用位移隐藏才有过渡 */
  transform: translateX(calc(100% + 40px));
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.35, 1), opacity 0.28s ease, visibility 0s linear 0.28s;
}

/* 头行 */
#chat-head {
  padding: 0 12px; margin: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: none; border: none; box-shadow: none; min-height: 40px;
}
.ph-user { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ph-ava { position: relative; width: 40px; height: 40px; flex: none; }
.ph-ava .ring, .ph-ava .glow { position: absolute; inset: 0; width: 40px; height: 40px; }
.ph-ava .face {
  position: absolute; left: 2px; top: 2px;
  width: 36px; height: 36px; border-radius: 50%;
  object-fit: cover; object-position: top center;
}
#ph-name { font: 800 15px/1 "PingFang SC", system-ui, sans-serif; color: #fff; letter-spacing: -0.13px; }
.ph-right { display: flex; align-items: center; gap: 10px; }
#quota-pill {
  height: 32px; padding: 0 12px;
  display: inline-flex; align-items: center; gap: 8px;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.05);
  box-shadow: none;
}
#quota-pill img { width: 18px; height: 18px; object-fit: contain; }
#quota-pill b { font: 800 12px/1 "PingFang SC", system-ui, sans-serif; color: #fff; letter-spacing: -0.13px; font-variant-numeric: tabular-nums; }
/* 收起把手：面板【左外侧】的一片竖标签，箭头朝右＝往右推走。
   竖直居中不用 translateY —— 上下都钉到面板的同一对边距、再给 margin: auto 0，
   由过度约束自动居中；这样 transform 只留给"收起时跟着面板一起右滑"，两者不打架。 */
#sheet-collapse {
  position: fixed;
  /* 往面板里【压 1px】并抬到面板之上：面板自己有一圈 box-shadow，把手若在它下面，
     那圈阴影会正好糊在接缝上，看起来像被切成两块（2026-08-19 用户报障）。
     压 1px 是为了盖掉面板的左边框，这样两块看着是连成一体的。 */
  right: calc(var(--chat-right) + var(--chat-w) - 1px);
  top: var(--chat-top); bottom: var(--chat-bottom);
  height: 84px; margin: auto 0;
  width: 26px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.07); border-right: none;
  border-radius: 12px 0 0 12px;
  background: rgba(31, 36, 34, 0.85);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.35);
  cursor: pointer; color: #fff;
  z-index: calc(var(--z-panel) + 1);
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.35, 1), opacity 0.28s ease, visibility 0s linear 0s;
}
#sheet-collapse img { width: 16px; height: 16px; display: block; }   /* 切图本身就朝右，不转 */
#sheet-collapse:hover { background: rgba(46, 53, 50, 0.9); }
#sheet-collapse:active { background: rgba(58, 66, 62, 0.95); }
/* 收起时跟面板同一条曲线一起右滑出屏，不能留在原地 */
body.chat-hidden #sheet-collapse {
  transform: translateX(calc(var(--chat-w) + 40px));
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.35, 1), opacity 0.28s ease, visibility 0s linear 0.28s;
}
.ph-divider { height: 1px; background: rgba(255, 255, 255, 0.1); flex: none; }

/* 消息流 */
#messages { padding: 0 12px; background: none; }
#messages::-webkit-scrollbar { width: 0; }
#chat .msg.bot {
  max-width: 100%;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  color: #fff;
  font: 500 13px/1.5 "PingFang SC", system-ui, sans-serif;
  letter-spacing: -0.13px;
  padding: 8px;
}
/* 用户消息不动：沿用 style.css 原版橙色渐变气泡（2026-08-07 用户拍板） */

/* ── 选中工具条（Figma 170:16949）：居中玻璃条 = 4 圆钮 + 竖分割 + 红糖果「完成」 ──
   注意特异性：style.css 有 #toolbar button / #toolbar #delete / #toolbar #deselect 的旧皮，
   这里全部用同级或更高选择器压平；旧 msg-in 动画的 transform 关键帧会顶掉 translateX
   （fill:both 的坑）→ animation:none。 */
#toolbar {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  /* 与语音药丸同一离底距离（见 style.css :root --dock-bottom）。
     工具条和药丸是【互斥出现】的——选中物体时药丸淡出、工具条露面——两者离底
     不一致的话，选中/取消选中会看到底部跳一下（原来 30px vs 13px，差 17px）。 */
  bottom: var(--dock-bottom);
  top: auto; right: auto;
  z-index: 26;
  width: max-content;
  animation: none;
  display: flex; align-items: center; justify-content: center; gap: 16px;
  padding: 8px 16px;
  border-radius: 16px;
  background: rgba(31, 36, 34, 0.85);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); /* Figma BACKGROUND_BLUR=10（生成代码给的 5 是半倍缩略值） */
  border: 1px solid rgba(255, 255, 255, 0.07);
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.4), inset 0 0 4px rgba(255, 255, 255, 0.25);
}
#toolbar[hidden] { display: none; }
/* 旧按钮皮整体清零（含 #delete 红底、hover 浮起） */
#toolbar button, #toolbar #delete, #toolbar #duplicate, #toolbar #detail-edit, #toolbar #soul-edit {
  background: none; border: none; box-shadow: none; padding: 0;
  min-width: 0; width: auto; height: auto; color: inherit; font: inherit; border-radius: 0;
}
#toolbar button:hover, #toolbar #delete:hover { background: none; transform: none; }
#toolbar .tb-act {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  cursor: pointer;
}
#toolbar .tb-act .tb-ico {
  width: 36px; height: 36px;
  display: grid; place-items: center;
  /* 与首页顶部功能钮同一块底（home_toolButton_bg 切图缩放到 36），不再自画玻璃 */
  background: url("assets/homeui/home_toolButton_bg.png") center / 36px 36px no-repeat;
  border: none; border-radius: 50%;
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.28));
}
#toolbar .tb-act .tb-ico img { width: 20px; height: 20px; object-fit: contain; }
#toolbar .tb-act i {
  font: 400 11px/1.4 "PingFang SC", system-ui, sans-serif; font-style: normal;
  color: rgba(255, 255, 255, 0.7); letter-spacing: -0.13px;
}
#toolbar .tb-act.soul i { color: #ffa600; }
#toolbar .tb-act:active .tb-ico { transform: scale(0.92); }
#toolbar .tb-div { width: 1px; align-self: stretch; background: rgba(255, 255, 255, 0.2); }
/* 「完成」按新稿 252:19642（2026-09-10 对稿）：86×38 圆角 8，橙渐变 #fa7d4b→#fa612b，
   下唇 2px 深橙 #b73100 + 上缘 .5px 奶黄高光（都是 inset，不占尺寸），Inter 800 15px 白字带投影。
   旧版是 44 高红渐变（#ff7e6d→#f13b3d）+ 四道内阴影，与稿面不符。
   这套「橙糖果」就是游戏内小按钮的统一皮：新手引导的「下一步」同配方只缩尺寸（src/guide.js .gd-next）。
   按下手感与全局一致（2026-09-08 拍板）：轻微缩小 + 下沉 + 压 10% 黑，不再用 padding-top 顶字。 */
#toolbar #deselect {
  width: 86px; height: 38px; min-height: 38px; padding: 0; border: none; cursor: pointer;   /* min-height 压掉 style.css 的 40px 触控下限，否则量出来 40 */
  border-radius: 8px;
  background: linear-gradient(180deg, #fa7d4b, #fa612b);
  box-shadow: inset 0 -2px 0.5px #b73100, inset 0 0.5px 0 rgba(255, 254, 183, 0.57);
  font: 800 15px/1 "Inter", "PingFang SC", sans-serif; color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: transform 0.08s ease, filter 0.08s ease;
}
#toolbar #deselect:hover { background: linear-gradient(180deg, #fa7d4b, #fa612b); transform: none; }
#toolbar #deselect:active { transform: translateY(1px) scale(0.96); filter: brightness(0.9); padding: 0; }

/* ── 选中态的「调整」流程（2026-09-16 用户拍板）──
   ① 对话面板开着（点「调整」打开的，或选中时面板本来就开着）：工具条 / 升降 / 拨盘 / 已装芯彩带
      一起让位——画面上只留面板，打字不被挡（08-20 报过选中态控件和面板叠在一起）。
   ② 搭建中收起面板：仍是选中态（选中框、贴地转盘都在），但底部功能入口收掉，换成语音药丸那条
      建造进程文案：滚动换字、右边停止钮、点左边开面板，全是药丸现成的，不另做一份。
      这段时间也不该去复制 / 删除 / 挪正在被改的东西。
   ③ 建完或收起面板、功能入口重新露面：与刚选中时同一套进场（.hud-in，main.js playSelectionHUDEnter 挂上，
      见下面「选中态三件控件的进出场」）。 */
body.has-selection.gen-busy.chat-hidden #mic-pill { opacity: 1; pointer-events: auto; transform: translateX(-50%); }
#toolbar { transition: opacity 0.2s ease, visibility 0s linear 0s; }
/* ④ 选中态录音中（body.mic-rec，点「调整」直接进的录音态，2026-09-18 用户）：同 ① 一样让位。
      点 ✕ 取消 → 录音态结束 → 控件回来；点「改用打字」→ 面板打开 → 走 ①，收面板没输入 → 也回来；
      说完发送 → 生成中走 ②。 */
body.has-selection:not(.chat-hidden) #toolbar,
body.has-selection.mic-rec #toolbar,
body.has-selection.gen-busy #toolbar {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}
body.has-selection:not(.chat-hidden) :is(#lift, #rot, #soul-installed),
body.has-selection.mic-rec :is(#lift, #rot, #soul-installed),
body.has-selection.gen-busy :is(#lift, #rot, #soul-installed) {
  opacity: 0 !important; visibility: hidden; pointer-events: none !important;   /* 彩带的 opacity / pointer-events 是 soul.js 写的行内样式 */
  transition: opacity 0.2s ease, visibility 0s linear 0.2s !important;
}

/* ── 选中态三件控件的进出场（2026-09-16 用户拍板「这样更自然」）──
   进场（.hud-in，main.js playSelectionHUDEnter 挂）：工具条从底部升起、里面的钮再逐个错峰冒头；
   旋转拨盘从下往上；右侧升降油门从右往左滑入。药丸和气泡同时往下退（上面 body.has-selection 那两条）。
   工具条比药丸晚 60ms 起步：先让药丸退开，别在同一处撞车。
   退场（.hud-out，deselect 挂，放完才 hidden）：各自退回来路，短一点、ease-in——收东西不用像出场那么隆重。
   两组关键帧都把 translateX(-50%) 写全：#toolbar / #rot 靠它居中，关键帧里漏了会先跳到左边再动。
   fill 用 backwards 不用 both：动画完就放手，别把 transform 钉死、压掉「完成」钮的按下缩放。 */
@keyframes hud-up   { from { opacity: 0; transform: translateX(-50%) translateY(48px); } to { opacity: 1; transform: translateX(-50%); } }
@keyframes hud-down { to   { opacity: 0; transform: translateX(-50%) translateY(48px); } }
@keyframes hud-from-right { from { opacity: 0; transform: translateX(56px); } to { opacity: 1; transform: none; } }
@keyframes hud-to-right   { to   { opacity: 0; transform: translateX(56px); } }
@keyframes tb-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
#toolbar.hud-in, #rot.hud-in { animation: hud-up var(--dur-3, 0.38s) var(--ease-expo, ease) 60ms backwards; }
#lift.hud-in { animation: hud-from-right var(--dur-3, 0.38s) var(--ease-expo, ease) 60ms backwards; }
#toolbar.hud-out, #rot.hud-out { animation: hud-down 0.22s ease-in forwards; }
#lift.hud-out { animation: hud-to-right 0.22s ease-in forwards; }
#toolbar.hud-in > * { animation: tb-rise var(--dur-3, 0.38s) var(--ease-expo, ease) 60ms backwards; }
#toolbar.hud-in > :nth-child(2) { animation-delay: 100ms; }
#toolbar.hud-in > :nth-child(3) { animation-delay: 140ms; }
#toolbar.hud-in > :nth-child(4) { animation-delay: 180ms; }
#toolbar.hud-in > :nth-child(5) { animation-delay: 220ms; }
#toolbar.hud-in > :nth-child(6) { animation-delay: 260ms; }
#toolbar.hud-in > :nth-child(7) { animation-delay: 300ms; }
@media (prefers-reduced-motion: reduce) {
  #toolbar.hud-in, #rot.hud-in, #lift.hud-in, #toolbar.hud-out, #rot.hud-out, #lift.hud-out, #toolbar.hud-in > * { animation: none; }
}

/* ── 左下虚拟摇杆（新稿 170:17116，2026-08-07 二版）：底环 + 四方向箭头 + knob 全切图 ── */
#stick {
  position: fixed;
  /* 贴左下角：底边与底部功能区（药丸/工具条，都是 --dock-bottom）齐平，
     左间距 = 底间距（2026-08-18 用户拍板）。 */
  left: var(--dock-left);
  bottom: var(--dock-bottom);
  width: var(--stick-size); height: var(--stick-size);
  /* 层级压过工具条(26)：两者在窄屏上必然重叠（都贴底、工具条又几乎占满屏宽），
     摇杆在下面的话，冲着摇杆按下去实际点到的是【删除】按钮——实测 375×812 下
     摇杆下缘 (105,790) 命中的是 #delete。宁可挡住按钮，也不能误删。
     仍低于语音药丸(30)：药丸居中、与摇杆只有一小条交叠，让药丸在上更合理。 */
  z-index: 27;
  touch-action: none;
  user-select: none; -webkit-user-select: none;
  opacity: 0.2;                          /* 闲置退后（2026-08-08 定：20%），拖动时全亮 */
  /* 2026-09-17 起摇杆钉死在这里：原来 JS 会把整条 transform 到手指下、松手再滑回来，
     那个 transform 过渡跟着一起撤了（见 main.js stickPoint 那段）。 */
  transition: opacity 0.2s ease;
}
#stick.active { opacity: 1; }
#stick .stick-base { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
/* 四方向示意箭头（Frame 162~165，16×16）：对称贴大圆内缘 4px（2026-08-08 口径确认：
   稿里右/下的 196 是摆歪的，以「四向距边缘 4px」为准） */
/* 箭头本身不缩放（16px 再小就糊了），只把定位改成相对居中 + 固定 4px 边距，
   这样任何环径下都贴着内缘四正方向。 */
#stick .stick-dir { position: absolute; width: 16px; height: 16px; pointer-events: none; }
#stick .stick-dir.up { left: calc(50% - 8px); top: 4px; }
#stick .stick-dir.down { left: calc(50% - 8px); bottom: 4px; }
#stick .stick-dir.left { left: 4px; top: calc(50% - 8px); }
#stick .stick-dir.right { right: 4px; top: calc(50% - 8px); }
/* 摇杆 knob 用【纯 CSS 圆】而不是切图（2026-08-18 用户报障「中心圆边缘有锯齿」）。
   原来是 assets/gamehud/stick_knob.svg，整个圆包在一个 SVG filter 里（外阴影
   stdDeviation 4 + 内阴影 stdDeviation 9）。SVG filter 的输出有自己的光栅化分辨率，
   不保证跟着 devicePixelRatio 走，dpr 3 的手机上边缘就可能出阶梯。
   border-radius + box-shadow 由合成器按设备分辨率直接画，不经过位图也不经过滤镜，
   在任何引擎上都是平滑的。数值 1:1 对着稿子：可见圆 76(r38)、填充白 20%、
   外发光 blur 8 黑 40%、内发光 blur 18 白 50% 内缩 1
   （Figma 的 blur 值 = SVG stdDeviation×2，所以 4→8、9→18）。 */
#stick-knob {
  position: absolute; left: 50%; top: 50%;
  /* 全部按环径的比例走：原稿 200 环配 76 的 knob = 38%，阴影 8/18 = 4%/9%。 */
  width: calc(var(--stick-size) * 0.38); height: calc(var(--stick-size) * 0.38);
  margin: calc(var(--stick-size) * -0.19) 0 0 calc(var(--stick-size) * -0.19);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 0 calc(var(--stick-size) * 0.04) rgba(0, 0, 0, 0.4),
              inset 0 0 calc(var(--stick-size) * 0.09) 1px rgba(255, 255, 255, 0.5);
  pointer-events: none;
  transition: transform 0.18s ease;      /* 松手回中的弹性；拖动时 JS 关掉 */
}
body.detail-mode #stick, body.soul-stage #stick { display: none !important; }

/* ── 右下镜头摇杆 #stick2（2026-09-15，参照大疆：上下=升降，左右=原地转视角）──
   与左摇杆同一套切图、同一个尺寸变量，贴右下角，底边与左摇杆齐平（2026-09-15 用户拍板：
   原来抬在对话气泡上面，气泡已挪到药丸左边，右下角整个让给它）。
   选中物体时工具条铺满底部，摇杆 z 仍压过工具条（同左摇杆的取舍：宁可挡按钮不能误删）。 */
#stick2 {
  position: fixed;
  right: max(var(--dock-gap), env(safe-area-inset-right, 0px));
  bottom: var(--dock-bottom);
  width: var(--stick-size); height: var(--stick-size);
  z-index: 27;
  touch-action: none;
  user-select: none; -webkit-user-select: none;
  opacity: 0.2;
  transition: opacity 0.2s ease;
}
#stick2.active { opacity: 1; }
#stick2 .stick-base { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
#stick2 .stick-dir { position: absolute; width: 16px; height: 16px; pointer-events: none; }
#stick2 .stick-dir.up { left: calc(50% - 8px); top: 4px; }
#stick2 .stick-dir.down { left: calc(50% - 8px); bottom: 4px; }
#stick2 .stick-dir.left { left: 4px; top: calc(50% - 8px); }
#stick2 .stick-dir.right { right: 4px; top: calc(50% - 8px); }
#stick2-knob {
  position: absolute; left: 50%; top: 50%;
  width: calc(var(--stick-size) * 0.38); height: calc(var(--stick-size) * 0.38);
  margin: calc(var(--stick-size) * -0.19) 0 0 calc(var(--stick-size) * -0.19);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 0 calc(var(--stick-size) * 0.04) rgba(0, 0, 0, 0.4),
              inset 0 0 calc(var(--stick-size) * 0.09) 1px rgba(255, 255, 255, 0.5);
  pointer-events: none;
  transition: transform 0.18s ease;
}
body.detail-mode #stick2, body.soul-stage #stick2 { display: none !important; }
body:not(.chat-hidden) #stick2 { opacity: 0; pointer-events: none; transition: opacity 0.25s ease; }
/* 打开对话面板时摇杆让路（面板在右，摇杆在左可留——但录音药丸展开会压到，先一并淡出） */
body:not(.chat-hidden) #stick { opacity: 0; pointer-events: none; transition: opacity 0.25s ease; }

/* ── 右侧升降油门（选中出现）：上滑升 / 下滑降，离心越远越快，松手回中 ── */
#lift {
  position: fixed;
  /* 2026-09-16 用户拍板：挪到旋转拨盘（#rot，底部居中 168 宽）右边、底边对齐——
     原来贴右侧居中，选中物体后与右下的镜头摇杆 #stick2 重叠。油门 46×168，底边与拨盘同一条线。
     2026-09-17 用户：右缘与底部工具条 #toolbar 的右缘对齐。工具条是按内容宽度居中的
     （width:max-content，随语言 / 按钮增减变），纯 CSS 拿不到它的宽，main.js 用 ResizeObserver
     量出半宽写进 --tb-half（工具条隐藏时不写，保住上一次的值）；没量到时按旧值 146 = 拨盘半宽 84 + 16 + 46。
     JS 只监听指针，不写 #lift 的 transform。 */
  left: calc(50% + var(--tb-half, 146px) - 46px);
  bottom: var(--rot-bottom);
  --lift-h: 168px;   /* 与 .lift-track 的 height 一致；SVG 是定高的 */
  transform: none;
  z-index: 24;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
#lift[hidden] { display: none; }
/* 沙漏形：两端宽、中间窄——形状本身就在说「越靠两端越快」，不用文字解释。
   【用 SVG 画，不用 clip-path】：clip-path 只能切直边多边形，四角和腰都是尖的，
   而且会连 border/border-radius 一起裁掉、还会把子元素（旋钮）一并切了
   （2026-08-14 用户两次报障）。SVG 路径上下各是一个【半圆球】（骨头/哑铃形，用户给的参考图；最宽处 42 ≈ 旋转轨道
   的 40 厚度，两个控件视觉重量一致）、每侧两段三次贝塞尔在腰部以
   【垂直切线】相接，所以腰是圆滑收束；描边由 path 的 stroke 出，样式与原胶囊一致。
   旋钮不再是 .lift-track 的子元素（见 index.html），从根上不可能被裁。 */
.lift-track {
  width: 46px; height: 168px;
  background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2046%20168%27%3E%3Cpath%20d%3D%27M%202%2C24%20A%2021%2C21%200%200%201%2044%2C24%20C%2044%2C46%2034%2C52%2034%2C84%20C%2034%2C116%2044%2C122%2044%2C144%20A%2021%2C21%200%200%201%202%2C144%20C%202%2C122%2012%2C116%2012%2C84%20C%2012%2C52%202%2C46%202%2C24%20Z%27%20fill%3D%27rgba%2831%2C36%2C34%2C0.72%29%27%20stroke%3D%27rgba%28255%2C255%2C255%2C0.22%29%27%20stroke-width%3D%271.6%27%2F%3E%3C%2Fsvg%3E") center/100% 100% no-repeat;
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.35));
  position: relative;
}
/* 两端的方向提示：上球 ↑、下球 ↓。小而淡——它是「提示」不是按钮，
   不能跟中间的旋钮抢视线（用户要求：小点、视觉轻点）。 */
.lift-track::before, .lift-track::after {
  position: absolute; left: 0; right: 0; text-align: center;
  font-size: 13px; line-height: 1;
  color: rgba(255, 255, 255, 0.42);
  pointer-events: none;
  transition: opacity .15s ease;
}
/* 油门钮推近端点时端点箭头渐隐（JS 写 --fadeU/--fadeD）：否则钮上的符号
   与端点提示叠在一起（玩家实测指出） */
.lift-track::before { opacity: var(--fadeU, 1); }
.lift-track::after  { opacity: var(--fadeD, 1); }
.lift-track::before { content: "↑"; top: 16px; }
.lift-track::after  { content: "↓"; bottom: 16px; }
#lift-knob {
  position: absolute; left: 50%; top: 50%;   /* 相对 #lift 定位，不在被裁的轨道里 */
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.22) 65%);
  border: 1px solid rgba(255, 255, 255, 0.6);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  color: rgba(20, 24, 20, 0.75);
  font-size: 15px; font-style: normal;
  transition: transform 0.18s ease;
  pointer-events: none;
}
body.detail-mode #lift, body.soul-stage #lift { display: none !important; }

/* ── 右侧镜头升降（未选中时出现）：两个独立圆钮，按住持续升/降 ──
   与选中时的 #lift 油门互斥、占同一个位置（同样按【上边缘】定位，矮屏幕自动
   让开能量条，理由见上面 #lift 的注释），拇指不用在两种状态间换地方。
   配色取 .lift-track / .rot-track 那块深色玻璃，跟右侧控件是同一家族。 */
/* #elev 独立升降按钮已撤（2026-09-15）：升降并进右下镜头摇杆 #stick2，见下方 */

/* ── 右侧旋转拨盘（选中出现，在升降油门正下方）：左右拖＝转，松手回中 ──
   贴地转盘只留视觉指示（指针告诉你正前方朝哪），抓取交给这里：
   固定 168×40，不随模型大小变，手势永远落在 DOM 上，不与画布的视角拖动冲突。 */
#rot {
  /* 移到底部功能栏正上方（2026-08-17 玩家拍板）：原在右侧中部，与升降油门
     挤在一起，转物体时手会挡住物体本身 */
  position: fixed;
  left: 50%; transform: translateX(-50%);
  bottom: var(--rot-bottom);   /* 工具条上方 12px，见 style.css :root */
  z-index: 24;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
#rot[hidden] { display: none; }
/* 拖动时在轨道正上方浮出当前角度（2026-08-18 用户要求：−180°…+180°，
   数值不再变化 2 秒后自动消失）。
   【为什么要让"已装载"彩带临时避让】--installed-bottom 就是轨道顶 +8px，
   那条彩带(#soul-installed，z-index 25)正好占着这块地方；角度牌压上去会两层字叠字。
   所以做成互斥：牌子在的时候彩带淡出，牌子消失再淡回来 —— 只有 2 秒，
   而这 2 秒里玩家的注意力本来就在角度上。 */
#rot-val {
  position: absolute; left: 50%; bottom: calc(100% + 8px);
  transform: translateX(-50%) translateY(4px);
  z-index: 26;                       /* 压过彩带的 25 */
  /* 【裸文字 + 投影，不给底色】（2026-08-18 用户拍板：加了药丸底显得太重）。
     没有底色兜底，唯一的可读性来源就是投影 —— 用两层：一层紧贴的硬投影压住
     浅色地面（草地是这游戏最常见的背景），一层散开的柔光把边缘从花纹里拎出来。 */
  font: 800 14px/1 "Inter", "PingFang SC", system-ui, sans-serif; font-style: normal;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.75), 0 0 10px rgba(0, 0, 0, 0.5);
  white-space: nowrap; pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}
body.rot-live #rot-val { opacity: 1; transform: translateX(-50%) translateY(0); }
/* 【必须 !important】soul.js 的 updateInstalledPanel 在运行时把 opacity 写成【内联样式】
   （它自己还有一套 5 秒自动淡出的定时器），内联优先级高于任何选择器，不加 !important
   这条规则在"彩带正亮着"的那几秒里完全不起作用 —— 而那几秒恰恰是唯一需要避让的窗口。
   （曾按建元素时那段 cssText 判断"它没写 opacity"，那是错的：opacity 是后来才内联上去的。） */
body.rot-live #soul-installed { opacity: 0 !important; transition: opacity var(--dur-2) var(--ease); }
.rot-track {
  width: 168px; height: 40px;
  border-radius: 100px;
  background: rgba(31, 36, 34, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.35), inset 0 0 4px rgba(255, 255, 255, 0.18);
  position: relative;
}
/* 轨道内【不画刻度线】（用户要求）：量程靠下方的 −180/0/+180 文字表达，
   轨道里干干净净，旋钮位置本身就是读数。旋转是有界的 360°，所以是绝对角度
   滑条（旋钮位置＝当前朝向），而不是像升降那样的持续推速度。 */
/* 两端的方向提示：左端 ↺ 逆时针、右端 ↻ 顺时针。同样小而淡。 */
/* 【上下镜像】（2026-08-18 用户拍板）：实测滑到右边物体是【逆时针】转的
   （three 的 +rotation.y 在俯视下是逆时针），而右端印的 ↻ 是顺时针，标反了。
   镜像会反转手性——↺ 上下翻过来就读作顺时针、↻ 翻过来读作逆时针，
   于是两端含义各自调转，正好对上，字符本身不用换。 */
.rot-track::before, .rot-track::after {
  position: absolute; top: 50%; transform: translateY(-50%) scaleY(-1);
  font-size: 14px; line-height: 1;
  color: rgba(255, 255, 255, 0.42);
  pointer-events: none;
  transition: opacity .15s ease;
}
/* 旋钮滑近端点时该端 ↺/↻ 渐隐（JS 写 --fadeL/--fadeR），见 lift 同款注释 */
.rot-track::before { opacity: var(--fadeL, 1); }
.rot-track::after  { opacity: var(--fadeR, 1); }
.rot-track::before { content: "↺"; left: 11px; }
.rot-track::after  { content: "↻"; right: 11px; }
/* 【底部量程文字已下线】（2026-08-17 用户要求）：−180°/0°/+180° 三个数字对玩家
   没有信息量——旋钮在轨道上的位置本身就是读数，两端的 ↺/↻ 已经说明了方向。
   去掉后旋转条的视觉底边就是它的盒子底边，与工具条的间距才量得准。 */
#rot-knob {
  position: absolute; left: 50%; top: 50%;
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border-radius: 50%;
  display: grid; place-items: center;
  /* 这里【曾经】有一条 padding-bottom:3.3px，是给 ⟳ 字形做墨迹垂直补偿的
     （字形墨迹中心比行盒中心低 1.65px）。现在钮内换成了 SVG「环+指针」，
     它的几何中心就是自己盒子的中心，不需要补偿；留着反而会把图整体顶高 1.65px。 */
  background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.22) 65%);
  border: 1px solid rgba(255, 255, 255, 0.6);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  color: rgba(20, 24, 20, 0.75);
  font-size: 17px; font-style: normal;
  transition: transform 0.18s ease;
  pointer-events: none;
}
/* 钮内是「环 + 指针」的【绝对朝向指示器】，不是会转的 ⟳ 符号（2026-08-18 用户拍板）：
   ⟳ 的箭头方向是画死的，转起来也说不清是朝左还是朝右；换成指针后，
   指针朝正上＝0°，指哪就是物体正面朝哪，与选中物体时场景里那个贴地转盘
   （main.js buildRotDial 的 band + needle）是同一套视觉语言。
   【只转这个 SVG、不转圆圈】圆面是 radial-gradient(circle at 35% 30%) 的高光 +
   0 2px 6px 的投影，整体转会变成"光源在打转"；而且外层 transform 已被 JS
   占用来沿轨道 translateX 定位。SVG 几何中心＝盒中心，绕 50% 50% 自转即可。 */
#rot-face {
  display: block; width: 22px; height: 22px;   /* 34px 的钮里放 22：再小指针就糊了 */
  transform-origin: 50% 50%;
  transition: transform 0.18s ease;
}
body.detail-mode #rot, body.soul-stage #rot { display: none !important; }

/* 贴地转盘（three gizmo，main.js buildRotDial）现在只做视觉指示，不再吃手势 */

/* 选中态：小地图保留（稿 170:16926 右上仍有地图——盖掉 style.css 的隐藏）；
   语音药丸/对话气泡让位给工具条 */
body.has-selection #minimap { display: block !important; }
/* 选中物体：药丸和气泡一起【往下退场】（2026-09-16 用户拍板「底部整体功能往下出场」），工具条从底下
   升上来接位（.hud-in，见「选中态三件控件的进出场」那段）；取消选中再升回来。 */
body.has-selection #mic-pill { opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(28px); }

/* 消息流对版补丁（2026-08-07 二轮）：稿里没有的中间层全部收掉——
   「神笔马良」头像行（.turn-avatar/.turn-name）、「开工中 · N 个任务」头（.pl-head）、
   任务名行（.lane-subject）都不再露出；面板消息区只剩 角色玻璃卡 + 消息卡。
   （结构与逻辑不动，只是不渲染成可见层——历史/状态照常流转。） */
#chat .turn { display: block; margin: 0; }
#chat .turn-avatar, #chat .turn-name { display: none; }
#chat .turn-content { gap: 10px; }
/* 「搭好啦」那条消息可点：定位到这一轮搭的物体，镜头飞到它正前方（main.js linkSummary / focusFront）。
   视觉上不加任何图标（铁律：不自创 icon），只给指针手势和按下的手感（全局按下口径：缩一点 + 压 10% 黑）。 */
#chat .msg.msg-link { cursor: pointer; transition: transform 0.08s ease, filter 0.25s ease, opacity 0.25s ease; }
#chat .msg.msg-link:active { transform: scale(0.98); }
/* 调整流程（选中了物体）：面板里所有「搭好啦」消息都不再定位（main.js click 里 selected 直接返回），
   所以也不该长得像链接——收掉手型和按下缩放；取消选中即恢复 */
body.has-selection #chat .msg.msg-link { cursor: default; }
body.has-selection #chat .msg.msg-link:active { transform: none; }
/* 这一轮搭的东西【全部】被删了：整轮工作流（角色卡 + 「搭好啦」消息）置灰；点那条消息会轻提示已删除。
   编辑那轮没有 .turn 容器，就只灰那一条消息（main.js refreshTurnLinks）。
   用 grayscale + 半透明而不是换一套灰色配色：角色卡里有头像切图和各色角色名，只有滤镜能一次灰干净。
   这些卡片搭完就是静止的，滤镜不会每帧重算（性能铁律忌讳的是子树里有动画的 filter）。 */
#chat .turn.is-gone, #chat .msg.is-gone { filter: grayscale(1); opacity: 0.5; transition: filter 0.25s ease, opacity 0.25s ease; }
#chat .pipeline { background: none; border: none; border-radius: 0; overflow: visible; }
#chat .pl-head { display: none; }
#chat .pl-reference {
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.05);
}
#chat .lane { display: block; padding: 0; border-top: none; background: none; }
#chat .lane-subject { display: none; }

/* 角色卡（工人/设计师…，170:16800）：玻璃卡 + 48 圆头像（建工头像从底部探出）。
   原 .flow-node 是 44px 缩进 + 头像绝对定位 + 节点连线——卡片化后连线撤掉、
   缩进扩到 64（48 头像 + 8 间距 + 8 卡内边距），头像随卡内边距落位。 */
#chat .flow-node {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  padding: 8px 8px 8px 64px;
  min-height: 64px;
  margin-bottom: 10px;
  display: flex; flex-direction: column; justify-content: center;
}
#chat .flow-node::before { display: none; }  /* 独立卡片，不再画角色连接线 */
#chat .flow-node:hover { transform: none; }
#chat .flow-av {
  position: absolute; left: 8px; top: 8px;
  width: 48px; height: 48px; flex: none;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 50%;
  overflow: hidden;
  box-shadow: none;
  font-size: 0;                 /* 兜底字符（角色缺切图时）不撑破布局 */
}
#chat .flow-av img {
  width: 35px; height: 42px;
  object-fit: cover; object-position: top center;
  border-radius: 0; display: block;
}
#chat .flow-nm { font: 800 15px/1.2 "PingFang SC", system-ui, sans-serif; color: #fff; letter-spacing: -0.13px; }

/* 建议词 + 输入行 */
#suggestions { padding: 0 12px; gap: 6px; scroll-padding: 0 12px; }   /* scroll-padding：style.css 给这行开了 scroll-snap，吸附对齐的是滚动口边缘、不算 padding——行一溢出，换内容时第一颗会被吸到贴边、比上面卡片左 12px（2026-09-17 用户截图） */
#chat .chip {
  padding: 4px 10px;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: #fff;
  font: 400 11px/1.4 "PingFang SC", system-ui, sans-serif;
  letter-spacing: -0.13px;
  box-shadow: none;
}
#chat .chip:hover { background: rgba(255, 255, 255, 0.12); transform: none; }
#chat-form {
  margin: 0 12px; padding: 0 5px 0 12px;
  height: 44px; flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
#chat-input {
  flex: 1; min-width: 0;
  background: none; border: none; box-shadow: none;
  /* padding-left 不能是 0：<input> 自带 overflow:clip，内容框左边缘就是裁剪边界，
     光标停在开头（位置 0）时正好画在这条线上，会被切掉一半（2026-09-07 报障）。
     跟圆角无关——border-radius 单独不裁任何东西，要配 overflow 才裁；这里
     #chat-form 有 100px 圆角但没开 overflow，而 #chat 的 20px 圆角离输入框 25px 够不着。
     负 margin 把这 2px 的布局位移抵消掉：文字起点一点不动（实测位移 0），
     只是内容框往左让出 2px 给光标。 */
  padding: 0 0 0 2px; margin-left: -2px;
  color: #fff;
  font: 500 13px/1.4 "PingFang SC", system-ui, sans-serif;
  letter-spacing: -0.13px;
}
#chat-input::placeholder { color: rgba(255, 255, 255, 0.3); }
/* 输入框内的粘贴图标（src/paste.js 包了一层 .paste-wrap）：条子里的 flex:1 由 wrap 接管，
   输入框本身在 wrap 里撑满；图标缩小一号贴合 44 高的条 */
#chat-form .paste-wrap { flex: 1; min-width: 0; }
#chat-form .paste-ic { width: 26px; height: 26px; right: 2px; }
#chat-input:focus { outline: none; box-shadow: none; background: none; }
/* 调整态（选中物体后 main.js 给输入框挂 .editing）：style.css 旧皮肤会铺 8% 黄底 + 黄边，
   新皮肤的胶囊底在 #chat-form 上，输入框本身必须保持透明，否则文字后面浮一条淡黄（2026-09-17 用户截图）。
   调整态的提示交给占位文字「调整「X」：…」。 */
#chat-input.editing { background: none; border-color: transparent; }
#chat-send {
  width: 32px; height: 32px; flex: none; padding: 0;
  display: grid; place-items: center;
  border: 0.64px solid rgba(255, 229, 133, 0.7);
  border-radius: 16px;
  background: #f25b17;
  box-shadow: 0 0 3.2px rgba(255, 81, 0, 0.9), inset 0 0 3.2px rgba(255, 171, 92, 0.7);
  color: #fff; font-size: 0;
}
#chat-send img { width: 19px; height: 19px; display: block; transform: rotate(90deg); }
#chat-send:hover { transform: none; }
#chat-send:disabled { opacity: 0.5; background: #f25b17; }
/* 生成中变「停止」：切图藏起换成方块符号（JS 只切类不动 DOM） */
#chat-send.is-stop img { display: none; }
/* 停止方块 = CSS 画的圆角方块（2026-08-19 拍板加圆角；■ 字形圆不了角） */
#chat-send.is-stop::before { content: ""; width: 12px; height: 12px; border-radius: 3.5px; background: #fff; }
/* 生成中输入区锁住（main.js setSendMode 切 .is-busy + disabled）：输入框连同粘贴钮整块淡下去，
   只有右边的停止钮保持原样。iOS 会给 disabled 输入框自带半透明灰字，先抹平再统一在外层淡化，
   两层叠起来就几乎看不见了。 */
#chat-input:disabled { opacity: 1; color: #fff; -webkit-text-fill-color: #fff; cursor: default; }
#chat-form.is-busy .paste-wrap { opacity: 0.45; pointer-events: none; transition: opacity 0.2s ease; }

/* 生成中：药丸麦克风变「停止生成」（main.js setSendMode 同步切 .gen-stop，点击中断）。
   与文字输入的停止钮同款圆角方块。 */
#big-mic.gen-stop img { display: none; }
#big-mic.gen-stop::before {
  content: ""; width: 14px; height: 14px; border-radius: 4px; background: #fff;
}

/* ── 键盘条（src/kbbar.js，2026-09-16）：触屏上聚焦聊天输入框时，整条 #chat-form 被挪进贴着键盘顶的
   #kbbar 里，页面本身不被键盘推。条的壳、定位在模块里；这里只写游戏页那几块进了条以后的样子。 ── */
#kbbar #chat-form { margin: 0; width: 100%; }
/* 条右端收键盘的 ✕（kbbar.js 自带，默认 36px）：游戏页跟旁边 32px 的发送钮一般大。
   模块的样式是开条时才插进 head 的，排在本文件后面，同特异度会输——所以多带一个标签名压过它。 */
#kbbar button.kb-close { width: 32px; height: 32px; }
/* 右钮三态：没打字 = 麦克风、打了字 = 发送、生成中 = 停止（.is-stop 藏两张图换方块，仍适用）。
   麦克风那张只在键盘条里出现（main.js syncSendFace 只在条开着时挂 .is-mic）。 */
#chat-send img.mic { display: none; transform: none; }
#chat-send.is-mic img.mic { display: block; }
#chat-send.is-mic img:not(.mic) { display: none; }
/* 录音态：语音药丸整颗被挪进条里替代输入框（kbbar.setAside），沿用它自己的 .rec 布局
   （✕ | 跳动波形 | 在听… | 实时转写 | 发光麦克风 = 停止）。它是 fixed 定位，进条里改回文档流。 */
#kbbar #mic-pill {
  position: static; transform: none; opacity: 1; pointer-events: auto;
  left: auto; bottom: auto; flex: 1; min-width: 0; justify-content: center; max-width: none;
}

/* ── 赋魂展台（body.soul-stage）：中间是「只剩这一个物体」的空旷场景，HUD 全部让开 ──
   【为什么写在 voice.css 末尾而不是 style.css】voice.css 里有
   `body.has-selection #minimap { display: block !important }`，与本条特异度相同
   （都是 0,1,1,1）且都带 !important —— 这时后声明的赢。写在 style.css 里会被它压掉：
   实测 body 上明明有 soul-stage、内联也是空的，小地图仍然 display:block。 */
body.soul-stage #minimap,
body.soul-stage #edit-toggle,
body.soul-stage #scene-toggle,
body.soul-stage #scene-menu,
body.soul-stage #scenery-del,
body.soul-stage #undo-bar,
body.soul-stage #toolbar,
body.soul-stage #sheet-handle,
body.soul-stage #energy-badge,
body.soul-stage #mic-pill,
body.soul-stage #soul-installed,
body.soul-stage #home-entry,
body.soul-stage #perf { display: none !important; }

/* ══════════ 进入地图的加载屏（Figma 732:24169「草地世界加载」）══════════
   稿面 852×393，三种地貌（grass/desert/snow）只有【背景图】【图标】【图标高度】不同，其余一样。
   本段在 style.css 之后加载，同特异性下后到者胜——style.css 里那份 demo 占位
   （🧱 emoji + "BlockWorld"）的每一条属性这里都要覆盖到，漏一条就会留下来。

   为什么不做 852×393 缩放舞台：游戏页 index.html 本来就是流式布局（HUD 全是固定 px +
   safe-area），不像首页/登录页那样有 --s 舞台。所以这里背景 cover 铺满视口、内容按设计 px
   居中——在目标机型（横屏 852×393pt）上就是 1:1 稿面。

   地貌怎么定的：<html data-biome="…">，由 index.html <head> 里那段经典脚本同步写入，
   第一帧就正确。 */

/* 兜底渐变垫在背景图下面：图还没解出来、或者某个地貌的图还没切，看到的也是这张图
   该有的天地色，不会闪白。值取自新背景图顶部 1/12 与底部 1/12 的实测均值。 */
#loading {
  display: block;                                  /* 稿面不再是居中一列，覆盖 style.css 的 flex 居中 */
  background-color: transparent;
  background-image: var(--load-bg, none), var(--load-sky);
  background-size: cover, auto;
  background-position: center center, center center;
  background-repeat: no-repeat, no-repeat;
}
html[data-biome="grass"]  #loading { --load-bg: url("assets/loading/bg_grass.jpg");  --load-sky: linear-gradient(180deg, #9cb1dc, #1f2420); }
html[data-biome="desert"] #loading { --load-bg: url("assets/loading/bg_desert.jpg"); --load-sky: linear-gradient(180deg, #828493, #332a28); }
html[data-biome="snow"]   #loading { --load-bg: url("assets/loading/bg_snow.jpg");   --load-sky: linear-gradient(180deg, #8498df, #222f54); }

/* 右上角 logo（稿面 image 185，210×69.808，left 642 → 右边缘正好贴到 852，所以 right:0）。
   ⚠️ 类名用 .load-brand 不用 .load-logo：style.css 里 .load-logo 是 demo 那枚会上下浮动的
   emoji（font-size:56px + load-bob 动画），沿用就得把它每条属性都覆盖一遍，不如换个名。 */
#loading .load-brand {
  position: absolute;
  top: 13px;
  right: env(safe-area-inset-right, 0px);
  width: 210px;
  height: 69.808px;
  object-fit: contain;
  pointer-events: none;
}

/* 底部那条带（稿面 Frame 279，852×88）。
   稿面把它导出成了位图，实际是一条【纯竖向 alpha 渐变】——实测底色恒为 rgb(10,22,2)、
   alpha 从 0 线性升到 0.494（对 y 线性拟合最大偏差 0.54，逐行跨列标准差 0.73），
   所以用 CSS 渐变还原，不落切图。
   高度不写死，由内容撑：12 + 20(唤醒行) + 6 + 10(进度条) + 7 + 15(轮播行) + 18 = 88，正好是稿面值。
   左右内边距稿面是 112（内容宽 628≈稿面进度条的 630）；窄屏按 13.03% 收，最小留 16。 */
#loading .load-band {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 12px clamp(16px, 13.03%, 111px) 18px;
  background: linear-gradient(to bottom, rgba(10, 22, 2, 0), rgba(10, 22, 2, 0.494));
}

/* 「正在唤醒{地貌}世界…」——【不带地图名】（2026-09-15 改稿）。
   地貌名由 index.html 那段经典脚本按 data-biome 写入，首帧就是对的。 */
#loading .load-head {
  font: 600 14px/20px "PingFang SC", "Hiragino Sans GB", sans-serif;
  color: #fff;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 进度条（稿面 882:6990/6991）：630×10 方角，底 #121212 + 1px 白 0.34 描边；
   填充 8px 高、橙底 + 右缘往里 9.1px 的亮黄内发光（inset 的 -6px 把光推到右端）。
   ⚠️ 这一版【是方角】，跟旧版那条 radius 35 的胶囊不是一回事。
   ⚠️ 描边用 outline 不用 border（2026-09-16）：稿面的填充是 left:1 / top:+1、8 高，
   相对轨道的 630×10【几何盒】各内缩 1px，而 Figma 的描边画在盒子【外面】、不占布局——
   所以稿面上描边和填充之间有一圈 1px 黑缝。之前写成 border（画在盒内）后填充直接
   贴着描边，那圈缝就没了，用户一眼看出「少了 padding」。
   现在：盒子仍是 10 高 border-box，padding 1 ⇒ 内容盒 8 高给填充；描边走 outline，
   画在盒外、不挤布局，下面 tip 行的位置一个像素都不动。overflow:hidden 不会裁掉自己的
   outline，放心。
   width 走 100%：band 的内边距已经把内容宽收到稿面的 630。 */
#loading .load-bar {
  box-sizing: border-box;
  width: 100%;
  height: 10px;
  padding: 1px;
  border: 0;
  outline: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 0;
  background: #121212;
  overflow: hidden;
  box-shadow: none;
}
#loading .load-bar > i {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 0;
  background: #ffa600;
  box-shadow: inset -6px 0 9.1px #feeb4b;
  transition: width 0.25s ease;
  position: relative; overflow: hidden;
}
/* 呼吸高光（2026-09-15）：一条斜向高光在已填充段上来回滑。只用 transform，走合成器——
   主线程解析 2.4MB 代码那一秒画面冻结时它照样在动，玩家不会以为死机。 */
#loading .load-bar > i::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .45), transparent);
  animation: loadSheen 1.4s ease-in-out infinite;
}
@keyframes loadSheen { from { transform: translateX(-100%); } to { transform: translateX(350%); } }

/* 底部轮播行（稿面 882:6992）：地貌文案 + 「……{已建} / {总数} 个造物」。
   文案 5s 换一条，循环在 main.js 的 startLoadTips；换的时候淡一下，硬切太廉价。
   造物数每帧在变，tabular-nums 免得整行左右抖。 */
#loading .load-tip {
  margin-top: 1px;                                 /* 稿面 Frame278→文案是 gap 7，条内 gap 6，差这 1px */
  font: 500 10px/15px "PingFang SC", "Hiragino Sans GB", sans-serif;
  color: #f5f7fc;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#loading #load-tip { transition: opacity 0.25s ease; }
#loading #load-count { font-variant-numeric: tabular-nums; }

/* ══════════ 细节编辑顶部条的「退出」钮 ══════════
   demo 那版是纯色 #e8823a 的圆角丸，跟全站现行按钮不是一套——现行配方是
   「竖直渐变 + 下暗边 + 上高光 + 字投影 + 按下手感」，见个人信息「去补能」
   (.pf-btn)、设置那两颗大钮 (.st-lg)、登录三钮 (.lg-b)。统一过来。
   规则写在这里不写 style.css：那个文件要保持和 demo 好对 diff（本文件后加载）。
   ⚠️ demo 有一条 #detail-bar #detail-exit:hover 会把 background 换成纯色，
   必须同名覆盖掉，否则一悬停渐变就没了。 */
#detail-bar #detail-exit {
  padding: 7px 18px;
  border: none;
  border-radius: 8px;
  color: #fff;
  font: 800 13px/1 "Inter", "PingFang SC", sans-serif;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .25);
  background: linear-gradient(180deg, #fe723a, #fa4f13);
  box-shadow: inset 0 -2px .5px rgba(183, 49, 0, .77),
              inset 0 1px .5px rgba(255, 255, 255, .3);
  transition: transform .08s ease, filter .08s ease;
}
#detail-bar #detail-exit:hover { background: linear-gradient(180deg, #fe723a, #fa4f13); }
#detail-bar #detail-exit:active { transform: translateY(1px) scale(.96); filter: brightness(.9); }

/* ══════════ 细节编辑：顶部提示条 + 底部输入条 ══════════
   都对齐游戏内右侧那条语音药丸的玻璃配方（#mic-pill，本文件 110 行附近）：
   rgba(32,36,34,.85) + blur(10)、0.8px 极淡白描边、外投影配内高光。
   demo 那版是 rgba(18,22,28,.9) 的纯深色块 + 14px 圆角，跟现行 HUD 不是一套。
   规则写这里不写 style.css：那个文件要保持和 demo 好对 diff（本文件后加载）。 */
#detail-bar, #detail-input {
  background: rgba(32, 36, 34, 0.85);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 0.8px solid rgba(255, 255, 255, 0.05);
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.4), inset 0 0 4px rgba(255, 255, 255, 0.25);
}
#detail-input { border-radius: 100px; padding: 4px 4px 4px 6px; gap: 6px; }
/* 拆解态要【露出】语音药丸（2026-09-18 用户：拆解里改部件也先说话）。
   进拆解是从选中物体进来的，body.has-selection 还挂着，上面那条「选中态药丸往下退场」会把它压住；
   桌面端拆解时对话面板还开着，`body:not(.chat-hidden)` 那条也会压住。两条特异度都是 (1,1,1)，
   本条写在它们【之后】，靠后来居上赢，不用 !important。 */
body.detail-mode #mic-pill { opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(28px); }
/* 【选中部件之后】才露出来（2026-09-18 用户：点拆解不该直接就出来，得先选一个部件）。
   body.detail-picked 由 main.js 的 selectDetailPart / clearDetailSelection 挂摘。 */
body.detail-mode.detail-picked #mic-pill { opacity: 1; pointer-events: auto; transform: translateX(-50%); }
/* 面板一开药丸就退下去（2026-09-18 用户：点胶囊弹出侧面板以后要把胶囊隐藏起来）——
   与非拆解态的 `body:not(.chat-hidden) #mic-pill` 同一个规矩，只是特异度要压过上面那条。 */
body.detail-mode:not(.chat-hidden) #mic-pill { opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(10px); }
/* 但点了键盘钮、底部改件输入条拉起来之后，药丸让位——一个屏幕上不给两个输入口。
   #detail-input 在 DOM 里排在 #mic-pill 前面，用兄弟选择器就够，不必再加一个 body class。 */
#detail-input:not([hidden]) ~ #mic-pill { opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(10px); }
/* 输入框本身退成透明：玻璃已经在外层了，里面再垫一层灰块会显得脏 */
#detail-input input {
  background: transparent; border-radius: 100px; padding: 10px 14px;
  color: #e8ecf0; font-size: 14px;
}
/* 「修改」钮用全站现行按钮配方（同 .pf-btn / .st-lg / .lg-b），只是收成药丸形 */
#detail-input button {
  padding: 0 20px; min-height: 38px; border-radius: 100px;
  color: #fff; font: 800 14px/1 "Inter", "PingFang SC", sans-serif;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .25);
  background: linear-gradient(180deg, #fe723a, #fa4f13);
  box-shadow: inset 0 -2px .5px rgba(183, 49, 0, .77),
              inset 0 1px .5px rgba(255, 255, 255, .3);
  transition: transform .08s ease, filter .08s ease;
}
#detail-input button:active:not(:disabled) { transform: translateY(1px) scale(.96); filter: brightness(.9); }
/* 细节编辑条里的粘贴图标：#detail-input button 那套皮（id 级）会盖过 paste.js 的 .paste-ic，
   这里按同样的 id 级把它压回「无底、橙线框」（与 src/paste.js 的 D 方案一致） */
#detail-input .paste-wrap { flex: 1; min-width: 0; }
#detail-input .paste-ic { width: 26px; height: 26px; padding: 0; border: none; border-radius: 8px; font-size: 0;
  background: none; color: #ff9a63; box-shadow: none; }
/* 顶部条现在只剩提示文字，收紧左右内边距，别留原来给退出钮的那块空 */
#detail-bar { padding: 9px 18px; gap: 0; }

/* 赋魂展台 / 拆解态：收掉「＋扩建」那排浮钮。
   展台是「只剩这一个物体」的空旷场景，地上飘着扩建入口会穿帮（用户 2026-09-11）。
   3D 那层的地面虚影和边界线由 main.js 的 enterSoulStage 一并藏起来——
   两层分开收是因为它们本来就分属 DOM 和场景，没有共同开关。 */
body.soul-stage #expand-btns,
body.detail-mode #expand-btns { display: none !important; }

/* 对话面板打开时收掉「＋扩建」浮钮（2026-09-16 用户报障：唤起文字输入后，扩建按钮还压在面板上面）。
   为什么会压上去：#expand-btns 是 style.css 里 z-index var(--z-float)=40 的全屏定位层，按钮按
   空位的屏幕投影摆，比右侧浮窗 #chat 高，正好投影落在面板范围里的那几颗就叠在输入框上。
   面板开着时玩家在打字，扩建不是这一刻的事 —— 跟同一时刻淡掉的摇杆（#stick / #stick2）同节奏收起。
   · 用 opacity + visibility 而不是 display:none：能淡出，且 visibility 关掉后子按钮自然点不到
     （.expand-anchor 自己没设 visibility，会继承）。visibility 要【延迟到淡完】再关，否则直接硬切。
   · 只收 DOM 这层按钮；地面上的白底绿边虚影是 3D 场景里的东西，在面板【下面】，不会挡字，不动。
   · 面板收起（body.chat-hidden，也是默认态）时这条不命中，按钮照常由 main.js 的
     updateExpandButtons 按镜头远近逐帧决定显不显示。 */
#expand-btns { transition: opacity .25s ease; }
body:not(.chat-hidden) #expand-btns {
  opacity: 0; visibility: hidden;
  transition: opacity .25s ease, visibility 0s linear .25s;
}


/* ── 串门 / 参观模式（2026-09-15）：body.visiting 由 main.js 按服务端快照的 readonly 标记加上 ──
   藏掉一切能改世界的入口：工具条、撤销、升降/旋转手柄、远景删除、扩地、拆解条、语音药丸、
   对话气泡与面板、输入条、额度胶囊、电表徽章、已装载彩带。两个摇杆照常，看得动。
   这只是遮眼睛；真正拦改动的是服务端（写接口只认主人）。 */
body.visiting #toolbar, body.visiting #undo-bar, body.visiting #lift, body.visiting #rot,
body.visiting #scenery-del, body.visiting #expand-btns, body.visiting #confirm-mask,
body.visiting #detail-bar, body.visiting #mic-pill,
body.visiting #chat, body.visiting #sheet-handle, body.visiting #sheet-collapse,
body.visiting #quota-pill, body.visiting #energy-badge, body.visiting #soul-installed { display: none !important; }
/* 顶栏：与左上回首页钮同一块玻璃配方，顶部居中；让开灵动岛（铁律 12 只管左右，顶部走 safe-area-top） */
#visit-bar {
  position: fixed; left: 50%; top: calc(16px + env(safe-area-inset-top, 0px));
  transform: translateX(-50%); z-index: 21;
  height: 36px; padding: 0 14px; display: flex; align-items: center; gap: 4px;
  border: 0.8px solid rgba(255, 255, 255, 0.4); border-radius: 18px;
  background: rgba(38, 46, 62, 0.7);
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.25), inset 0 0 4px rgba(255, 255, 255, 0.25);
  font-size: 13px; font-weight: 600; color: #fff; white-space: nowrap;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); pointer-events: none;
  max-width: calc(100vw - 2 * max(120px, env(safe-area-inset-left, 0px)));
  overflow: hidden; text-overflow: ellipsis;
}
/* ⚠️ 上面写了 display:flex，会压过 UA 的 [hidden]{display:none}——首版就是这么让它在自己家也常驻的
   （空名字的「正在参观 的家」，用户 2026-09-15 报障）。hidden 时必须显式关掉。 */
#visit-bar[hidden] { display: none; }
#visit-bar b { font-weight: 800; color: #ffd83b; max-width: 9em; overflow: hidden; text-overflow: ellipsis; }
/* 4 秒后淡出上浮，transitionend 后 main.js 置 hidden（等加载屏掀开后玩家还能看到约 3 秒） */
#visit-bar { transition: opacity 0.5s ease, transform 0.5s ease; }
#visit-bar.fade { opacity: 0; transform: translateX(-50%) translateY(-8px); }
