/* ==================================================================
 * 聊天室 —— 样式表
 *
 * 关于早期注释里出现过的名字：本项目早期用「三栏 + 蓝绿配色」描述自己时
 * 借用了现成产品做参照，后来发现这既不准确也不必要——
 * 它现在是自己的东西，不该挂着别人的名号。
 * 布局：左会话 / 中聊天 / 右成员；公式用 $...$ 行内、$$...$$ 块级。
 * ================================================================== */

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei",
               "Hiragino Sans GB", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  color: #191919;
  background: #f2f2f2;
  -webkit-font-smoothing: antialiased;
}
button { font-family: inherit; font-size: 13px; cursor: pointer; }
input, textarea, select { font-family: inherit; font-size: 14px; }
.hidden { display: none !important; }
.only-narrow { display: none; }

/* ---------------- 主色调变量 ---------------- */
:root {
  --wx-bg: #f5f5f5;
  --wx-side: #e7e7e7;
  --wx-head: #f5f5f5;
  --wx-line: #dcdcdc;
  --wx-green: #07c160;
  --wx-green-d: #06ad56;
  --wx-bubble: #95ec69;
  --wx-text: #191919;
  --wx-sub: #8c8c8c;
  --wx-blue: #576b95;

  /* Q-Chat-E-108（变量作用域检查抓出）
     ⚠⚠ 下面这 7 个变量**原本只在 body.dark 里定义**，
     但被 20 处规则在 dark 块【之外】使用 ——
     浅色（默认）模式下这些变量全部无效，`var(--x)` 取不到值，
     整条声明被丢弃。受影响的元素包括：
       搜索结果悬停 · 表情面板 · 公式符号面板 · 权限面板 · 查看原文框 · 加载更多
     表现是：浅色下鼠标划过搜索结果**毫无反馈**（用户以为点不动），
     符号/权限面板的背景、边框、次要文字全部退回继承值。

     根因：深色模式是后加的，加的时候把新变量写进了 body.dark，
     没同步一份到 :root。加的时候深色看着正常，浅色静默退化，没人发现。

     ⚠ 值必须与深色那份语义对应（不是照抄）：
       --hover 取 .user-line:hover 现用的 #efefef，不是深色的 #1e2126 */
  --bg: #fff;
  --bg-panel: #fff;
  --bg-chat: #f5f5f5;
  --text: #191919;
  --text-dim: #8c8c8c;
  --border: #dcdcdc;
  --hover: #efefef;
  --link: #576b95;
  --code-bg: #f6f8fa;
}

/* ================= 布局 ================= */
.app {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr) 236px;
  height: 100vh;
  background: var(--wx-bg);
}
.sidebar {
  background: var(--wx-side);
  border-right: 1px solid var(--wx-line);
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--wx-bg);
  border-right: 1px solid var(--wx-line);
}
.side-panel {
  background: #fafafa;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* ================= 左侧栏 ================= */
.side-top {
  height: 60px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-bottom: 1px solid #dcdcdc;
}
.search-box {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  background: #dedede;
  border-radius: 4px;
  padding: 6px 8px;
}
.search-box input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  outline: none;
  font-size: 13px;
}
.search-ico { font-size: 12px; opacity: .6; }
.icon-btn {
  width: 30px; height: 30px;
  border: 0;
  border-radius: 4px;
  background: #dedede;
  color: #333;
  font-size: 15px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.icon-btn:hover { background: #d5d5d5; }
.icon-btn.green { background: var(--wx-green); color: #fff; }
.icon-btn.green:hover { background: var(--wx-green-d); }

.me-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border-bottom: 1px solid #dcdcdc;
}
.me-info { flex: 1; min-width: 0; }
.me-nick { font-weight: 600; display: flex; align-items: center; gap: 6px; }
.me-sub { color: var(--wx-sub); font-size: 12px; margin-top: 2px; }

.room-list { flex: 1; overflow-y: auto; }
.room-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  border-bottom: 1px solid rgba(0, 0, 0, .04);
}
.room-item:hover { background: #dcdcdc; }
.room-item.on { background: #c9c9c9; }
.room-avatar {
  width: 38px; height: 38px;
  border-radius: 4px;
  font-size: 15px;
  background: #6b7a8f;
}
.room-mid { flex: 1; min-width: 0; }
.room-name {
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.room-preview {
  font-size: 12px;
  color: var(--wx-sub);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 150px;
}
.room-right { text-align: right; }
.room-time { font-size: 11px; color: #a0a0a0; }
.badge {
  display: inline-block;
  min-width: 17px;
  margin-top: 3px;
  padding: 0 5px;
  border-radius: 9px;
  background: #fa5151;
  color: #fff;
  font-size: 11px;
  line-height: 17px;
  text-align: center;
}
.side-foot {
  display: flex;
  /* 两个会开面板/改状态的按钮紧邻，拉开一点 */
  gap: 12px;
  padding: 8px;
  border-top: 1px solid #dcdcdc;

  /* 给左下角固定的「← 首页」按钮让出空间。
     两者原本在同一位置重叠，管理面板/退出按钮点不到。 */
  padding-bottom: 46px;}
.empty-tip { padding: 16px; text-align: center; color: #999; font-size: 12px; }

/* ================= 头像 ================= */
.avatar {
  width: 38px; height: 38px;
  flex: 0 0 auto;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 600;
  font-size: 15px;
  user-select: none;
}
.avatar-sm { width: 28px; height: 28px; font-size: 12px; border-radius: 3px; }
.avatar-md { width: 38px; height: 38px; font-size: 15px; }
.avatar-lg { width: 56px; height: 56px; font-size: 22px; border-radius: 6px; }
.avatar-owner { box-shadow: 0 0 0 2px #ffb400; }
.avatar-admin { box-shadow: 0 0 0 2px #4b8ff7; }

/* ================= 标签 ================= */
.tag {
  display: inline-block;
  padding: 0 5px;
  border-radius: 3px;
  font-size: 11px;
  font-weight: 400;
  line-height: 16px;
  vertical-align: 1px;
}
.tag.gray { background: #e0e0e0; color: #666; }
.tag.green { background: #e3f7e9; color: #07a350; }
.tag.blue { background: #e5efff; color: #4b6cc7; }
.tag.red { background: #fde8e8; color: #d93025; }
.tag.orange { background: #fff2e0; color: #d97706; }

/* ================= 聊天头部 ================= */
.chat-head {
  height: 60px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 16px;
  background: var(--wx-head);
  border-bottom: 1px solid var(--wx-line);
}
.ch-info { flex: 1; min-width: 0; }
.ch-title { font-size: 16px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 未保存警示（S-114）：必须一直可见，不能是会消失的 toast。
   写盘失败期间它就挂在房间名下面，恢复成功才隐藏。 */
/* 录音倒计时预警（E-018 / S-116）：最后 10 秒变红加心跳，
   不能让用户说到一半被突然自动发送。 */
.rec-sec.warn, #recSec.warn {
  color: #c0392b; font-weight: 700;
  animation: recWarnPulse .9s ease-in-out infinite;
}
@keyframes recWarnPulse { 0%,100% { opacity: 1 } 50% { opacity: .45 } }

.len-hint {
  font-size: 11px; color: #8a8f98; padding: 2px 4px; text-align: right;
}
.len-hint.over { color: #c0392b; font-weight: 700; }
.len-hint.hidden { display: none; }
body.dark .len-hint { color: #6b7280; }
body.dark .len-hint.over { color: #e8735f; }

.field-hint.warn { color: #c0392b; font-weight: 600; }
.field-hint.ok { color: #2a7; }
body.dark .field-hint.warn { color: #e8735f; }
body.dark .field-hint.ok { color: #6ad39a; }

/* Q-Chat-E-105（CSS 双向接线检查，tools/verify/css-wiring.py 抓出）
   ⚠ 语法高亮的四个 token 类。
   markdown.js 早就生成了 tk-c（注释）/ tk-s（字符串）/ tk-n（数字）/ tk-k（关键字），
   **但 CSS 里一个都没定义** —— 于是代码块里所有东西都是同一个颜色。
   这等于"语法高亮"这个功能在做了一半的地方停住了：
   代码结构已经解析出来了（注释、字符串、关键字都认得），只是没上色。
   而"专业模式"的卖点之一就是 Markdown + 代码高亮，
   用户看到的是一整块纯色文本，只会以为不支持。 */
.tk-c { color: #6a737d; font-style: italic; }
.tk-s { color: #032f62; }
.tk-n { color: #005cc5; }
.tk-k { color: #d73a49; font-weight: 600; }
body.dark .tk-c { color: #8b949e; }
body.dark .tk-s { color: #a5d6ff; }
body.dark .tk-n { color: #79c0ff; }
body.dark .tk-k { color: #ff7b72; }

/* Q-Chat-E-105
   ⚠ body.save-failing 原来加了却**没有任何 CSS 读它**。
   顶部那条 .save-warn 提示确实在，但它在一个固定位置 ——
   用户正在输入框里打字时，眼睛在输入框上，未必看到顶部。

   body 上挂这个类的意义就是让"正在丢数据"这件事**跟着用户走**：
   输入框边框变红，无论他在哪个房间、正在写什么，都躲不开这个提示。
   ⚠ 只描边框不改背景：背景变红会让人以为输入框坏了、不敢继续打。 */
body.save-failing #input {
  border-color: #d32f2f !important;
  box-shadow: 0 0 0 2px rgba(217, 48, 47, .18);
}
body.dark.save-failing #input { border-color: #ff6b6b !important; }

/* Q-Chat-E-105
   ⚠ 这三个类原本只有 JS 在用、CSS 里没定义。
   .invite-code 是生成出来的口令本体 —— 它是整个界面里用户唯一要抄走的东西，
   没有样式就是一行普通小字：既看不出是重点，也没法等宽对齐着念，
   而"念错一个字符"正是自定义口令功能要解决的问题。
   所以必须：等宽 + 放大 + 可整体选中（user-select:all 一点即全选）。 */
.invite-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 20px; font-weight: 700; letter-spacing: 2px;
  padding: 10px 12px; margin: 6px 0;
  border: 1px dashed var(--line, #c8ced6); border-radius: 8px;
  background: var(--bg-soft, #f6f8fa);
  text-align: center; user-select: all; cursor: text;
  word-break: break-all;
}
body.dark .invite-code {
  background: #24303d; border-color: #46586b;
}
/* 口令输出区：空的时候不该占高度，否则弹窗底部凭空一截空白 */
.invite-out:empty { display: none; }
.invite-out { margin-top: 8px; }

/* 改密码弹窗用的行式排版。
   ⚠ 没定义时 label 和 input 会挤成一团（label 是 inline），
   而这是"重置站点后强制改密码"的必经之路 —— 第一次用就撞上。 */
.form-row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.form-row label { font-size: 12px; color: var(--sub, #6b7280); }
.form-row input {
  padding: 8px 10px; font-size: 14px;
  border: 1px solid var(--line, #d5d9de); border-radius: 6px;
  background: var(--bg, #fff); color: var(--fg, #1f2328);
}
body.dark .form-row input {
  background: #1b232c; color: #e6edf3; border-color: #3d4d5c;
}

.invite-list { max-height: 220px; overflow-y: auto; }
.invite-row {
  padding: 8px 10px; border: 1px solid var(--line, #e3e6ea); border-radius: 8px;
  margin-bottom: 6px;
}
.invite-row.revoked { opacity: .55; }
.ir-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ir-top code { font-size: 15px; font-weight: 700; letter-spacing: 1px; }
.ir-sub { font-size: 11px; color: #8a8f98; margin-top: 3px; }
body.dark .ir-sub { color: #6b7280; }

.policy-links { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12px; }
.policy-links a { color: #3b7ff2; text-decoration: none; }
.policy-links a:hover { text-decoration: underline; }
body.dark .policy-links a { color: #6ab0ff; }

.save-warn {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 2px; padding: 1px 7px;
  border-radius: 10px;
  background: #c0392b; color: #fff;
  font-size: 11px; font-weight: 600; line-height: 1.6;
  cursor: help;
}
.save-warn .ic { width: 12px; height: 12px; fill: currentColor; }
.save-warn.hidden { display: none; }

.ch-sub { font-size: 12px; color: var(--wx-sub); margin-top: 2px; }
.ch-actions { display: flex; gap: 8px; }

/* ================= 公告 / 禁言条 ================= */
.notice-bar {
  flex: 0 0 auto;
  max-height: 108px;
  overflow: auto;
  padding: 8px 16px;
  background: #fffbe6;
  border-bottom: 1px solid #f0e6bf;
  font-size: 13px;
  cursor: pointer;
}
.notice-bar .md > *:first-child { margin-top: 0; }
.notice-bar .md > *:last-child { margin-bottom: 0; }
.muted-tip {
  flex: 0 0 auto;
  padding: 6px 16px;
  background: #fde8e8;
  color: #a33;
  font-size: 12px;
}

/* ================= 消息区 ================= */
.msg-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 12px 16px 20px;
  background: var(--wx-bg);
}
.time-sep {
  text-align: center;
  color: #b2b2b2;
  font-size: 12px;
  margin: 10px 0 14px;
}
.sys-bubble {
  text-align: center;
  font-size: 12px;
  color: #8c8c8c;
  background: rgba(0, 0, 0, .05);
  display: inline-block;
  margin: 6px auto;
  padding: 3px 10px;
  border-radius: 4px;
  max-width: 80%;
  word-break: break-word;
}
.msg-scroll > .sys-bubble { display: block; margin-left: auto; margin-right: auto; width: fit-content; }

.msg-row {
  display: flex;
  gap: 10px;
  margin-bottom: 16px;
  align-items: flex-start;
}
.msg-row.me { flex-direction: row-reverse; }
.msg-body { max-width: min(620px, 68%); min-width: 0; }
.msg-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: #9a9a9a;
  margin-bottom: 4px;
}
.msg-row.me .msg-head { flex-direction: row-reverse; }
.msg-name { color: #6b6b6b; }
.msg-time { color: #b0b0b0; }

.bubble {
  position: relative;
  background: #fff;
  border-radius: 4px;
  padding: 9px 12px;
  box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
  word-break: break-word;
  overflow-wrap: anywhere;
}
.bubble::before {
  content: "";
  position: absolute;
  top: 10px;
  left: -5px;
  width: 0; height: 0;
  border: 5px solid transparent;
  border-right-color: #fff;
  border-left: 0;
}
.msg-row.me .bubble { background: var(--wx-bubble); }
.msg-row.me .bubble::before {
  left: auto; right: -5px;
  border: 5px solid transparent;
  border-left-color: var(--wx-bubble);
  border-right: 0;
}
.bubble-dead { color: #999; font-style: italic; }
.msg-scroll > .msg-row > .msg-body > .bubble { max-height: none; }

.reply-quote {
  border-left: 3px solid rgba(0, 0, 0, .18);
  padding: 2px 0 2px 8px;
  margin-bottom: 6px;
  font-size: 12px;
  color: #666;
  background: rgba(255, 255, 255, .35);
  border-radius: 2px;
}
.msg-row.me .reply-quote { background: rgba(255, 255, 255, .45); }

.msg-acts {
  display: flex;
  gap: 8px;
  margin-top: 4px;
  font-size: 11px;
  opacity: 0;
  transition: opacity .12s;
}
.msg-row:hover .msg-acts, .msg-row:focus-within .msg-acts { opacity: 1; }
/* 触屏没有 hover：撤回/编辑/移除这些操作在手机上等于不存在。
   hover: none 覆盖手机与平板，直接常显。 */
@media (hover: none) {
  .msg-acts { opacity: 1; }
}
.msg-row.me .msg-acts { justify-content: flex-end; }
.msg-act.disabled, .msg-act:disabled {
  opacity: .45;
  cursor: not-allowed;
  /* 禁用的按钮也要能被 hover 出 title —— 否则用户点了没反应，
     连解释都看不到。不设 pointer-events:none 就是为了这个。 */
}

.msg-act {
  border: 0;
  background: transparent;
  color: var(--wx-blue);
  padding: 0;
}
.msg-act:hover { text-decoration: underline; }
.msg-act.danger { color: #d93025; }

/* ================= 媒体消息 ================= */
.media-wrap { max-width: 320px; position: relative; }
.thumb {
  display: block;
  max-width: 100%;
  max-height: 260px;
  border-radius: 4px;
  cursor: zoom-in;
  background: #ddd;
  object-fit: cover;
}
video.thumb { width: 320px; height: 180px; background: #000; }
.play-badge {
  position: absolute;
  top: 90px;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px; height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .45);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  padding-left: 3px;
  cursor: pointer;
  pointer-events: auto;
}
.play-badge:hover { background: rgba(0, 0, 0, .6); }
.media-cap {
  font-size: 11px;
  color: #888;
  margin-top: 3px;
  word-break: break-all;
}
.msg-row.me .media-cap { text-align: right; }

/* ================= 输入区 ================= */
.composer {
  flex: 0 0 auto;
  border-top: 1px solid var(--wx-line);
  background: var(--wx-head);
  padding: 6px 12px 8px;
  position: relative;
}
/* 「正在输入」提示条（S-056）。
   JS 里 showTyping() 一直存在，但页面没有这个元素，
   功能静默失效了很久 —— 所以样式和 DOM 必须一起补，
   只补一边照样看不见。 */
.typing-bar {
  font-size: 12px;
  color: #1a6fd4;
  padding: 3px 8px;
  margin-bottom: 4px;
  min-height: 18px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.typing-bar::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #1a6fd4;
  animation: typing-pulse 1.2s ease-in-out infinite;
}
@keyframes typing-pulse {
  0%, 100% { opacity: .3; }
  50% { opacity: 1; }
}
body.dark .typing-bar { color: #6ab0ff; }
body.dark .typing-bar::before { background: #6ab0ff; }

.reply-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: #ececec;
  border-radius: 4px;
  padding: 5px 8px;
  font-size: 12px;
  color: #666;
  margin-bottom: 6px;
}
.composer-tools {
  display: flex;
  align-items: center;
  /* 误触代价决定间距：这排里每个邻居都会开面板或唤起系统选择器，
     指偏 8px 就换一个功能。gap 从 4 拉到 10。 */
  gap: 10px;
  margin-bottom: 4px;
}
.tool-btn {
  width: 28px; height: 28px;
  border: 0;
  background: transparent;
  font-size: 16px;
  border-radius: 4px;
  line-height: 1;
}
.tool-btn:hover { background: #e0e0e0; }
.composer-hint {
  margin-left: 6px;
  font-size: 11px;
  color: #a0a0a0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.composer textarea {
  width: 100%;
  border: 0;
  outline: none;
  resize: none;
  background: transparent;
  padding: 4px 2px;
  min-height: 60px;
  max-height: 160px;
  line-height: 1.6;
  font-size: 14px;
  font-family: inherit;
  display: block;
}
.composer-foot { display: flex; justify-content: flex-end; }
.emoji-panel {
  position: absolute;
  left: 12px;
  bottom: 84px;
  width: 268px;
  background: #fff;
  border: 1px solid var(--wx-line);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .14);
  padding: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  z-index: 30;
}
.emoji {
  width: 32px; height: 32px;
  border: 0;
  background: transparent;
  font-size: 19px;
  border-radius: 4px;
}
.emoji:hover { background: #eee; }

/* ================= 右侧成员面板 ================= */
.sp-head {
  height: 60px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 14px;
  border-bottom: 1px solid var(--wx-line);
  font-weight: 600;
}
.sp-body { flex: 1; overflow-y: auto; padding: 6px 0; }
.sp-sec { border-top: 1px solid var(--wx-line); padding: 10px 14px 14px; }
.sec-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  color: var(--wx-sub);
  margin-bottom: 6px;
}
.notice-text {
  font-size: 13px;
  color: #444;
  max-height: 190px;
  overflow: auto;
  line-height: 1.6;
  word-break: break-word;
}
.notice-text .md > *:first-child { margin-top: 0; }
.notice-text .md > *:last-child { margin-bottom: 0; }

.user-line {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  cursor: pointer;
  flex-wrap: wrap;
}
.user-line:hover { background: #efefef; }
.user-line-main { flex: 1; min-width: 0; }
.user-line-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-real { color: #a0a0a0; font-size: 12px; }
.user-line-role { font-size: 11px; color: var(--wx-sub); margin-top: 1px; }
.line-ops { display: flex; gap: 4px; flex-wrap: wrap; }

/* ================= 门禁 / 登录 ================= */
.mask {
  position: fixed;
  inset: 0;
  background: linear-gradient(160deg, #2b3038, #1d2127 60%, #222831);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  overflow: auto;
  padding: 20px;
}
.gate-card {
  width: 380px;
  max-width: 100%;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
  padding: 26px 28px 22px;
}
.gate-title { font-size: 20px; font-weight: 600; text-align: center; }
.gate-sub { text-align: center; color: #8c8c8c; font-size: 12px; margin: 6px 0 18px; }
.hr-line { height: 1px; background: #eee; margin: 14px 0; }
.form-tip {
  background: #f3f6fb;
  border-left: 3px solid var(--wx-blue);
  padding: 8px 10px;
  font-size: 12px;
  color: #4a5568;
  line-height: 1.6;
  margin-bottom: 14px;
  border-radius: 0 4px 4px 0;
}
.form-note { margin-top: 12px; font-size: 11px; color: #999; line-height: 1.6; }
.field-label { display: block; font-size: 12px; color: #666; margin: 10px 0 4px; }
.field {
  width: 100%;
  border: 1px solid #dcdcdc;
  border-radius: 4px;
  padding: 9px 10px;
  outline: none;
  background: #fafafa;
}
.field:focus { border-color: var(--wx-green); background: #fff; }
.field-hint { font-size: 11px; color: #999; margin-top: 6px; line-height: 1.6; }
.tabs {
  display: flex;
  gap: 18px;
  border-bottom: 1px solid #eee;
  margin-bottom: 14px;
}
.tab {
  border: 0;
  background: transparent;
  padding: 6px 0;
  font-size: 14px;
  color: #888;
  border-bottom: 2px solid transparent;
}
.tab.on { color: var(--wx-green); border-bottom-color: var(--wx-green); font-weight: 600; }

/* ================= 按钮 ================= */
.btn {
  border: 1px solid #d9d9d9;
  background: #fff;
  color: #333;
  border-radius: 4px;
  padding: 7px 16px;
  font-size: 13px;
}
.btn:hover { border-color: #bbb; }
.btn.primary { background: var(--wx-green); border-color: var(--wx-green); color: #fff; }
.btn.primary:hover { background: var(--wx-green-d); border-color: var(--wx-green-d); }
.btn.ghost { background: #f5f5f5; }
.btn.danger { background: #fff; border-color: #f0b4b4; color: #d93025; }
.btn.danger:hover { background: #fff1f1; }
.btn.block { display: block; width: 100%; margin-top: 16px; padding: 10px 16px; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
.mini-btn {
  border: 1px solid #d9d9d9;
  background: #fff;
  border-radius: 4px;
  padding: 3px 8px;
  font-size: 12px;
  color: #555;
}
.mini-btn:hover { border-color: #bbb; background: #fafafa; }

/* ================= 弹窗 ================= */
.modal-wrap {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .38);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 900;
  padding: 20px;
}
.modal {
  width: 480px;
  max-width: 100%;
  max-height: 86vh;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .3);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-wide { width: 720px; }
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid #eee;
  font-size: 15px;
  font-weight: 600;
}
.modal-x { border: 0; background: transparent; font-size: 18px; color: #999; line-height: 1; }
.modal-body { padding: 16px 18px; overflow: auto; flex: 1; }
.ap-body { min-height: 240px; }
.pad { padding: 4px 2px 8px; font-size: 13px; line-height: 1.7; color: #333; }
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 18px;
  border-top: 1px solid #eee;
  background: #fafafa;
}

/* ================= 提示条 ================= */
.toast-wrap {
  position: fixed;
  top: 18px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1200;
  /* 提示层不该拦截点击：它盖在内容上方，吞掉点击表现为"点了没反应" */
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
}
.toast {
  background: rgba(0, 0, 0, .78);
  color: #fff;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 13px;
  animation: toastIn .16s ease;
  max-width: 70vw;
}
.toast-ok { background: rgba(7, 193, 96, .92); }
.toast-err { background: rgba(217, 48, 37, .92); }
/* ⚠ 警告态必须有自己的样式（S-098）。
   代码里用 toast(msg,'warn') 提示"清单没提供哈希，无法验证完整性"，
   但 CSS 里原来**根本没有 .toast-warn** ——
   于是这条警告长得和普通提示一模一样（默认黑色半透明），
   用户扫一眼就过去了，等于没提示。

   这是"第三种状态"：既不是成功（绿）也不是失败（红），
   而是"能装，但没法确认它是原版"。必须视觉上和另外两种区分开，
   否则用户会把"无法验证"当成"验证过了"。 */
.toast-warn { background: rgba(214, 138, 26, .94); }
body.dark .toast-warn { background: rgba(196, 122, 18, .94); }
@keyframes toastIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; } }

/* ================= 图片 / 视频预览 ================= */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .88);
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lb-wrap { position: relative; max-width: 92vw; max-height: 92vh; }
.lb-media { max-width: 92vw; max-height: 82vh; display: block; border-radius: 4px; }
.lb-bar {
  /* 原来是 top: -38px（浮在媒体上方 38px）。
     媒体高度接近 92vh 时，这 38px 就跑到视口外面去了 —— 关闭按钮点不到，
     用户只能刷新页面才能退出预览。改成贴视口顶部。 */
  position: fixed;
  top: 10px;
  right: 12px;
  z-index: 1110;
  display: flex;
  gap: 10px;
}
.lb-btn {
  border: 0;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  border-radius: 4px;
  padding: 4px 10px;
  font-size: 12px;
}
.lb-btn:hover { background: rgba(255, 255, 255, .3); }
.lb-acts { margin-top: 10px; text-align: center; }

/* ================= 命令 / 环境 / 权限 ================= */
.cmd-list { display: flex; flex-direction: column; gap: 2px; }
.cmd-row {
  display: flex;
  gap: 12px;
  padding: 7px 8px;
  border-radius: 4px;
  font-size: 13px;
}
.cmd-row code {
  flex: 0 0 190px;
  background: #f3f4f6;
  border-radius: 3px;
  padding: 1px 6px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px;
  color: #c7254e;
}
.cmd-row.clickable { cursor: pointer; }
.cmd-row.clickable:hover { background: #f3f4f6; }

.env-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.env-item { background: #f7f8fa; border-radius: 6px; padding: 9px 12px; }
.env-k { font-size: 12px; color: #8c8c8c; }
.env-v { font-size: 13px; margin-top: 2px; word-break: break-all; }

.chip-box { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.chip {
  border: 1px solid #dcdcdc;
  background: #fff;
  border-radius: 14px;
  padding: 4px 12px;
  font-size: 12px;
  color: #555;
}
.chip.on { background: #e8f8ef; border-color: var(--wx-green); color: #07a350; }

.perm-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 14px; }
.perm-item { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 3px 0; }
.perm-table-wrap { overflow: auto; margin-bottom: 10px; }
.perm-table-wrap code { color: #a0a0a0; font-size: 11px; }

.log-list { display: flex; flex-direction: column; gap: 2px; font-size: 12px; }
.log-row {
  display: grid;
  grid-template-columns: 116px 90px 86px minmax(0, 1fr);
  gap: 8px;
  padding: 5px 6px;
  border-bottom: 1px solid #f2f2f2;
}
.log-t { color: #a0a0a0; }
.log-w { color: var(--wx-blue); }
.log-a { color: #888; }
.log-d { color: #444; word-break: break-all; }

/* ================= 用户卡 ================= */
.uc-head { display: flex; gap: 14px; align-items: center; }
.uc-nick { font-size: 17px; font-weight: 600; }
.uc-real { font-size: 12px; color: #888; margin-top: 2px; }
.uc-role { font-size: 12px; color: var(--wx-blue); margin-top: 2px; }
.uc-bio { margin: 12px 0; font-size: 13px; color: #555; line-height: 1.7; }
.uc-meta { font-size: 11px; color: #a0a0a0; }
.uc-state { margin-top: 6px; }
.uc-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.uc-acts .btn { padding: 6px 12px; }

/* ================= Markdown ================= */
.md { line-height: 1.75; }
.md > *:first-child { margin-top: 0; }
.md > *:last-child { margin-bottom: 0; }
.md-p { margin: .5em 0; }
.md-h { margin: .8em 0 .4em; font-weight: 700; line-height: 1.4; }
.md-h1 { font-size: 1.35em; }
.md-h2 { font-size: 1.22em; }
.md-h3 { font-size: 1.1em; }
.md-h4, .md-h5, .md-h6 { font-size: 1em; }
.md-quote {
  border-left: 3px solid #d8d8d8;
  padding: 2px 0 2px 10px;
  margin: .5em 0;
  color: #666;
}
.md-list { margin: .4em 0; padding-left: 1.4em; }
.md-li { margin: .15em 0; }
.md-task { list-style: none; margin-left: -1.1em; }
.md-hr { border: 0; border-top: 1px solid #e5e5e5; margin: .8em 0; }
.md-del { text-decoration: line-through; color: #999; }
.md-strong { font-weight: 700; }
.md-em { font-style: italic; }
.md-link { color: var(--wx-blue); text-decoration: none; }
.md-link:hover { text-decoration: underline; }
.md-img { max-width: 100%; border-radius: 4px; margin: 4px 0; cursor: zoom-in; }
.md-table-wrap { overflow-x: auto; margin: .5em 0; }
.md-table { border-collapse: collapse; font-size: 13px; }
.md-table th, .md-table td {
  border: 1px solid #dfe2e5;
  padding: 5px 10px;
  text-align: left;
}
.md-table th { background: #f6f8fa; font-weight: 600; }
.md-table tr:nth-child(even) td { background: #fafbfc; }
.md-code-inline {
  background: #f3f4f6;
  border-radius: 3px;
  padding: 1px 5px;
  font-family: ui-monospace, Menlo, Consolas, "Courier New", monospace;
  font-size: .92em;
  color: #c7254e;
}
.md-code-block {
  background: #f7f8fa;
  border: 1px solid #eceff2;
  border-radius: 5px;
  margin: .5em 0;
  overflow: hidden;
}
.md-code-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 3px 10px;
  background: #eef1f4;
  font-size: 11px;
  color: #8c8c8c;
}
.md-code-lang { font-family: ui-monospace, Menlo, Consolas, monospace; }
.md-copy { border: 0; background: transparent; color: var(--wx-blue); font-size: 11px; }
.md-code-block pre {
  margin: 0;
  padding: 10px 12px;
  overflow-x: auto;
  font-family: ui-monospace, Menlo, Consolas, "Courier New", monospace;
  font-size: 12.5px;
  line-height: 1.6;
}

/* ================= LaTeX 公式排版 ================= */
.md-inline-math { white-space: nowrap; }
.tex-holder { display: inline-block; vertical-align: middle; }
.tex-block {
  display: block;
  margin: .6em 0;
  text-align: center;
  overflow-x: auto;
  font-family: "Times New Roman", Cambria, STIXGeneral, serif;
  font-size: 1.05em;
  line-height: 1.5;
}
.tex-error {
  color: #d93025;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px;
  background: #fff1f1;
  border-radius: 3px;
  padding: 0 4px;
}
.tex-ord { font-style: normal; }
.tex-op, .tex-bin, .tex-rel { margin: 0 .16em; font-style: normal; }
.tex-bin { margin: 0 .22em; }
.tex-rel { margin: 0 .28em; }
.tex-open, .tex-close { font-style: normal; }
.tex-punct { margin-right: .16em; }
.tex-scripts {
  display: inline-flex;
  flex-direction: column;
  vertical-align: middle;
  font-size: .72em;
  line-height: 1.05;
  margin-left: .06em;
}
.tex-scripts > .tex-sub { order: 2; }
.tex-scripts > .tex-sup { order: 1; }
.tex-scripts.tex-only-sub { vertical-align: baseline; }
.tex-frac {
  display: inline-flex;
  flex-direction: column;
  vertical-align: -0.45em;
  text-align: center;
  margin: 0 .12em;
  font-size: .96em;
}
.tex-frac-num { padding: 0 .32em; border-bottom: 1px solid currentColor; }
.tex-frac-den { padding: 0 .32em; }
.tex-sqrt { display: inline-flex; align-items: stretch; margin: 0 .08em; }
.tex-sqrt-sign { font-size: 1.1em; transform: scaleY(1.15); }
.tex-sqrt-body { border-top: 1px solid currentColor; padding: .1em .18em 0; margin-top: .18em; }
.tex-underover {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  vertical-align: middle;
  font-size: .8em;
  line-height: 1.1;
  margin: 0 .1em;
}
.tex-underover > .tex-under { order: 3; }
.tex-underover > .tex-over { order: 1; }
.tex-matrix {
  display: inline-flex;
  flex-direction: column;
  vertical-align: middle;
  margin: 0 .2em;
}
.tex-matrix { position: relative; padding: 0 .25em; }
.tex-matrix::before, .tex-matrix::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  width: .42em;
  border: 1px solid currentColor;
}
.tex-matrix::before { left: 0; border-right: 0; }
.tex-matrix::after { right: 0; border-left: 0; }
.tex-row { display: flex; justify-content: center; }
.tex-cell { padding: .1em .32em; text-align: center; }
.tex-big { font-size: 1.2em; }
.tex-rm, .tex-text { font-family: inherit; font-style: normal; }

/* ================= 滚动条 ================= */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, .18); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, .3); }
::-webkit-scrollbar-track { background: transparent; }

/* ================= 窄屏 ================= */
@media (max-width: 1000px) {
  .app { grid-template-columns: 260px minmax(0, 1fr); }
  /* ⚠ 原来是 display:none（E-046）。
     而「成员」按钮只是 toggle 一个 .hidden 类 ——
     窄屏下按钮照样能点、类照样加删，但面板永远不显示，
     表现为"点了没反应"。用户会以为是软件坏了。

     正确做法：窄屏不是"没有成员面板"，而是"它变成浮层盖在上面"。
     所以这里改成绝对定位浮层，打开时盖住内容区。 */
  .side-panel {
    position: absolute; right: 0; top: 0; bottom: 0;
    width: min(300px, 86vw);
    z-index: 60;
    box-shadow: -8px 0 24px rgba(0, 0, 0, .18);
  }
  .side-panel.hidden { display: none; }
}
@media (max-width: 720px) {
  .app { grid-template-columns: minmax(0, 1fr); }
  .sidebar {
    position: fixed;
    z-index: 500;
    top: 0; bottom: 0; left: 0;
    width: 78%;
    max-width: 300px;
    transform: translateX(-100%);
    transition: transform .2s;
    box-shadow: 4px 0 20px rgba(0, 0, 0, .2);
  }
  .sidebar.show { transform: none; }
  .only-narrow { display: inline-flex; }
  .msg-body { max-width: 78%; }
  .env-grid, .perm-grid { grid-template-columns: 1fr; }
  .log-row { grid-template-columns: 1fr; gap: 2px; }
  .cmd-row { flex-direction: column; gap: 2px; }
  .cmd-row code { flex: none; width: fit-content; }
}

/* ================= 注册申请审核 ================= */
.audit-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid #eceff2;
  border-radius: 6px;
  margin-bottom: 8px;
  background: #fafbfc;
  flex-wrap: wrap;
}
.audit-note {
  font-size: 13px;
  color: #333;
  line-height: 1.6;
  margin: 3px 0;
  word-break: break-word;
  white-space: pre-wrap;
}
.field-hint + .btn, .field-hint + .chip-box { margin-top: 8px; }

/* ================= 在线状态 ================= */
.mem-dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #c8ccd0;
  margin-left: 5px;
  vertical-align: middle;
}
.mem-dot.on { background: #2dc100; }

/* ================= 语音消息 ================= */
.voice-msg {
  display: flex; align-items: center; gap: 8px;
  min-width: 130px; padding: 4px 2px;
}
.voice-play {
  width: 26px; height: 26px; border-radius: 50%;
  border: none; background: #07c160; color: #fff;
  font-size: 12px; cursor: pointer; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.voice-bars { display: flex; align-items: center; gap: 2px; height: 22px; flex: 1; }
.vbar { width: 2px; background: #9aa3ad; border-radius: 1px; }
.voice-bars.playing .vbar { background: #07c160; animation: vbarPulse .6s infinite; }
@keyframes vbarPulse { 0%,100% { opacity: .45 } 50% { opacity: 1 } }
.voice-dur { font-size: 12px; color: #8b9298; flex: none; }
.rec-tip { font-size: 12px; color: #e64340; font-weight: 600; }
.rec-tip.hidden { display: none; }
.tool-btn.recording { background: #e64340; color: #fff; border-radius: 4px; }

/* ================= 文件附件 ================= */
.file-dl {
  border: 1px solid #d6dae0; background: #fff; color: #07c160;
  border-radius: 4px; padding: 4px 10px; font-size: 12px; cursor: pointer;
}
.file-dl:hover { background: #f2f8f4; }

/* ================= 模式：极简 / 专业 ================= */
.pro-only { display: inline-flex; }
body.mode-simple .pro-only { display: none !important; }
.mode-btn { font-size: 12px; padding: 2px 8px; width: auto !important; }

/* ================= Markdown 工具条 ================= */
.md-bar {
  display: flex; gap: 4px; padding: 4px 6px;
  border-bottom: 1px solid #eceef0; flex-wrap: wrap;
}
.md-btn {
  min-width: 30px; height: 26px; padding: 0 7px;
  border: 1px solid #dfe2e6; background: #fff; border-radius: 4px;
  font-size: 12px; cursor: pointer; color: #333;
}
.md-btn:hover { background: #f2f5f7; border-color: #07c160; }

/* ================= 深色模式 ================= */
body.dark {
  --bg: #111214;
  --bg-panel: #16181c;
  --bg-chat: #0f1013;
  --text: #e6e6e6;
  --text-dim: #9aa0a6;
  --border: #2a2d33;
  --hover: #1e2126;
  --bubble-me: #3a6b4f;
  --bubble-other: #1f2126;
  --bubble-me-text: #eaf5ee;
  --bubble-other-text: #e6e6e6;
  --link: #7fb3ff;
  --code-bg: #1a1c21;
}

/* Q-Chat-E-108（变量接线检查抓出）
   ⚠ 上面那 4 个 --bubble-* 变量定义了**却零使用**。
   于是深色模式下气泡仍是浅色的：
     对方气泡 = .bubble 的 #fff → 深色界面里一块刺眼的白
     我的气泡 = --wx-bubble 的 #95ec69 → 亮绿，同样刺眼
   深色模式做了整站配色，唯独占了屏幕最大面积的气泡没接上。
   这种"变量齐备、规则没写"的失效最隐蔽 —— 看 :root 以为都配好了。 */
body.dark .bubble {
  background: var(--bubble-other);
  color: var(--bubble-other-text);
}
body.dark .msg-row.me .bubble {
  background: var(--bubble-me);
  color: var(--bubble-me-text);
}
body.dark .sidebar,
body.dark .side-panel,
body.dark .composer,
body.dark .chat-head { background: var(--bg-panel); border-color: var(--border); }
body.dark .msg-scroll { background: var(--bg-chat); }
/* Q-Chat-E-106（CSS 接线检查抓出）
   ⚠ .member-item 这个 class **从未存在过**，成员行实际叫 .user-line。
   于是这条 hover 规则对成员行完全不生效 ——
   深色模式下鼠标划过成员列表，只有房间列表有反馈，成员列表毫无反应。
   用户会以为成员行不可点击（而它明明是可以点的）。 */
body.dark .room-item:hover,
body.dark .user-line:hover { background: var(--hover); }

/* ⚠⚠ 深色模式下弹窗是"白框套深色芯" —— 这条最刺眼（Q-Chat-E-106）。
   .modal-card 是**不存在的名字**（弹窗容器实际叫 .modal），
   所以只有 .modal-body 拿到了深色背景，而：
     .modal      背景硬编码 #fff
     .modal-head 未定义背景 → 继承 .modal 的白
     .modal-foot 同上
   → 深色模式下弹窗的**标题栏和底部按钮栏整条是白的**，只有中间一块深色。
   这是"名字漂移"导致的规则失效：写这条规则时容器还叫 modal-card，
   后来改名成 modal，这条没跟着改，于是静默失效了三年。

   ⚠ 必须连 head / foot 一起补，只补 .modal 不够 ——
     head 和 foot 有自己的 border（#eee），在深色面板上是两条突兀的亮线。 */
body.dark .modal { background: var(--bg-panel); color: var(--text); }
body.dark .modal-body { background: var(--bg-panel); color: var(--text); }
body.dark .modal-head { border-bottom-color: #2a2f36; }
/* ⚠ .modal-foot 还自带 background:#fafafa（浅灰），
   只改边框不改背景的话，深色弹窗底部会是一条浅灰带 —— 比白边更怪。 */
body.dark .modal-foot { border-top-color: #2a2f36; background: #1b1e23; }
body.dark .field,
body.dark .input,
body.dark textarea.input {
  background: #1c1f24; color: var(--text); border-color: var(--border);
}
body.dark .md-code-block,
body.dark .md-code,
body.dark code { background: var(--code-bg); color: #e6e6e6; }
body.dark .md-table th,
body.dark .md-table td { border-color: var(--border); }
body.dark .md-table th { background: #1c1f24; }
body.dark .muted-tip,
body.dark .notice-bar { background: #1c1f24; color: var(--text-dim); }
body.dark .env-item { background: #1c1f24; }
body.dark .at-mention {
  /* 可点就得看着可点（E-054）。
     原来有蓝底却没有 cursor:pointer ——
     用户看到蓝底会去点，点了没反应，会以为是自己操作错了。 */
  cursor: pointer; background: #2d3f5a; color: #9ecbff; }
body.dark .gate-card { background: var(--bg-panel); }

/* @提及高亮 */
/* cursor:pointer 不能少（E-054）：
   有高亮样式 + 有点击处理，但没 pointer，
   用户看不出它能点 —— 视觉上就是一段带底色的普通文字。 */
.at-mention {
  cursor: pointer;
  color: #1a6fd4;
  background: #e8f2ff;
  border-radius: 3px;
  padding: 0 2px;
  font-weight: 600;
}
.at-mention.at-me {
  background: #ffd54a;
  color: #7a4b00;
}
.bubble.at-hit { box-shadow: 0 0 0 2px #ffd54a inset; }

/* 搜索结果 */
.search-list { max-height: 320px; overflow: auto; margin-top: 8px; }
.search-item {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px;
  margin-bottom: 6px; cursor: pointer;
}
.search-item:hover { background: var(--hover); }
.search-meta { font-size: 12px; color: var(--text-dim); margin-bottom: 3px; }
.search-body { font-size: 13px; word-break: break-all; }
.flash { animation: flashbg 1.2s ease; }
@keyframes flashbg {
  0%, 100% { background: transparent; }
  30% { background: #ffe9a8; }
}

/* 表情面板 */
.emoji-panel, .sym-panel { max-height: 420px; overflow: auto; }
.emoji-sec, .sym-sec { margin-bottom: 12px; }
.emoji-title, .sym-title { font-size: 12px; color: var(--text-dim); margin-bottom: 5px; }
.emoji-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; }
.emoji-cell {
  font-size: 20px; padding: 5px 0; border: none; background: transparent;
  cursor: pointer; border-radius: 5px; line-height: 1;
}
.emoji-cell:hover { background: var(--hover); }
.sym-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.sym-cell {
  padding: 7px 4px; border: 1px solid var(--border); background: var(--bg);
  border-radius: 5px; cursor: pointer; font-size: 13px; color: var(--text);
}
.sym-cell:hover { background: var(--hover); border-color: #1a6fd4; }
.sym-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }

/* ============ 头像编辑 / 日志筛选 / 搜索结果 ============ */
.avatar-edit { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.avatar-prev {
  width: 56px; height: 56px; border-radius: 6px;
  background: #e9edf1 center/cover no-repeat;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; color: #fff; flex: none;
}
.log-filter { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.field.sm { width: auto; min-width: 96px; padding: 5px 8px; font-size: 13px; flex: 1 1 auto; }
.btn.sm { padding: 5px 10px; font-size: 13px; }
.avatar-img { background-position: center; }
.form-tip.warn { background: #fff8e6; border-color: #ffe0a3; color: #8a5a00; }

/* ============ 极简模式 ============
   隐藏专业向控件，让界面保持干净。
   公式照常渲染，只是不显示语法辅助入口。 */
/* Q-Chat-E-106（CSS 接线检查抓出）
   ⚠ 极简模式原本**形同虚设**：下面 7 个选择器里，只有 .md-bar 是真实存在的，
   其余 6 个对应的元素要么不存在、要么用了别的名字。
   于是切到极简模式后，界面几乎没变化 ——
   而 "极简/专业" 是设置里的一个明确开关，切了没反应，用户只会以为按钮坏了。

   逐个核对结果：
     .md-bar          ✅ 存在（Markdown 工具条）
     .btn-sym         ❌ 符号面板按钮用的是 id="btnSym"，class 是 md-btn
     .cmd-hint        ❌ 元素从未实现
     .md-src-toggle   ❌ 元素从未实现
     .md-code-lang    ✅ 存在（markdown.js 生成）
     .code-line-no    ❌ 元素从未实现
     .msg-tools .btn-src ❌ 代码里容器叫 msg-acts、按钮叫 msg-act，没有 btn-src

   修法：**只做接线，不猜功能**。
   存在的对齐到真名字（.btn-sym → #btnSym，.msg-tools .btn-src → .msg-acts .btn-src）；
   不存在的（cmd-hint / md-src-toggle / code-line-no）保留并标注"预留" ——
   删了会丢线索，猜着实现又可能做错方向。 */
body.mode-simple .md-bar,
body.mode-simple #btnSym,
body.mode-simple .md-code-lang { display: none !important; }
/* 预留（对应元素尚未实现，规则暂不生效，勿删）：
   .cmd-hint   —— 命令输入提示
   .md-src-toggle —— Markdown 源码切换
   .code-line-no  —— 代码块行号 */
body.mode-simple .msg-acts .btn-src { display: none !important; }

/* 专业模式：显示语法辅助 */
body.mode-pro .md-bar { display: flex; }

/* 被处置消息的原文查看 */
.raw-box {
  background: var(--code-bg, #f6f7f9);
  border: 1px solid var(--border, #e3e6ea);
  border-radius: 6px;
  padding: 10px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 300px;
  overflow: auto;
}

/* ============ 权限分配（逐项分化） ============ */
.perm-editor { max-height: 60vh; overflow: auto; }
.perm-tpl { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 8px 0; }
.tpl-label { font-size: 12px; color: var(--text-dim); }
/* 套用后的撤销条（E-085-TPL）。
   必须显眼 —— 用户刚改完 40 多项，要让他一眼看到"还能反悔"。 */
.perm-undo { margin-top: 6px; padding: 6px 8px; border-radius: 6px;
  background: var(--warn-bg, #fff8e1); border: 1px solid var(--warn-line, #ffe082); }
.perm-undo-tip { font-size: 12px; color: var(--text-dim); }
.perm-sec { margin-bottom: 14px; }
.perm-sec-title {
  font-size: 13px; font-weight: 600; color: var(--link, #1a6fd4);
  border-bottom: 1px solid var(--border, #eceef0);
  padding-bottom: 4px; margin-bottom: 6px;
}
.perm-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 5px 2px; border-bottom: 1px dashed var(--border, #f0f2f4);
}
.perm-name { font-size: 13px; flex: 1; line-height: 1.4; }
.perm-code {
  display: block; font-size: 11px; color: var(--text-dim, #9aa0a6);
  font-family: ui-monospace, Menlo, Consolas, monospace;
}
.perm-sel { width: 88px; flex: none; }
.perm-sel[value="allow"], .perm-sel option[value="allow"] { color: #2dc100; }
.perm-sel option[value="deny"] { color: #e64340; }
.tag.red { background: #fde8e8; color: #c0392b; }
.tag.orange { background: #fff2e0; color: #b26a00; }
.tag.gray { background: #eef0f2; color: #7a8288; }
.tag {
  display: inline-block; font-size: 11px; padding: 1px 6px;
  border-radius: 3px; margin-left: 4px; vertical-align: middle;
}

/* ============ 置顶消息 ============ */
.pinned-bar {
  background: #fffdf5;
  border-bottom: 1px solid #f2e6c8;
  padding: 6px 12px;
  max-height: 92px;
  overflow: auto;
}
.pinned-bar.hidden { display: none; }
.pinned-head { font-size: 11px; color: #b26a00; margin-bottom: 3px; font-weight: 600; }
.pinned-row {
  font-size: 12px; color: #555; padding: 3px 6px; margin-bottom: 2px;
  background: #fff8e6; border-radius: 4px; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pinned-row:hover { background: #fff2d6; }
/* ⚠ .pinned-txt 原来**根本没有样式定义**（S-104）。
   父级 .pinned-row 写了 text-overflow:ellipsis，
   但那是作用在 flex **容器**上的，不会传给子 span。
   而 span 作为 flex item 默认 min-width:auto —— 不收缩。
   两者叠加的结果：置顶的长消息**不省略、直接撑破置顶栏**，
   把旁边的取消置顶按钮挤出去。

   min-width:0 是关键：不加它，flex:1 也压不下去。 */
.pinned-txt {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ⚠ 设置面板里的下拉框也一直没有样式（S-104）。
   浏览器默认 select 是白底黑字、带原生箭头，
   在这套界面里很突兀 —— **深色模式下尤其刺眼**：
   整个面板是深色，唯独这个框是白的。
   深色模式必须显式覆盖，否则它不会跟着变。 */
.sel {
  padding: 4px 8px;
  border: 1px solid #d8dbe0;
  border-radius: 4px;
  background: #fff;
  color: #333;
  font-size: 13px;
  max-width: 60%;
}
body.dark .sel { background: #2a2d31; color: #ddd; border-color: #43474d; }
.pinned-row b { color: #333; flex: none; }
.pinned-x {
  border: none; background: transparent; color: #b26a00;
  cursor: pointer; font-size: 12px; flex: none; padding: 0 2px;
}
body.dark .pinned-bar { background: #1e1c14; border-color: #3a3524; }
body.dark .pinned-row { background: #262218; color: #ccc; }

/* ================= 返回站点首页（全屏应用角落入口） ================= */
.back-home {
  position: fixed; left: 10px; bottom: 10px; z-index: 300;
  display: inline-flex; align-items: center;
  padding: 5px 11px; border-radius: 14px;
  background: rgba(0, 0, 0, .45); color: #fff;
  font-size: 12px; text-decoration: none;
  backdrop-filter: blur(4px);
  opacity: .55; transition: opacity .18s;
}
.back-home:hover { opacity: 1; color: #fff; }
@media (max-width: 640px) { .back-home { font-size: 11px; padding: 4px 9px; } }

/* 单机演示模式说明 */
.demo-note {
  margin-top: 8px; padding: 8px 10px;
  background: #fff8e6; border-left: 3px solid #e6a23c;
  border-radius: 0 4px 4px 0; font-size: 12.5px; line-height: 1.6; color: #8a5a00;
}
body.dark .demo-note { background: #2a2318; color: #d9b26a; }

/* 加载更早消息 */
.load-more {
  display: block; width: calc(100% - 24px); margin: 8px 12px;
  padding: 7px; border: 1px dashed var(--border); border-radius: 6px;
  background: transparent; color: var(--link); font-size: 12.5px; cursor: pointer;
}
/* Q-Chat-E-108：原来是 var(--bg-hover) —— 这个名字**从未定义过**，
   且没有 fallback。整条声明无效，悬停完全没反馈。
   正确名字是 --hover（上面刚补进 :root，浅色 #efefef / 深色 #1e2126）。 */
.load-more:hover { background: var(--hover); }

/* 消息渲染缓存的外壳：display:contents 让它不产生盒子，不影响布局 */
.msg-cache-wrap { display: contents; }


/* 侧栏底部按钮：原本 padding 3px 8px（约 26px 高），两个紧挨着。
   「管理面板」误触要手动关；「退出」已有二次确认，代价可控但仍不该这么小。 */
.side-foot .mini-btn {
  padding: 7px 14px;
  font-size: 13px;
  min-height: 32px;
}


/* ================= 按下反馈 =================
 * 三个样式表里 :active 规则原本是 0 —— 按下完全没有视觉变化。
 * 后果不只是"手感差"：用户不确定点没点中 → 反复点 → 加重误触。
 * 这里给所有可点元素统一补上。 */
button:active,
.icon-btn:active,
.tool-btn:active,
.mini-btn:active,
.tab:active,
.msg-act:active {
  transform: translateY(1px);
  filter: brightness(.92);
}
.btn:active { transform: translateY(1px); box-shadow: inset 0 1px 3px rgba(0,0,0,.18); }
.tool-btn:active { background: #d0d0d0; }
.rec-x:active { background: rgba(255,255,255,.35); }

/* 触屏：会开面板或唤起系统选择器的按钮，撑到 36px（配合已拉开的 10px 间距）。
   误触无事发生的（语音播放、表情格）保持原尺寸不动。 */
@media (hover: none) {
  .composer-tools .tool-btn { width: 36px; height: 36px; font-size: 18px; }
}


/* ================= 线性图标（替换 emoji） =================
 * 以前工具栏用 emoji：🖼 🎬 📎 🎙 😊 —— 跨平台渲染不一致，
 * 深色模式下部分 emoji 自带颜色不会跟着主题变，高分屏还发虚。
 * 换成手写 SVG，currentColor 上色，随主题自动适配。 */
.ic {
  width: 20px; height: 20px;
  display: inline-block;
  vertical-align: middle;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ic-sm { width: 16px; height: 16px; }
/* 死样式（Q-Chat-E-106 核查后确认，保留以便追溯）：
   .ic-lg        —— 规划过 22px 大号图标，实际全局只用 .ic / .ic-sm，从未用上
   .perm-grid    —— 权限面板实际用 .perm-row（flex 单列），网格版未采用
   .perm-item    —— 同上
   .rec-sec      —— 录音秒数实际用 id="recSec"，这里的 class 版选择器永不命中
   ⚠ 保留而非删除：删了会丢掉"曾经规划过什么"的线索。
     但**不要照着这些名字去接功能** —— 它们不等于"待实现的需求"。 */
.ic-lg { width: 22px; height: 22px; }
/* 实心例外（play） */
.ic-fill { fill: currentColor; stroke: none; }

/* 工具栏按钮改成装图标后，尺寸与居中需要重设 */
.tool-btn .ic { width: 20px; height: 20px; }
.tool-btn {
  display: inline-flex; align-items: center; justify-content: center;
}
@media (hover: none) {
  .composer-tools .tool-btn .ic { width: 22px; height: 22px; }
}


/* 媒体缺失占位态
 * 原来取不到媒体就是一块灰方块，用户不知道是还在加载、
 * 没权限、还是数据没了 —— 而且图片/视频静默、文件/语音有提示，
 * 同一个问题三种处理。现在统一成一个能看懂、可重试的占位态。 */
.media-missing {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 90px;
  padding: 12px;
  border: 1px dashed var(--line, #d0d5dd);
  border-radius: 6px;
  background: var(--bg2, #f7f8fa);
  color: #888;
  font-size: 12px;
  text-align: center;
}
.media-missing .ic { width: 24px; height: 24px; opacity: .5; }
.media-retry {
  margin-top: 2px;
  padding: 3px 10px;
  font-size: 12px;
  color: var(--pri, #3b7ff2);
  background: transparent;
  border: 1px solid currentColor;
  border-radius: 4px;
  cursor: pointer;
}
.media-retry:active { transform: translateY(1px); }


/* ================= PWA 更新提示 / 安装按钮 =================
 * 拿到新版本必须告诉用户。不提示的话，用户一直用缓存里的旧版，
 * 而我们以为已经上线了 —— 这正是"幽灵版本"那个坑的缓存版。 */
.sw-update {
  position: fixed;
  left: 50%; bottom: 18px;
  transform: translateX(-50%);
  z-index: 1200;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px;
  border-radius: 22px;
  background: #2b6cd4; color: #fff;
  font-size: 13px;
  box-shadow: 0 4px 16px rgba(0,0,0,.22);
}
.sw-update button {
  font: inherit; color: inherit; cursor: pointer;
}
.sw-upd-ok {
  padding: 4px 12px; border-radius: 14px;
  background: #fff; color: #2b6cd4; border: 0; font-weight: 600;
}
.sw-upd-no {
  background: transparent; border: 0; font-size: 16px;
  padding: 0 2px; opacity: .8;
}
.sw-install {
  position: fixed;
  right: 14px; bottom: 18px;
  z-index: 1200;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  border: 1px solid var(--pri, #3b7ff2);
  border-radius: 22px;
  background: #fff; color: var(--pri, #3b7ff2);
  font-size: 13px; cursor: pointer;
  box-shadow: 0 2px 10px rgba(0,0,0,.12);
}
.sw-install .ic { width: 16px; height: 16px; }
@media (max-width: 560px) {
  .sw-install { bottom: 62px; }   /* 给「← 首页」让位 */
  .sw-update  { bottom: 62px; }
}


/* ================= 免打扰状态条 / 通知设置 =================
 * 免打扰生效却在界面上不吭声，用户只会以为通知功能坏了。
 * 所以状态条是必备的，不是装饰。 */
.dnd-tip {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  background: #fff7e6;
  color: #8a5a00;
  font-size: 12px;
  border-bottom: 1px solid #ffe4b0;
}
body.dark .dnd-tip {
  background: #3a2f16; color: #ffd591; border-bottom-color: #57492a;
}
.dnd-tip.hidden { display: none; }

/* 设置面板里的分区与行 */
.pref-sec-title {
  margin: 14px 0 6px;
  font-size: 12px; font-weight: 600; color: #888;
  letter-spacing: .5px;
}
.pref-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line, #eceef1);
  font-size: 13px;
}
.pref-row:last-child { border-bottom: 0; }
.pref-row input[type="checkbox"] { width: 18px; height: 18px; flex: none; }
.pref-note { font-style: normal; color: #999; font-size: 12px; }


/* ================= 会话提醒（模仿微信 / QQ 的分级） =================
 * 分级：普通未读 = 红点数字；被 @ = 红色文字标签，优先级高于未读。
 * 免打扰的会话不显红点（既然说了不提醒就别制造焦虑），但静音图标必须可见。 */
.badge.at {
  background: #e5453a;
  color: #fff;
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 600;
}
.room-mute {
  display: flex; align-items: center;
  color: #b0b6be;
  margin-bottom: 3px;
}
.room-mute .ic { width: 14px; height: 14px; }

/* 未读时会话名加粗 —— 微信的做法，一眼扫出哪条有新消息 */
.room-item.has-unread .room-name { font-weight: 600; }
.room-item.has-unread .room-preview { color: #666; }
body.dark .room-item.has-unread .room-preview { color: #aaa; }

/* 通知授权引导条 */
.notify-cta {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px;
  background: #eef4ff;
  color: #2b5cb8;
  font-size: 12px;
  border-bottom: 1px solid #d5e3fb;
}
body.dark .notify-cta {
  background: #1d2536; color: #9dc0ff; border-bottom-color: #2c3648;
}
.notify-cta.hidden { display: none; }
.notify-cta .btn { flex: none; }
.notify-cta-x {
  margin-left: auto; flex: none;
  background: transparent; border: 0; cursor: pointer;
  color: inherit; opacity: .6; font-size: 15px; padding: 0 2px;
}


/* 登录页「记住账号」 */
.remember-row {
  display: flex; align-items: center; gap: 7px;
  margin: 10px 0 2px;
  font-size: 13px; color: #666; cursor: pointer;
}
.remember-row input { width: 16px; height: 16px; }
body.dark .remember-row { color: #aaa; }


/* 改密码后展示新密码：字号要大，要能一眼看清 */
.newpwd-box {
  background: #f3f6fb; border: 1px dashed #b9c9e3; border-radius: 6px;
  padding: 12px; text-align: center; margin: 6px 0 10px;
}
.newpwd-box code { font-size: 20px; font-weight: 700; letter-spacing: 1px; color: #2b5cb8; }
body.dark .newpwd-box { background: #1d2536; border-color: #3a4a66; }
body.dark .newpwd-box code { color: #9dc0ff; }


/* 本机桥接入口：探测到客户端才显示，平时不占地方 */
.bridge-box { margin-top: 14px; }
.bridge-box.hidden { display: none; }
.bridge-line {
  height: 1px; background: var(--line, #eceef1);
  margin: 0 0 12px; position: relative;
}
.bridge-line::after {
  content: '或'; position: absolute; left: 50%; top: -9px;
  transform: translateX(-50%);
  background: #fff; padding: 0 8px;
  font-size: 12px; color: #aaa;
}
body.dark .bridge-line::after { background: #171c22; }
.bridge-title { font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.bridge-note { font-size: 12px; color: #999; margin-top: 6px; }

/* 更新对话框里的下载源速度列表 */
.src-item {
  font-size: 12px; line-height: 1.9; color: #666;
  font-variant-numeric: tabular-nums;
}
.src-item:first-of-type { color: var(--pri, #2b5cb8); font-weight: 600; }
body.dark .src-item { color: #8d97a8; }
body.dark .src-item:first-of-type { color: #9dc0ff; }

/* 下载进度条 */
.dl-prog { margin-top: 10px; }
.dl-bar {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: rgba(127,127,127,.18); margin-bottom: 6px;
}
.dl-bar > i {
  display: block; height: 100%; width: 0;
  background: var(--pri, #3b7ff2); transition: width .2s linear;
}
.dl-prog > span { font-size: 12px; color: #888; font-variant-numeric: tabular-nums; }
body.dark .dl-prog > span { color: #8d97a8; }

/* 「已编辑」角标：跟在正文末尾，小字、弱化，不抢内容 */
.msg-edited {
  display: inline-block; margin-left: 6px;
  font-size: 11px; color: #999; vertical-align: baseline;
  cursor: help;
}
body.dark .msg-edited { color: #6f7889; }

/* 「待发送」角标：离线期间攒下的消息，联网后自动发出 */
.msg-pending {
  display: inline-block; margin-left: 6px;
  font-size: 11px; color: #b8860b;
  border: 1px solid currentColor; border-radius: 3px;
  padding: 0 4px; cursor: help;
}
body.dark .msg-pending { color: #d8b34a; }

/* 「未发出」：与「待发送」区分开。
   待发送是"排队中，迟早会发"；未发出是"根本没排上队，不会自动发"。
   两者混用一个样式，用户就分不清哪条需要自己动手。
   用红色 + 可点击，明确表示"这条要你处理"。 */
.msg-failed {
  display: inline-block; margin-left: 6px;
  font-size: 11px; color: #c0392b;
  border: 1px solid currentColor; border-radius: 3px;
  padding: 0 4px; cursor: pointer;
}
.msg-failed:hover { background: #c0392b; color: #fff; }
body.dark .msg-failed { color: #e8735f; }
body.dark .msg-failed:hover { background: #e8735f; color: #1a1d21; }
