:root{
  --bg:#ffffff; --bg-soft:#f9f9f9; --bg-side:#f9f9f9; --bg-hover:#ececec; --bg-input:#ffffff;
  --bg-card:#ffffff;   /* v3.38.0：补定义——cmd-dropdown / ms-bar 背景依赖此变量 */
  --panel:var(--bg);   /* 面板/浮层背景（原引用未定义，补上，跟随 --bg） */
  --bg-panel:var(--bg-soft);   /* 模态框/面板卡片背景（原引用未定义，补上，跟随 --bg-soft） */
  --border:#e5e5e5; --border-soft:#f0f0f0;
  --text:#202123; --text-sub:#6b6b6e; --text-faint:#9b9b9e;
  --text-soft:var(--text-sub);   /* 柔和文字（原引用未定义，补上，跟随 --text-sub） */
  --text-muted:var(--text-faint);   /* 弱化文字（原引用未定义，补上，跟随 --text-faint） */
  --accent:#10a37f; --accent-hover:#0e8f6f; --accent-soft:#e6f6f1;
  --danger:#ef4444; --danger-soft:#fdeaea;
  --code-bg:#1e1e1e; --code-text:#d4d4d4;
  --bubble-user:#10a37f; --bubble-user-text:#ffffff;
  --shadow:0 4px 16px rgba(0,0,0,.08);
  --radius:14px;
  /* v4.31.3：交互动效的统一节拍。各处手写的 .15s/.2s 保持原样不动，
     这两条只给**新增**规则用 —— 以后要整体调速只改这里。 */
  --dur-1:.14s; --dur-2:.22s; --ease-std:cubic-bezier(.4,0,.2,1);
  /* v4.31.3：让浏览器按当前主题绘制**原生控件**。
     原来深色主题下，滚动条、<select> 下拉、checkbox、日期选择器仍是白底 ——
     在手机上尤其突兀（一整块亮色从深色界面里跳出来）。 */
  color-scheme:light;
}
[data-theme="dark"]{
  --bg:#212121; --bg-soft:#2b2b2b; --bg-side:#171717; --bg-hover:#383838; --bg-input:#2f2f2f;
  --bg-card:#2b2b2b;   /* v3.38.0：补定义——cmd-dropdown / ms-bar 深色背景 */
  --panel:var(--bg);   /* 跟随 --bg（深色面板） */
  --bg-panel:var(--bg-soft);   /* 模态框/面板卡片背景（跟随 --bg-soft） */
  --border:#3a3a3a; --border-soft:#333;
  --text:#ececec; --text-sub:#a6a6a6; --text-faint:#777;
  --text-soft:var(--text-sub);   /* 跟随 --text-sub（深色柔和文字） */
  --text-muted:var(--text-faint);   /* 弱化文字（跟随 --text-faint） */
  --accent:#10a37f; --accent-hover:#19b78e; --accent-soft:#1e3430;
  --danger:#f87171; --danger-soft:#3a2323;
  --code-bg:#0d0d0d; --code-text:#d4d4d4;
  --bubble-user:#10a37f; --bubble-user-text:#ffffff;
  --shadow:0 4px 16px rgba(0,0,0,.4);
  /* v4.31.3：深色主题下原生控件也走深色（滚动条 / select / checkbox 等） */
  color-scheme:dark;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",Roboto,sans-serif;
  background:var(--bg);color:var(--text);font-size:15px;line-height:1.65;
  /* v4.52.0：字体平滑 —— 与用户参照的那个界面（DSH）对齐。
     同一款字体（Windows 上西文 Segoe UI / 中文微软雅黑），加不加这条的观感差别很明显：
     不加时字更粗更糊。这是他说的"看着更小更清楚"的一半来源。 */
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  /* ★ v4.57.1：**中西混排的字距**。
     本机原来用的是浏览器默认 —— Chrome 实测计算值是 `no-autospace`，
     于是「这段代码用 getComputedStyle() 量」这种中英夹杂看起来**挤在一起**
     （中文与西文之间不留任何间隙）；用户参照的那个界面显式开了 `normal`。
     ⚠️ **字面量要关掉它**（见下面 `code,pre` 那条）：自动间距会打乱等宽字面量里的列对齐，
     那比"挤一点"更糟。 */
  text-autospace:normal;
  overflow:hidden;
}
/* 代码/命令是**字面量**：列对齐比字距重要 ⇒ 关掉自动间距（照参照界面的做法） */
code,pre{text-autospace:no-autospace}
/* ⚠️ v4.51.32：这里我本来加了一条 `@media(max-width:520px){body{font-size:14px}}`，
   实测后发现**用错了杠杆**：消息正文走的是 `.md` 的**绝对**字号（下面 768px 断点那条），
   body 降到 14px 根本不会影响正文，只会把侧栏/提示这类继承 body 的文字压得过小。
   ⇒ 已撤回；真正的修法是把 `.md` 降下来（见 768px 断点内的注释）。 */
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
input,textarea,select{font-family:inherit;color:inherit}
svg{flex-shrink:0}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-thumb{background:rgba(128,128,128,.35);border-radius:8px}
::-webkit-scrollbar-track{background:transparent}

/* ===== 视图切换 ===== */
#view-login{display:flex;align-items:center;justify-content:center;height:100%;background:var(--bg-soft);padding:24px}
#view-app{display:none;height:100%}

/* ===== 登录页 ===== */
.login-card{background:var(--bg);border:1px solid var(--border);border-radius:18px;padding:40px 36px;width:100%;max-width:400px;box-shadow:var(--shadow)}
.login-logo{display:flex;align-items:center;gap:12px;justify-content:center;margin-bottom:8px}
.login-logo .logo-ic{width:44px;height:44px;border-radius:12px;background:var(--accent);display:flex;align-items:center;justify-content:center;color:#fff}
.login-card h1{font-size:20px;text-align:center;font-weight:700;margin-bottom:4px}
.login-card .sub{text-align:center;color:var(--text-sub);font-size:13px;margin-bottom:28px}
.field{margin-bottom:16px}
.field label{display:block;font-size:13px;color:var(--text-sub);margin-bottom:6px;font-weight:500}
.field input,.field textarea,.field select{
  width:100%;padding:12px 14px;font-size:16px;border:1px solid var(--border);border-radius:10px;
  background:var(--bg-input);outline:none;transition:border .15s;
}
.field input:focus,.field textarea:focus{border-color:var(--accent)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;border-radius:10px;font-size:15px;font-weight:600;min-height:46px;transition:background .15s,opacity .15s}
.btn-primary{background:var(--accent);color:#fff;width:100%}
.btn-primary:hover{background:var(--accent-hover)}
.btn-ghost{background:var(--bg-hover);color:var(--text)}
.btn-ghost:hover{filter:brightness(.96)}
.btn-danger{background:var(--danger);color:#fff}
.btn[disabled]{opacity:.5;cursor:not-allowed}
.form-err{color:var(--danger);font-size:13px;min-height:20px;margin:4px 0 8px;text-align:center}
.login-tip{font-size:12px;color:var(--text-faint);text-align:center;margin-top:18px;line-height:1.6}
kbd{background:var(--bg-hover);border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:11px;font-family:ui-monospace,'Microsoft YaHei'}

/* ===== 应用布局 ===== */
#view-app{display:none;flex-direction:column}
.app-body{display:flex;flex:1;min-height:0;position:relative}
.topbar{
  height:56px;display:flex;align-items:center;gap:10px;padding:0 14px;
  border-bottom:1px solid var(--border-soft);background:var(--bg);z-index:30;flex-shrink:0;
}
.topbar .brand{font-weight:700;font-size:16px;display:flex;align-items:center;gap:8px}
.icon-btn{width:40px;height:40px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;color:var(--text-sub);transition:background .15s}
/* v4.38.0：顶栏「API 余额」胶囊 —— 常驻显示、点击刷新。
   放在搜索图标左边；数字用等宽数字避免刷新时宽度跳动（tabular-nums）。*/
.balance-pill{
  display:inline-flex;align-items:center;justify-content:center;
  height:26px;padding:0 10px;margin-right:6px;border-radius:999px;
  border:1px solid var(--border);background:var(--bg-soft);
  color:var(--text);font-size:12px;line-height:1;
  font-variant-numeric:tabular-nums;
  white-space:nowrap;cursor:pointer;
  transition:background .15s,border-color .15s,color .15s;
}
.balance-pill:hover{background:var(--bg-hover)}
.balance-pill:active{transform:translateY(1px)}
/* 未刷新过 / 查询失败：弱化，别让它看起来像"确定的数字" */
.balance-pill.stale{color:var(--text-faint);border-color:var(--border-soft)}
/* 正在查：半透明 + 不可点，避免连点打外部接口 */
.balance-pill.busy{opacity:.55;pointer-events:none}
/* 余额偏低（<=10 元，与后端 format_accounts 的默认阈值一致）：给个温和的警示色 */
.balance-pill.low{color:var(--danger);border-color:var(--danger);background:var(--danger-soft)}
@media(max-width:520px){
  .balance-pill{height:24px;padding:0 8px;font-size:11px;margin-right:4px}
}
.icon-btn:hover{background:var(--bg-hover);color:var(--text)}
/* ---- ⋯ 更多菜单 ---- */
.more-wrap{position:relative}
.more-menu{position:absolute;right:0;top:calc(100% + 6px);min-width:220px;max-width:280px;
  background:var(--panel);border:1px solid var(--border);border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,.18);
  padding:6px;z-index:60;font-size:14px;
  /* v4.38.1：**菜单必须能滚**。手机上 20+ 项约 740px，原来没有 max-height，
     超出屏幕的部分直接看不见、也没法滚 —— 用户反馈「太长了，手机显示不开」。*/
  max-height:min(72vh,620px);overflow-y:auto;overscroll-behavior:contain}

/* v4.38.1：折叠分组 —— 把低频项收进 <details>，菜单从 20+ 行缩到 ~13 行。
   ⚠️ **只折叠、不删除**：展开后每一项照旧能点（按钮与功能不许悄悄消失）。*/
.mm-fold{margin:0}
.mm-fold>summary{list-style:none;cursor:pointer}
.mm-fold>summary::-webkit-details-marker{display:none}
.mm-fold>summary::after{content:"▸";margin-left:auto;font-size:11px;opacity:.55}
.mm-fold[open]>summary::after{content:"▾"}
.mm-fold-body{padding-left:6px;margin-left:10px;border-left:2px solid var(--border-soft)}
.more-menu[hidden]{display:none}
.mm-item{padding:9px 12px;border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:8px;
  color:var(--text);user-select:none}
.mm-item:hover{background:var(--bg-hover)}
.mm-hint{font-size:12px;color:var(--text-faint);margin-right:2px}
.mm-item.mm-toggle{cursor:default;justify-content:space-between;padding:9px 12px}
.mm-item.mm-toggle:hover{background:transparent}
.mm-item.mm-toggle>span{flex:1}
.mm-item.mm-toggle input{width:16px;height:16px;cursor:pointer}
.mm-item.mm-danger{color:var(--danger)}
/* v4.38.1：手机上让 ⋯ 菜单更紧凑（行高压下来，配合 max-height 滚动，
   整份菜单一屏能看全，不用来回滚）。 */
@media(max-width:520px){
  .more-menu{min-width:200px;max-width:80vw;max-height:min(76vh,540px);font-size:13.5px;padding:5px}
  .mm-item{padding:7px 10px;border-radius:7px}
  .mm-item.mm-toggle{padding:7px 10px}
  .mm-fold-body{margin-left:6px;padding-left:5px}
}
.mm-item.mm-danger:hover{background:rgba(220,38,38,.08)}
/* ★★★ v4.59.58（2026-10-13）：**选择模型菜单的"和谐化"** —— 用户要求照 DSH 那个插件的样子。
   加四样（全部是"有数据才显示"，没有就不占位）：
     ① 搜索框（38 个模型靠眼睛找已费劲，渠道只会越加越多）
     ② 能力标签（`model_caps[].caps` → 中文名）
     ③ 上下文窗口（`model_caps[].ctx`）
     ④ 费用标记（`model_fees[]` —— **与渠道级 tag 分开显示**，同渠道里可能两者都有）
   ⚠️ 配色一律用主题变量（`--text-faint` / `--border` / `--bg-hover`）⇒ 暗色/亮色自动适配。*/
.mm-searchwrap{display:flex;align-items:center;gap:8px;padding:2px 8px 8px}
#mm-model-search{flex:1;padding:7px 10px;font-size:13px;border-radius:8px;
  border:1px solid var(--border);background:var(--bg-input,transparent);color:var(--text);outline:none}
#mm-model-search:focus{border-color:var(--accent,#3b82f6)}
.mm-searchcount{font-size:11.5px;color:var(--text-faint);white-space:nowrap}
.mm-caps{display:inline-flex;gap:4px;flex-wrap:wrap;margin-left:2px}
.mm-cap{font-size:10.5px;line-height:1.5;padding:0 5px;border-radius:6px;
  background:var(--bg-hover);color:var(--text-faint);border:1px solid var(--border-soft,rgba(127,127,127,.25))}
.mm-ctx{font-size:10.5px;color:var(--text-faint);margin-left:2px;white-space:nowrap}
.mm-fee{margin-left:2px}
/* ★★★ v4.59.74（2026-10-13，用户要的"倍率"）：模型名后的积分倍率（`x0.79`）。
   照客户端的样子：**等宽小字 + 淡色**，不抢模型名的注意力；数字越大越费积分。 */
.mm-rate{margin-left:4px;font-size:11px;font-style:normal;font-variant-numeric:tabular-nums;
  padding:0 5px;border-radius:6px;color:var(--text-sub);
  background:var(--bg-hover);border:1px solid var(--border-soft,rgba(127,127,127,.22))}
.mm-noresult{margin-top:8px;line-height:1.6}
/* v4.59.164：**「无缓存」标记** —— 模型菜单里那个模型不返回前缀缓存时标一下。
   依据是 `usage_log` 全量实测（`hy3` 12 次、`glm-5.3-flash` 27 次，命中恒 0）。
   样式刻意与 `.mm-rate` 同族（等宽小字、淡底），但用**灰**而不是彩色 ——
   它不是"警告"，是"这条路上少一个省钱手段"的信息。 */
.mm-nocache{margin-left:4px;font-size:11px;font-style:normal;white-space:nowrap;
  padding:0 5px;border-radius:6px;color:var(--text-faint);
  background:transparent;border:1px dashed var(--border-soft,rgba(127,127,127,.35))}
/* ★★★ v4.59.61：**三个号的积分速览**（用户要"一眼看出下一个用哪个"）。
   布局：一行横排、可换行；每个号一个小胶囊。*/
.wb-credit{display:flex;align-items:center;gap:6px;padding:5px 10px 8px;flex-wrap:wrap;
  font-size:12px;color:var(--text-faint)}
.wb-acc{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;
  background:var(--bg-hover);border:1px solid var(--border-soft,rgba(127,127,127,.22))}
.wb-acc b{font-weight:600;color:var(--text)}
/* ⭐ 当前号：实心圆点 + 强调色（"你正在用这个"） */
.wb-acc.cur{border-color:var(--accent,#3b82f6);color:var(--accent,#3b82f6);font-weight:500}
.wb-acc.cur b{color:var(--accent,#3b82f6)}
/* ⭐ 积分最多的**非当前**号：虚线描边（"下一个建议用它"） */
.wb-acc.best{border-style:dashed;border-color:var(--warning,#d97706)}
.wb-acc.best b{color:var(--text)}
/* 「切换」按钮：小、可点、不进 tab 流（避免误触键盘导航） */
.wb-switch{margin-left:2px;padding:0 6px;border-radius:999px;font-size:11px;cursor:pointer;
  color:var(--accent,#3b82f6);background:transparent;border:1px solid currentColor;text-decoration:none}
.wb-switch:hover{background:var(--bg-hover)}
/* 国际版：标"独立"（不可切） */
.wb-indep{margin-left:2px;padding:0 6px;border-radius:999px;font-size:11px;
  color:var(--text-faint);border:1px solid var(--border-soft,rgba(127,127,127,.25))}
/* 查不到的号：灰掉（**仍然列出来** —— 隐藏会让人以为那个号没了） */
.wb-acc.off{opacity:.5}
.wb-credit-stale{color:var(--warning,#d97706)}
.wb-credit-dom{opacity:.7;font-size:11px}
.mm-sep{height:1px;background:var(--border);margin:4px 8px}

/* ---- Router 来源标签（聊天回复下） ---- */
.router-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;padding-top:8px;border-top:1px dashed var(--border)}
.router-tag{font-size:11.5px;padding:3px 8px;border-radius:6px;background:var(--bg-soft);
  color:var(--text-soft);border:1px solid var(--border);display:inline-flex;align-items:center;gap:4px}
.router-tag.kb{color:#0a7d5e;background:#e6f7f1;border-color:#b8e6d4}
.router-tag.search{color:#7c3aed;background:#f3edff;border-color:#d6c2f7}
.router-tag.vps{color:#c2410c;background:#fff3eb;border-color:#fbd1b8}
.router-tag.memory{color:#1e6fdb;background:#e8f1ff;border-color:#bbd4f7}
.router-tag.router{color:#666;background:var(--bg-soft);border-color:var(--border)}



/* ===== 侧栏 ===== */
.sidebar{
  width:270px;background:var(--bg-side);border-right:1px solid var(--border-soft);
  display:flex;flex-direction:column;flex-shrink:0;transition:transform .25s ease,width .22s ease;z-index:40;
}
.side-top{padding:10px 10px 6px}
.new-chat-btn{
  display:flex;align-items:center;gap:10px;width:100%;padding:12px 14px;border-radius:12px;
  border:1px solid var(--border);background:var(--bg);font-size:14px;font-weight:600;min-height:46px;
  transition:background .15s,box-shadow .15s;
}
.new-chat-btn:hover{background:var(--bg-hover);box-shadow:var(--shadow)}
.conv-list{flex:1;overflow-y:auto;padding:6px 8px 12px;contain:content}
.conv-group{font-size:11px;font-weight:700;color:var(--text-faint);padding:12px 8px 4px;text-transform:uppercase;letter-spacing:.5px}
.conv-item{
  display:flex;align-items:center;gap:8px;padding:10px 10px;border-radius:10px;cursor:pointer;
  font-size:13.5px;color:var(--text-sub);transition:background .12s;min-height:44px;
}
.conv-item:hover{background:var(--bg-hover)}
.conv-item.active{background:var(--bg-hover);color:var(--text);font-weight:600}
.conv-item .t{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.conv-item .t mark{background:var(--accent);color:#fff;border-radius:3px;padding:0 2px;font-weight:600}
.conv-item .del{opacity:0;width:28px;height:28px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--text-faint)}
.conv-item:hover .del{opacity:1}
.conv-item .del:hover{color:var(--danger);background:rgba(239,68,68,.12)}
.side-foot{border-top:1px solid var(--border-soft);padding:8px;display:flex;flex-direction:column;gap:2px}
.side-foot button{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;font-size:13px;color:var(--text-sub);min-height:40px}
.side-foot button:hover{background:var(--bg-hover);color:var(--text)}
.overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:35}

/* ===== 聊天主区 ===== */
.chat-main{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--bg);position:relative}
.chat-scroll{flex:1;overflow-y:auto;scroll-behavior:auto;overscroll-behavior:contain}
.chat-inner{max-width:820px;margin:0 auto;padding:24px 20px 40px;display:flex;flex-direction:column;gap:22px}

/* ===== 对话大纲栏（右侧悬浮目录：列出本会话全部提问，点击跳转） ===== */
.chat-outline{position:absolute;right:16px;top:14px;width:212px;max-height:62vh;display:none;flex-direction:column;
  background:var(--bg-soft);border:1px solid var(--border);border-radius:12px;
  box-shadow:0 6px 22px rgba(0,0,0,.13);overflow:hidden;z-index:6}
.chat-outline.show{display:flex}
.ol-head{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:8px 10px;
  font-size:12px;color:var(--text-sub);border-bottom:1px solid var(--border);flex:none}
.ol-head b{font-weight:600}
.ol-toggle{width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;
  color:var(--text-faint);font-size:15px;line-height:1}
.ol-toggle:hover{background:var(--bg-hover);color:var(--text)}
.ol-body{overflow-y:auto;padding:6px;display:flex;flex-direction:column;gap:2px}
.ol-item{display:block;width:100%;text-align:left;padding:6px 8px;border-radius:8px;
  font-size:12.5px;line-height:1.45;color:var(--text-sub);cursor:pointer;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ol-item:hover{background:var(--bg-hover);color:var(--text)}
.ol-item.active{background:var(--accent);color:#fff}
.ol-fab{position:absolute;right:16px;top:14px;z-index:6;display:none;width:34px;height:34px;border-radius:10px;
  align-items:center;justify-content:center;color:var(--text-sub);background:var(--bg-soft);
  border:1px solid var(--border);box-shadow:0 4px 14px rgba(0,0,0,.12)}
.ol-fab.show{display:flex}
.ol-fab:hover{background:var(--bg-hover);color:var(--text)}
/* 跳转定位时的高亮闪烁 */
.msg.jump-flash .bubble{animation:jumpFlash 1.3s ease}
@keyframes jumpFlash{0%,70%{outline:2px solid var(--accent);outline-offset:3px}100%{outline:2px solid transparent;outline-offset:3px}}
/* ═══ ★ v4.57.0：会话内导航**细轨**（`#turn-rail`）═══
   每轮一个刻度，常显、只占 24px 宽、不挡任何内容；点一下跳到那一轮。
   与 `#chat-outline`（列表面板）**互斥显示**（判据在 `renderTurnRail()` 里）——
   面板在窄屏是底部抽屉（要打开、会挡视线），细轨才是手机上"快速定位"的那条路。
   ★ 刻度本身 `pointer-events:none`：整条轨是**一个**指针目标，按 Y 反算落在第几格
     （见 `bindTurnRail()`）—— 2px 高的刻度逐个做热区，既难点中又要 N 个监听器。 */
/* ★★★ v4.59.70（2026-10-13，用户要求）：**细轨从"屏幕右侧"挪到"左侧、AI 头像那一列"**。
   用户原话：「把对话里面右边那个小方块（就是显示有多少个对话的那个）改一下，
             放到 AI 回答的头像下方。因为那个地方始终是空白的」
   + 追问后确认：「它不就是定位的吗？多个对话在同一个页面里始终都能用…功能是有的，只是挪个位置」
   ⇒ **只改位置与形状；功能一个字不动**。
      `renderTurnRail` / `bindTurnRail` / `updateRailActive` / `jumpToMsg` **全部照旧** ——
      它们靠 `getBoundingClientRect()` 算命中与高亮，**与 left/right 无关** ⇒ 挪位置不影响点击。
   ★ 定位参照：它的父级是 `<main class="chat-main">`（**有 position:relative**）
     ⇒ `left` 的 0 点 = **聊天区左边缘**（不含侧栏）⇒ 写 `left:12px` 就落在头像那一列。
     实测头像左边缘：桌面 `.chat-inner{padding:24px 20px}` ⇒ 头像在 20px；
     窄屏 `padding:12px 6px` ⇒ 头像在 6px。
     ⇒ 取 **`left:12px`**（两档都在头像正下方那条竖线上，且不压住头像/气泡）。
   ★ 形状：用户说"小方块" ⇒ 从 12×2 短横改成 **10×10 圆角方块**（保留近/活跃的放大与变色）。
   ★ 为什么**不**挂进某条消息的 DOM：细轨属于**整个会话**（不是某一条消息），
     挂进消息会在每一轮都复制一份 ⇒ 屏幕上出现 N 条重复的轨。
     留在会话容器里、只把 `right` 改 `left` ⇒ 既在"头像那一列"，又**全页常驻可用**、
     也不会重复（这正是用户说的"多个对话在同一个页面里始终都能用"）。 */
/* ★★★ v4.59.73（2026-10-13，用户第四次澄清 —— **这次我理解对了**）：
   用户原话：「肯定要在我这个页面中央居中啊！我只是说，左边头像下方有空白区域，
             有时候是没有头像的。怎么那么轴呢？」
   ⇒ 两条要点（我前面理解反了，纠正）：
     ① **纵向要"页面居中"**（不是"贴某个头像下面"）—— 会话滚到哪儿它都在中间那条线上
     ② **横向在左边那一列**（因为"那里有空白"）—— 但**不许依赖头像存在**
        （"有时候是没有头像的"：AI 消息还没生成、或某些渲染路径下没有 `.avatar`）
   ⇒ 实现：
     · `left` 由 JS 实测（有头像就对头像中心；**没有头像 ⇒ 退到聊天区左侧内边距那列**）
     · `top:50%` ＋ `translateY(-50%)` = **真正的竖直居中**（纯 CSS，不依赖任何元素）
   ★ 为什么横向仍需 JS：聊天内容**居中**（`.chat-inner` max-width + margin:auto），
     左列位置随视口宽度变 ⇒ 纯 CSS 推不准（我推过，真浏览器一量差 16px）。
     但**兜底必须有**：`left:var(--rail-left, 20px)` ⇒ JS 没跑到/没头像时也在左列，不会跑去右边。 */
.turn-rail{position:absolute;
  left:var(--rail-left, 20px);right:auto;
  top:50%;transform:translateY(-50%);          /* ★ 竖直居中（不依赖任何元素） */
  display:none;flex-direction:column;justify-content:center;gap:7px;
  width:24px;
  max-height:min(52vh,460px);
  overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:none;z-index:6;touch-action:manipulation}
.turn-rail::-webkit-scrollbar{display:none}
.turn-rail.show{display:flex}
/* ★ 方块：10×10、圆角 3px（用户口径"小方块"）；悬浮/当前用**放大 + 变色**表达 */
.tr-mark{display:block;width:10px;height:10px;margin:0 auto;padding:0;border:0;
  border-radius:3px;background:var(--border);pointer-events:none;
  transition:transform .14s ease,background-color .14s ease}
.tr-mark.near{transform:scale(1.35);background:var(--text-sub)}
.tr-mark.active{transform:scale(1.5);background:var(--accent)}
.tr-mark:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.tr-mark{transition:none}}
/* ★★ v4.53.0：这里原来是 `@media (max-width:1080px){ .chat-outline,.ol-fab{display:none!important} }`
   —— 一句"窄屏空间不够"就把**整个会话内导航在手机上藏没了**（实测 390 视口下两者都是 0×0）。
   用户要的"快速定位到我发的内容"**功能本来就齐**（本会话问题面板 + 点击跳转 + 滚动联动高亮，
   `renderChatOutline`/`jumpToMsg`/`updateOutlineActive`），差的只是**在手机上放出来**。
   ★ 又：JS 端**没有**按宽度关门（只看问题条数与用户偏好）⇒ 改 CSS 即可，无需动 JS。
   改法：面板 → **底部抽屉**（左右贴边、底部留安全区、最高 52vh）；浮标 → 常驻在输入框上方；
   列表项加高加粗，手机上手点得到。 */
@media (max-width:1080px){
  .chat-outline{position:fixed;left:8px;right:8px;bottom:calc(8px + env(safe-area-inset-bottom));top:auto;
    width:auto;max-height:52vh;border-radius:14px;z-index:42;
    box-shadow:0 -6px 26px rgba(0,0,0,.22)}
  /* ★ v4.53.1：位置让位 + 半透明。原来写死 `bottom:86px`，而右下角本来就有
     「↑ 回到上一条」按钮（`.to-top-btn`：`bottom:calc(var(--composer-h,104px) + 14px)`、`z-index:60`）
     ⇒ 两者挤在同一列、几乎贴在一起（用户反馈"遮住了一些东西"）。
     现在：跟着**同一个 `--composer-h`** 让位到它上方 60px，`z-index:59` 让它永远排在箭头之下；
     平时 `opacity:.55`（就算飘在正文上也看得清），按下时恢复不透明。**一行 CSS 解决，不动 JS。** */
  .ol-fab{position:fixed;right:14px;bottom:calc(var(--composer-h,104px) + 60px);top:auto;
    opacity:.55;z-index:59}
  .ol-fab:active{opacity:1}
  .ol-head{padding:10px 12px;font-size:12.5px}
  .ol-item{font-size:13px;line-height:1.5;padding:9px 10px}
}

/* 欢迎页 */
.welcome{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:20px}
.welcome .logo-big{width:64px;height:64px;border-radius:18px;background:var(--accent);display:flex;align-items:center;justify-content:center;color:#fff;margin-bottom:20px;box-shadow:0 8px 24px rgba(16,163,127,.35)}
.welcome h2{font-size:24px;margin-bottom:8px;font-weight:800}
.welcome p{color:var(--text-sub);font-size:14px;margin-bottom:28px;max-width:460px}
.key-warn{margin-top:22px;padding:12px 16px;border-radius:12px;background:var(--danger-soft);color:var(--danger);font-size:13px;display:none;align-items:center;gap:8px;max-width:560px}

/* 墨墨背单词 */
.mm-check{display:flex;align-items:flex-start;gap:8px;font-size:13px;color:var(--text-sub);cursor:pointer;margin:2px 0 13px;line-height:1.6}
.mm-check input{margin-top:2px;flex-shrink:0}
/* 划词查词卡片 */
.mm-pop{position:fixed;z-index:80;width:330px;max-width:calc(100vw - 24px);background:var(--bg);border:1px solid var(--border);border-radius:14px;box-shadow:0 14px 44px rgba(0,0,0,.22);padding:14px 15px;font-size:13.5px;animation:fadein .16s ease}
.mm-pop-top{display:flex;align-items:center;gap:8px;margin-bottom:4px}
.mm-pop-word{font-size:18px;font-weight:800;letter-spacing:.2px}
.mm-pop-x{margin-left:auto;cursor:pointer;color:var(--text-sub);font-size:19px;line-height:1;padding:0 3px;user-select:none}
.mm-pop-x:hover{color:var(--text)}
.mm-pop-sec{margin-top:10px}
.mm-pop-sec .lb{font-size:11.5px;color:var(--text-sub);font-weight:700;margin-bottom:3px}
.mm-pop-sec .tx{line-height:1.7;word-break:break-word}
.mm-pop-ph{background:var(--bg-soft);border-radius:9px;padding:7px 10px;margin-top:6px;line-height:1.65}
.mm-pop-ph .en{font-weight:600}
.mm-pop-ph .cn{color:var(--text-sub);font-size:12.5px;margin-top:2px}
.mm-pop-empty{color:var(--text-sub);font-size:12.5px;line-height:1.7}
.mm-pop-act{display:flex;gap:8px;margin-top:13px}
.mm-pop-act .btn{flex:1}
/* 生词本页 */
.voc-prog{display:flex;gap:10px;margin-bottom:14px}
.voc-prog .pg{flex:1;background:var(--bg-soft);border-radius:12px;padding:11px 13px}
.voc-prog .n{font-size:21px;font-weight:800;line-height:1.2}
.voc-prog .l{font-size:11.5px;color:var(--text-sub);margin-top:3px}
.voc-chips{display:flex;flex-wrap:wrap;gap:7px;margin:11px 0 4px;min-height:4px}
.voc-chip{display:inline-flex;align-items:center;gap:6px;background:var(--bg-soft);border:1px solid var(--border-soft);border-radius:999px;padding:5px 6px 5px 11px;font-size:13px}
.voc-chip.ok{border-color:var(--accent);color:var(--accent)}
.voc-chip.bad{border-color:var(--danger);color:var(--danger)}
.voc-chip .x{cursor:pointer;opacity:.55;font-size:15px;line-height:1;padding:0 3px;user-select:none}
.voc-chip .x:hover{opacity:1}
.voc-today{font-size:12.5px;color:var(--text-sub);line-height:1.9}
.voc-today b{color:var(--text)}
.voc-empty{color:var(--text-sub);font-size:12.5px;line-height:1.8;padding:14px;background:var(--bg-soft);border-radius:10px;text-align:center}
/* 今日例句 */
.sent-item{border-top:1px solid var(--border-soft);padding:11px 0}
.sent-item:first-child{border-top:none;padding-top:2px}
.sent-word{display:flex;align-items:center;gap:8px;font-size:15px}
.sent-word b{letter-spacing:.2px}
.sent-from{font-size:11px;color:var(--text-sub);background:var(--bg-soft);border-radius:999px;padding:2px 8px}
.sent-en{margin-top:5px;line-height:1.7;font-size:13.5px;display:flex;align-items:flex-start;gap:6px}
.sent-cn{margin-top:3px;line-height:1.7;font-size:12.5px;color:var(--text-sub)}
.sent-spk{flex-shrink:0;background:none;border:none;cursor:pointer;font-size:14px;padding:1px 3px;opacity:.6;line-height:1.2}
.sent-spk:hover{opacity:1}
.sent-spk.playing{opacity:1;color:var(--accent)}
/* 助记短文：一段一个卡片，生词在文里描红 */
.sent-story{border-top:1px solid var(--border-soft);padding:12px 0}
.sent-story:first-child{border-top:none;padding-top:2px}
.sent-story-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.sent-story-no{font-size:12px;font-weight:600;color:var(--accent);
  background:var(--bg-soft);border-radius:999px;padding:2px 9px}
.sent-words{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px;align-items:center}
.sent-w{font-size:11.5px;color:var(--text-sub);background:var(--bg-soft);
  border:1px solid var(--border-soft);border-radius:999px;padding:2px 8px;cursor:pointer;
  transition:all .15s;white-space:nowrap}
.sent-w:hover{color:var(--accent);border-color:var(--accent)}
.sent-hl{color:var(--accent);font-weight:600;background:rgba(255,196,0,.14);
  border-radius:3px;padding:0 2px}
.sent-story .sent-en{margin-top:7px;display:block}

/* 消息 */
.msg{display:flex;gap:12px;animation:fadein .25s ease}
@keyframes fadein{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.msg.user{flex-direction:row-reverse}
.avatar{width:32px;height:32px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}
.msg.ai .avatar{background:var(--accent);color:#fff}
.msg.user .avatar{background:var(--bg-hover);color:var(--text-sub);order:2}
.msg-body{max-width:calc(100% - 100px);min-width:0}
.msg.user .msg-body{order:1;display:flex;flex-direction:column;align-items:flex-end}
.bubble{border-radius:var(--radius);padding:12px 16px;overflow-wrap:break-word;word-break:break-word}
.msg.user .bubble{background:var(--bubble-user);color:var(--bubble-user-text);border-bottom-right-radius:6px;white-space:pre-wrap}
.msg.ai .bubble{background:var(--bg-soft);border-bottom-left-radius:6px}
/* v4.46.11：消息操作按钮**常显**（原来 opacity:0，只有鼠标悬停在那条消息上才浮现，
   用户因此完全不知道有「引用 / 分支」这些功能）。现在默认半透明、悬停变实，
   并且按钮是「图标 + 文字」而不是光秃秃一个图标 —— 见 index.html 的 _mkActBtn。 */
.msg-actions{display:flex;flex-wrap:wrap;gap:2px;margin-top:6px;opacity:.5;transition:opacity .15s}
.msg:hover .msg-actions,.msg-actions:focus-within{opacity:1}
.msg-actions button{width:auto;height:26px;padding:0 8px;gap:5px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--text-faint);font-size:12px;line-height:1}
.msg-actions button:hover{background:var(--bg-hover);color:var(--text)}
.msg-actions .act-tx{white-space:nowrap}
/* AI 加词建议卡片 */
.add-words-card{margin-top:12px;padding:12px 14px;border:1px solid var(--border);border-radius:12px;background:var(--bg-soft)}
.add-words-card .aw-title{font-size:12.5px;color:var(--text-faint);margin-bottom:9px;line-height:1.5}
.add-words-card .aw-words{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:11px}
.add-words-card .aw-chip{font-size:13px;padding:3px 11px;border-radius:20px;background:var(--accent-soft);color:var(--accent);font-weight:600}
.add-words-card .aw-btn{width:auto;height:auto;padding:7px 16px;font-size:13.5px}
.typing-cursor{display:inline-block;width:7px;height:16px;background:var(--accent);vertical-align:-2px;margin-left:2px;animation:blink 1s steps(1) infinite;border-radius:1px}
@keyframes blink{50%{opacity:0}}
.thinking-wrap{font-size:12.5px;color:var(--text-faint);border-left:2px solid var(--border);padding:4px 6px 4px 12px;margin-bottom:10px;border-radius:6px;background:var(--bg-soft)}
.thinking-label{font-size:11px;font-weight:500;letter-spacing:.3px;display:flex;align-items:center;gap:6px;color:var(--text-faint);cursor:pointer;user-select:none;padding:2px 0}
.thinking-label:hover{color:var(--text-sub)}
.thinking-label .chev{margin-left:auto;transition:transform .15s}
.thinking-label .chev.open{transform:rotate(90deg)}
.thinking-body{white-space:pre-wrap;word-break:break-word;line-height:1.6;font-style:italic;max-height:300px;overflow-y:auto;margin-top:4px;display:none}
.thinking-wrap.open .thinking-body{display:block}
/* v4.46.12：**思考过程可见化**（用户原话：「等半天它也不显示任何东西……有时候都不知道
   它是在正常思考还是卡住了」）。三件事：① 首字到达前给一个**会动的**"正在思考"指示，
   让"在跑"和"卡死"能区分开；② 折叠时的标签必须**明说可以点开**（否则等于没内容）；
   ③ 刚答完的这一条**默认展开**思考，不让它"闪一下就没"。 */
.thinking-wrap.pending{border-left-color:var(--accent)}
.thinking-label .think-hint{font-size:11px;color:var(--text-faint);opacity:.8;margin-left:2px}
.thinking-label .think-secs{font-variant-numeric:tabular-nums;color:var(--text-sub);font-weight:500}
.think-dots{display:inline-flex;gap:3px;align-items:center}
.think-dots i{width:4px;height:4px;border-radius:50%;background:currentColor;animation:thinkBlink 1.2s infinite}
.think-dots i:nth-child(2){animation-delay:.2s}
.think-dots i:nth-child(3){animation-delay:.4s}
@keyframes thinkBlink{0%,80%,100%{opacity:.22}40%{opacity:1}}
.err-bubble{background:var(--danger-soft)!important;color:var(--danger)}
.empty-answer-tip{margin-top:10px;padding:8px 10px;font-size:12.5px;color:var(--text-faint);background:var(--bg-soft,#f6f6f6);border:1px dashed var(--border);border-radius:8px;line-height:1.5}

/* Markdown 渲染 */
.md{font-size:15px}
.md>*+*{margin-top:12px}
.md h1,.md h2,.md h3,.md h4{font-weight:700;line-height:1.4;margin-top:20px}
.md h1{font-size:1.35em}.md h2{font-size:1.22em}.md h3{font-size:1.1em}.md h4{font-size:1em}
.md ul,.md ol{padding-left:22px}
.md li+li{margin-top:4px}
.md a{color:var(--accent);text-decoration:none;border-bottom:1px solid transparent}
.md a:hover{border-bottom-color:var(--accent)}
.md blockquote{border-left:3px solid var(--accent);padding:2px 0 2px 14px;color:var(--text-sub)}
.md hr{border:none;border-top:1px solid var(--border);margin:16px 0}
.md code.inline{background:var(--bg-hover);padding:2px 6px;border-radius:5px;font-family:'JetBrains Mono','Fira Code','SF Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,'Microsoft YaHei';font-size:.88em}
.md table{border-collapse:collapse;width:100%;display:block;overflow-x:auto;font-size:.92em}
.md th,.md td{border:1px solid var(--border);padding:7px 12px;text-align:left}
.md th{background:var(--bg-hover);font-weight:600}
.md strong{font-weight:700}
.code-block-wrap{position:relative;margin:14px 0;border-radius:12px;overflow:hidden;border:1px solid rgba(255,255,255,.08)}
.code-head{display:flex;align-items:center;justify-content:space-between;background:#161616;padding:7px 12px}
.code-lang{color:#9b9b9b;font-size:11.5px;font-family:ui-monospace,'Microsoft YaHei';text-transform:lowercase;letter-spacing:.4px}
.code-copy{display:flex;align-items:center;gap:5px;color:#9b9b9b;font-size:11.5px;padding:3px 8px;border-radius:6px;min-height:26px}
.code-copy:hover{background:rgba(255,255,255,.1);color:#fff}
.code-block-wrap pre{background:var(--code-bg);color:var(--code-text);padding:14px 16px;overflow-x:auto;font-size:13px;line-height:1.6}
.code-block-wrap code{font-family:'JetBrains Mono','Fira Code','SF Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,'Microsoft YaHei'}
.tok-k{color:#569cd6}.tok-s{color:#ce9178}.tok-c{color:#6a9955}.tok-f{color:#dcdcaa}.tok-n{color:#b5cea8}.tok-t{color:#4ec9b0}

/* ===== 输入区 ===== */
.composer-wrap{padding:0 20px calc(14px + env(safe-area-inset-bottom));background:linear-gradient(transparent,var(--bg) 30%)}
.composer{max-width:820px;margin:0 auto;position:relative}
.mode-bar{display:flex;gap:6px;flex-wrap:wrap;margin:8px 2px 6px}
.mode-chip{font-size:12px;padding:4px 10px;border-radius:999px;border:1px solid var(--border);
  background:transparent;color:var(--text-sub);cursor:pointer;transition:all .15s;line-height:1.4}
.mode-chip:hover{border-color:var(--accent);color:var(--accent)}
.mode-chip.active{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
.mode-bar-sep{width:1px;height:16px;background:var(--border);margin:0 2px}
.mode-chip.alarm-chip{border-color:var(--danger, #e05d5d);color:var(--danger, #e05d5d)}
.mode-chip.alarm-chip:hover{background:rgba(224,93,93,.08)}
/* v4.59.164：报警经验库抽屉里那个"去做一次分析"的按钮（满宽，占一行） */
.alarm-lib-new{display:block;width:100%;margin:0 0 12px}
/* 报警分析弹窗 */
.alarm-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1200;display:flex;align-items:center;justify-content:center;padding:20px}
.alarm-panel{background:var(--bg);border:1px solid var(--border);border-radius:18px;width:100%;max-width:560px;max-height:86vh;display:flex;flex-direction:column;box-shadow:var(--shadow)}
.alarm-head{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border)}
.alarm-head h3{margin:0;font-size:16px}
.alarm-body{padding:18px 20px;overflow-y:auto}
.alarm-actions{display:flex;gap:8px;margin:4px 0 14px}
.alarm-out{margin-top:6px}
.alarm-card{border:1px solid var(--border);border-radius:12px;padding:14px 16px;background:var(--bg-soft);display:flex;flex-direction:column;gap:10px}
.alarm-card .ac-head{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px}
.alarm-card .ac-code{font-size:15px;font-weight:700;color:var(--danger,#e05d5d)}
.alarm-card .ac-tag{font-size:11px;color:var(--text-faint);border:1px solid var(--border);border-radius:6px;padding:1px 7px}
.alarm-card .ac-sec{font-size:12px;font-weight:700;color:var(--text-sub);margin:2px 0 4px}
.alarm-card .ac-text{font-size:13px;line-height:1.7;color:var(--text)}
.alarm-card .ac-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
.alarm-card .ac-list li{font-size:13px;line-height:1.6;padding-left:16px;position:relative;color:var(--text)}
.alarm-card .ac-list li::before{content:"▸";position:absolute;left:0;color:var(--accent)}
.alarm-card .ac-src{font-size:11px;color:var(--text-faint);margin-top:2px}
.alarm-card .ac-actions{display:flex;gap:8px;margin-top:4px;flex-wrap:wrap}
.alarm-loading{color:var(--text-sub);font-size:13px;text-align:center;padding:14px}
.alarm-err{color:var(--danger,#e05d5d);font-size:13px;text-align:center;padding:10px;background:rgba(224,93,93,.07);border-radius:8px}
.composer-box{
  display:flex;align-items:flex-end;gap:8px;background:var(--bg-soft);border:1px solid var(--border);
  border-radius:22px;padding:10px 12px;transition:border .15s,box-shadow .15s;
}
.composer-box:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px rgba(16,163,127,.12)}
#input{
  flex:1;min-width:0;border:none;background:transparent;resize:none;outline:none;font-size:16px;line-height:1.5;
  min-height:24px;max-height:180px;padding:6px 2px;color:var(--text);box-sizing:content-box;
}
#input::placeholder{color:var(--text-faint)}
.send-btn{width:44px;height:44px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;transition:background .15s,transform .1s;flex-shrink:0}
.send-btn:hover{background:var(--accent-hover)}
.send-btn:active{transform:scale(.94)}
.send-btn.stop{background:var(--text-sub)}
/* ===== v4.30.1：用量 + 模型说明 = **最下面那一行** =====
   用户原话：「token 放在最最下面，和显示的大模型放在一起，字体一样。」

   v4.30.0 我把它挪进了上面那排（● Auto / 联网），**理解错了** ——
   他说的「大模型显示的文字」是最底下这行「当前：Auto · 模型名」，
   不是上面那个模型按钮。而上面那排窄屏放不下会折行，于是 token 夹在
   「联网」和「✨自动 ⚡快速 🔍深度」中间 —— 就是他看到的那个别扭位置。

   现在：左边用量胶囊、右边模型说明，**同一行**，两段**字号完全相同** ——
   都取 `--note-fs`，改一个变量两边一起变，物理上不可能再"不一样大"。 */
.status-line{max-width:820px;margin:7px auto 0;--note-fs:12.5px}
.sl-row{display:flex;align-items:center;gap:10px;min-width:0}
.composer-note{flex:1;min-width:0;font-size:var(--note-fs);color:var(--text-faint);
  text-align:right;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.usage-bar{flex:0 0 auto;font-size:var(--note-fs);color:var(--text-sub);
  user-select:none;-webkit-user-select:none}
.usage-bar.empty{opacity:.55}
.ub-line{display:flex;align-items:center;gap:5px;flex-wrap:nowrap;white-space:nowrap;
  overflow:hidden;padding:3px 10px;border-radius:999px;background:var(--bg-soft);
  border:1px solid var(--border);cursor:pointer;transition:border-color .15s}
.ub-line:hover{border-color:var(--accent)}
.ub-body{display:flex;align-items:center;gap:5px;min-width:0;overflow:hidden;white-space:nowrap}
.ub-item{display:inline-flex;align-items:center;gap:4px}
.ub-k{color:var(--text-faint)}
.ub-v{color:var(--text);font-weight:700;font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum";letter-spacing:.2px}
.ub-sep{color:var(--border)}
.ub-unit{color:var(--text-faint)}
/* v4.59.161：本对话的**前缀缓存命中率**（有数据才显示，见 renderUsageBar）。
   ★ 不加下划线/背景：它和「本对话 N tokens」是同一行信息，不该抢首位。 */
.ub-cache{color:var(--text-faint);white-space:nowrap}
.ub-cache b{font-weight:700;font-variant-numeric:tabular-nums}
.ub-caret{flex-shrink:0;color:var(--text-faint);font-size:10px}
.ub-detail{margin-top:6px;padding:9px 12px;border-radius:12px;background:var(--bg-soft);
  border:1px solid var(--border);font-size:12px;line-height:1.9}
.ub-row{display:flex;gap:12px;flex-wrap:wrap;align-items:baseline}
.ub-rk{color:var(--text-faint);min-width:86px}
.ub-row b{color:var(--text);font-weight:700;font-variant-numeric:tabular-nums}
.ub-hint{margin-top:6px;padding-top:6px;border-top:1px dashed var(--border);
  font-size:11px;line-height:1.7;color:var(--text-faint)}
/* 引用回复条 */
.quote-bar{display:flex;align-items:center;gap:8px;background:var(--bg-soft);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:10px;padding:7px 10px;margin-bottom:8px;font-size:12.5px;color:var(--text-sub)}
.quote-bar svg{color:var(--text-faint);flex-shrink:0}
.quote-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-sub)}
.quote-close{width:20px;height:20px;border-radius:50%;background:var(--bg-hover);color:var(--text-faint);font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center;padding:0;flex-shrink:0}
.quote-close:hover{color:var(--text)}
/* 图片上传 */
.img-preview-row{display:flex;gap:8px;padding:0 2px 8px;flex-wrap:wrap}
/* v4.29.1：**空着的时候不要占地方。**
   用户问「联网和底下的快速/深度之间为什么有个大的白空间」—— 就是这里：

     模型栏(margin-bottom 8) + 图片行(空，但 padding-bottom 8)
     + 文档行(空，但 padding-bottom 8) + 模式栏(margin-top 8) = **32px 纯白**

   这两行本来只在"选了图片/文档"时才该出现，却一直以 8px 内边距的形式占着位。
   原来的外层 margin 之间还能互相抵消，中间插进两个带 padding 的空盒子之后
   抵消不掉了，于是白得特别明显。

   用 `:empty` 而不是让 JS 去切 class：HTML 里它们是真空的、JS 清空时写的是
   `innerHTML = ""`，所以 `:empty` 永远跟真实状态一致 —— 不会出现
   "忘了同步 class，于是选了图反而不显示" 这种两处状态打架的问题。 */
.img-preview-row:empty,
.doc-chip-row:empty{display:none}
.img-thumb{position:relative;width:62px;height:62px;border-radius:10px;overflow:hidden;
  border:1px solid var(--border);background:var(--bg-soft);flex-shrink:0}
.img-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.img-thumb .x{position:absolute;top:2px;right:2px;width:18px;height:18px;border-radius:50%;
  background:rgba(0,0,0,.55);color:#fff;border:0;font-size:12px;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0}
.img-thumb .x:hover{background:rgba(0,0,0,.75)}
.img-btn{width:40px;height:40px;border-radius:12px;border:1px solid var(--border);background:transparent;
  color:var(--text-sub);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;
  transition:border-color .15s,color .15s,background .15s}
.img-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
/* v4.30.0：单条强制联网按钮 —— 打开时**自己长出文字**。
   原来它是个孤零零的放大镜，用户问「到底是干嘛的？我也不知道是干嘛的」。
   打开时从 40×40 方块变成带字的小胶囊「🔍 本条联网」，关掉就缩回方块。 */
#web-force-btn.on{width:auto;min-width:40px;padding:0 10px;gap:5px}
.web-force-label{font-size:12px;line-height:1;white-space:nowrap;font-weight:600}
.web-force-label:empty{display:none}
/* v3.15.2：联网开关按钮 + 模式徽标（恢复 2026-09-04 移除的对话栏联网 UI） */
.web-btn{display:inline-flex;align-items:center;gap:4px;flex-shrink:0;padding:5px 10px;font-size:12px;
  border:1px solid var(--border);border-radius:8px;background:var(--bg-input);color:var(--text-soft);
  cursor:pointer;min-height:30px;transition:all .15s}
.web-btn:hover{border-color:var(--accent);color:var(--accent)}
.web-btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
.web-btn.disabled{opacity:.4;pointer-events:none}
.img-btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
.img-btn.disabled{opacity:.4;pointer-events:none}
/* v4.46.4：**联网按钮「二合一」** —— 状态直接长在按钮里。
   原来「联网」按钮旁边挂着一个独立的「自动/强制/关闭」徽标，但两者表达的
   **本来就是同一件事**（开=自动 / 关=关闭），并排显示只会让人以为是
   "两个功能互相影响"（用户原话：「我点了联网，那个自动就灭掉」）。
   现在只有一个控件，状态写在它自己身上；手机上也不再被隐藏（原来
   `.net-mode-badge` 在窄屏是 `display:none`，等于手机上根本看不到状态）。 */
.web-btn .web-btn-mode{
  font-size:10.5px;line-height:1.6;padding:0 6px;border-radius:8px;margin-left:1px;
  background:rgba(255,255,255,.24);color:inherit;white-space:nowrap;flex-shrink:0}
/* 关闭态：按钮本身是灰底，里面的标签也要跟着变"弱"，否则白底白字看不见 */
.web-btn:not(.on) .web-btn-mode{
  background:var(--bg-soft);color:var(--text-faint);border:1px solid var(--border)}
/* 强制联网（单条）：整个按钮换橙，一眼看出"这条一定会搜" */
.web-btn.force{background:#e08e0b;border-color:#e08e0b;color:#fff}
.web-btn.force:hover{border-color:#c97c06;color:#fff}
.msg-imgs{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.msg-imgs img{max-width:220px;max-height:220px;border-radius:10px;border:1px solid var(--border);display:block}
/* v4.39.1：消息里的**文件附件**（📎 名字 + 大小 + ↓ 下载）。
   让聊天记录里能看见"我发过这个文件" —— 用户反馈过「发了文件，历史对话里看不到」。 */
.msg-attach{display:inline-flex;align-items:center;gap:8px;margin-top:8px;padding:7px 10px;
  border:1px solid var(--border);border-radius:9px;background:var(--bg-soft);
  font-size:12.5px;max-width:100%;line-height:1.2}
.msg-attach .ic{flex-shrink:0;font-size:14px}
.msg-attach .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)}
.msg-attach .sz{flex-shrink:0;color:var(--text-faint);font-size:11px;font-variant-numeric:tabular-nums}
.msg-attach .dl{flex-shrink:0;color:var(--accent);cursor:pointer;white-space:nowrap;font-size:11.5px}
.msg-attach .dl:hover{text-decoration:underline}
/* 文档问答 */
.doc-chip-row{display:flex;gap:8px;padding:0 2px 8px;flex-wrap:wrap}
/* 空的文档行同样不占地方（理由见上面 .img-preview-row 那段） */
.doc-chip-row:empty{display:none}
.doc-chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:5px 10px;border-radius:999px;
  background:var(--accent-soft);color:var(--accent);font-size:12.5px;border:1px solid var(--accent)}
.doc-chip b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
.doc-chip .x{width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,.12);color:inherit;border:0;
  font-size:11px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;flex-shrink:0}
.doc-menu{position:absolute;bottom:calc(100% + 8px);right:0;z-index:60;min-width:280px;max-width:min(94vw,340px);
  background:var(--bg-panel);border:1px solid var(--border);border-radius:12px;
  box-shadow:0 12px 32px rgba(0,0,0,.18);padding:8px;display:none;max-height:60vh;overflow-y:auto}
.doc-menu.open{display:block}
.dm-head{font-size:11.5px;color:var(--text-faint);padding:4px 6px 8px;line-height:1.6}
.dm-up{width:100%;display:flex;align-items:center;justify-content:center;gap:6px;padding:9px 10px;border-radius:8px;
  border:1px dashed var(--border);background:transparent;color:var(--text-sub);font-size:13px;cursor:pointer;
  transition:border-color .15s,color .15s;margin-bottom:6px}
.dm-up:hover{border-color:var(--accent);color:var(--accent)}
.dm-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border-radius:8px;border:0;
  background:transparent;color:var(--text);font-size:13px;cursor:pointer;text-align:left;min-height:38px}
.dm-item:hover{background:var(--bg-hover)}
.dm-item.active{background:var(--accent-soft);color:var(--accent)}
.dm-item .t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dm-item .n{font-size:11px;color:var(--text-faint);flex-shrink:0}
/* v4.39.0：文档项的「↓ 下载原文件」按钮（只有存了原档才渲染）。仿 .del 的样式，
   悬停用 accent 色而不是 danger —— 下载是"正面"动作，别跟删除一个颜色。 */
.dm-item .dl{width:22px;height:22px;border-radius:6px;border:0;background:transparent;color:var(--text-faint);
  font-size:14px;line-height:1;cursor:pointer;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center}
.dm-item .dl:hover{color:var(--accent);background:var(--accent-soft)}
.dm-item .del{width:22px;height:22px;border-radius:6px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:13px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.dm-item .del:hover{color:var(--danger);background:var(--danger-soft)}
.dm-empty{font-size:12px;color:var(--text-faint);padding:10px 6px;line-height:1.6}
/* ── v4.50.0：视频入口菜单。定位与 .doc-menu 完全同一套（祖先 .composer 是 position:relative），
      所以它和文档菜单不会一个贴边、一个飘。 ── */
.video-menu{position:absolute;bottom:calc(100% + 8px);right:0;z-index:60;width:min(94vw,340px);
  background:var(--bg-panel);border:1px solid var(--border);border-radius:12px;
  box-shadow:0 12px 32px rgba(0,0,0,.18);padding:10px;display:none}
.video-menu.open{display:block}
.vm-input{width:100%;box-sizing:border-box;padding:9px 10px;border-radius:8px;
  border:1px solid var(--border);background:var(--bg-soft);color:var(--text);
  font-size:13px;margin-bottom:8px;font-family:inherit}
.vm-input:focus{outline:none;border-color:var(--accent)}
/* ── v4.50.0：视频结果卡片 ── */
.video-card{margin-top:4px;padding:9px 10px;border-radius:10px;
  border:1px solid var(--border);background:var(--bg-soft)}
.vc-title{font-size:12.5px;color:var(--text);line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.vc-sub{font-size:11px;color:var(--text-faint);margin-top:3px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vc-kb{font-size:11px;color:var(--text-faint);margin-top:5px;line-height:1.5;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vc-kb.on{color:#0a7d5e}
/* v4.51.12：字幕太长，中间那段没进模型上下文 —— 用户必须知道（他的原话：
   「读多少次都没关系，你得跟我说」）。同样不用 --danger：这不是错误。 */
.vc-warn{font-size:11px;color:var(--text-sub);margin-top:5px;line-height:1.5}
.vc-link{display:inline-block;font-size:11.5px;color:var(--accent);margin-top:6px}
.vc-link:hover{text-decoration:underline}
/* ── v4.50.0：新对话首屏的能力提示 ──
   ⚠️ 选择器必须是 `.welcome p.cap-hint`（两个类 + 元素）：
     只写 `.cap-hint` 的话，`.welcome p`（0,1,1）的 `margin-bottom:28px` 会赢，
     这条提示就会被撑开一大截、把「尚未配置 API Key」那块挤下去。 */
.welcome p.cap-hint{font-size:12.5px;color:var(--text-faint);line-height:1.7;
  margin-bottom:6px;max-width:460px}
.welcome p.cap-hint b{color:var(--text-sub);font-weight:600}

/* ===== 模型选择器（贴合输入框上方） ===== */
.topbar-spacer{margin-left:auto}
.model-bar{position:relative;display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-wrap:wrap}
.model-btn{
  display:inline-flex;align-items:center;gap:7px;max-width:min(360px,72vw);
  padding:6px 12px;border:1px solid var(--border);border-radius:999px;
  background:var(--bg-soft);color:var(--text);font-size:13px;cursor:pointer;
  transition:border-color .15s,background .15s;
}
.model-btn:hover{border-color:var(--accent)}
.model-btn.open{border-color:var(--accent);background:var(--accent-soft)}
.model-btn .chev{opacity:.55;flex-shrink:0;transition:transform .18s}
.model-btn.open .chev{transform:rotate(180deg)}
.model-btn-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tag-badge{display:inline-block;flex-shrink:0;font-size:10px;line-height:1.5;padding:1px 5px;border-radius:5px;
  font-weight:700;letter-spacing:.02em;border:1px solid transparent;white-space:nowrap}
.tag-badge[hidden]{display:none}
.tag-badge.paid{color:#b45309;background:rgba(245,158,11,.16);border-color:rgba(245,158,11,.42)}
.tag-badge.free{color:#15803d;background:rgba(34,197,94,.14);border-color:rgba(34,197,94,.38)}
.tag-badge.trial{color:#1d4ed8;background:rgba(96,165,250,.14);border-color:rgba(96,165,250,.38)}
.tag-badge.auto{color:#7c3aed;background:rgba(139,92,246,.15);border-color:rgba(139,92,246,.42)}
.mm-note{margin:0 10px 6px 30px;font-size:11px;line-height:1.5;color:var(--text-faint)}
.mm-sep{margin:8px 10px;border-top:1px solid var(--border)}
.img-btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
.msg-sources{margin-top:10px;padding-top:9px;border-top:1px solid var(--border)}
.token-stats{margin-top:8px;padding-top:7px;border-top:1px dashed var(--border);
  font-size:12px;color:var(--text-faint);letter-spacing:.2px;line-height:1.5}
.token-stats b{color:var(--text-muted);font-weight:600}
.src-head{font-size:11px;color:var(--text-faint);margin-bottom:6px;display:flex;align-items:center;gap:5px}
.src-list{display:flex;flex-direction:column;gap:3px}
.src-item{display:flex;align-items:baseline;gap:6px;font-size:12px;color:var(--accent);
  text-decoration:none;padding:3px 5px;border-radius:6px;line-height:1.4}
.src-item:hover{background:var(--bg-hover)}

/* ── v4.59.14：词汇卡片（W1）──────────────────────────────────────────────
   ★ 类名清单**唯一出处**是 `wordcard.WC_CLASSES`（21 条）——
     `_test_v45914_wordcard.py` 有一条断言：**每一条都必须在这里找到规则**（删一条 ⇒ 红）。
     防的是"卡片裸奔"：编译不报、服务端也不报，只有用户看到一堆没样式的按钮。
   ★ 只用 `:root` 里**已有**的变量 ⇒ 深色主题自动跟随（本文件深色主题只重定义变量）。 */
.wc{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);
  padding:10px 12px;margin:8px 0;box-shadow:var(--shadow)}
.wc-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 8px;line-height:1.9}
.wc-phon{font-size:12px;color:var(--text-sub);font-family:ui-monospace,Menlo,Consolas,monospace}
.wc-spk{border:0;background:transparent;cursor:pointer;font-size:14px;padding:0 2px;
  color:var(--accent);transition:transform var(--dur-1) var(--ease-std)}
.wc-spk:hover{transform:scale(1.15)}
.wc-tr{font-size:15px;color:var(--text);white-space:pre-wrap}
.wc-tier{font-size:11px;color:var(--accent);background:var(--accent-soft);
  border-radius:10px;padding:1px 7px;cursor:help}
.wc-collins{font-size:12px;color:var(--accent);letter-spacing:1px}
.wc-ox{font-size:10px;color:var(--text-faint);border:1px solid var(--border-soft);
  border-radius:8px;padding:0 5px}
.wc-tag{font-size:10px;color:var(--text-faint);background:var(--bg-soft);
  border-radius:8px;padding:0 5px}
.wc-lemma{font-size:11px;color:var(--text-faint)}
.wc-fold{margin-top:8px;border-top:1px solid var(--border-soft);padding-top:8px;
  font-size:12px;color:var(--text-sub)}
.wc-ex{line-height:2}
.wc-exi{background:var(--bg-soft);border-radius:6px;padding:1px 6px;margin-right:5px;
  font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px}
.wc-dfn{white-space:pre-wrap;color:var(--text-sub)}
.wc-gen{white-space:pre-wrap;margin-top:4px}
.wc-eg{margin-top:6px;color:var(--text)}
.wc-eg-cn{color:var(--text-sub);font-size:12px}
.wc-acts{display:flex;gap:8px;margin-top:9px;padding-top:8px;border-top:1px solid var(--border-soft)}
.wc-wb[disabled]{opacity:.5;cursor:not-allowed}
.wc-degraded{background:var(--bg-soft);border-style:dashed}
.wc-empty{font-size:12px;color:var(--danger);background:var(--danger-soft);
  border-radius:8px;padding:5px 8px}
@media (max-width:900px){           /* ★ 断点 900px：手机上卡片占满宽、动作条换行 */
  .wc{padding:9px 10px}
  .wc-acts{flex-wrap:wrap}
}
.src-n{flex-shrink:0;min-width:15px;height:15px;line-height:15px;text-align:center;font-size:10px;
  border-radius:4px;background:var(--accent-soft);color:var(--accent)}
.src-t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* v4.51.9：用户给的网址没读到 —— 必须让用户**看见**（别让一篇长文冒充"我读过了"）。
   用 --danger 而不是新开变量：它**浅色/深色两套都定义了**（#ef4444 / #f87171），
   新变量只会定义在 :root，深色主题下会变成一个看不清的颜色。 */
.read-failed{border-top-color:var(--danger)}
.read-failed .src-head{color:var(--danger);font-weight:600}
/* v4.51.12：网页读到了、但只读了前 2500 字 —— 这不是失败，是**必须说出来的事实**。
   所以用 --text-sub 而不是 --danger：页面确实读到了，报成红色错误等于喊狼来了，
   会把上面那条真告警一起拖失效（v4.51.10 的同一条教训）。
   white-space:pre-line 是必须的 —— 文案里的换行要靠它才显示得出来。 */
.read-truncated .src-note{font-size:12px;color:var(--text-sub);line-height:1.6;
  word-break:break-all;white-space:pre-line}
.src-dead{font-size:12px;color:var(--text-sub);word-break:break-all;
  padding:2px 5px;line-height:1.45}
.src-why{font-size:11px;color:var(--text-faint);margin-top:5px;line-height:1.5}
.src-eng{flex-shrink:0;font-size:10px;color:var(--text-faint);background:var(--bg-soft);
  border:1px solid var(--border);border-radius:4px;padding:0 5px;line-height:15px;white-space:nowrap}
.mm-chan-key{flex-shrink:0;font-size:11px;color:var(--text-faint);font-family:ui-monospace,SFMono-Regular,Menlo,'Microsoft YaHei';
  padding:1px 5px;border:1px solid var(--border);border-radius:5px;background:var(--bg-input)}
.status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--text-faint)}
.status-dot.ok{background:#22c55e;box-shadow:0 0 0 3px rgba(34,197,94,.16)}
.status-dot.bad{background:var(--danger);box-shadow:0 0 0 3px rgba(239,68,68,.16)}
.status-dot.checking{background:#f59e0b;animation:dotPulse 1s ease-in-out infinite}
@keyframes dotPulse{0%,100%{opacity:1}50%{opacity:.3}}
.recheck-btn{
  display:inline-flex;align-items:center;gap:5px;padding:6px 11px;font-size:12px;
  border:1px solid var(--border);border-radius:999px;background:transparent;
  color:var(--text-sub);cursor:pointer;transition:border-color .15s,color .15s;
}
.recheck-btn:hover{border-color:var(--accent);color:var(--accent)}
.recheck-btn[disabled]{opacity:.5;cursor:default}
.model-menu{
  display:none;position:absolute;left:0;bottom:calc(100% + 8px);
  width:min(440px,calc(100vw - 32px));max-height:min(430px,58vh);overflow-y:auto;
  background:var(--bg-soft);border:1px solid var(--border);border-radius:14px;
  box-shadow:var(--shadow);z-index:60;padding:6px;
}
.model-menu.open{display:block}
.mm-head{
  display:flex;align-items:center;gap:8px;padding:8px 10px 10px;
  font-size:12px;color:var(--text-sub);border-bottom:1px solid var(--border-soft);margin-bottom:4px;
}
.mm-head b{color:var(--text);font-size:12.5px}
.mm-head .legend{margin-left:auto;display:flex;gap:10px;font-size:11px}
.mm-head .legend span{display:inline-flex;align-items:center;gap:4px}
.mm-chan{display:flex;align-items:center;gap:8px;padding:9px 10px 5px;font-size:12.5px;font-weight:600;color:var(--text)}
.mm-chan .status-dot{margin-right:1px}
.mm-chan-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mm-chan-msg{margin-left:auto;font-size:11px;font-weight:400;color:var(--text-faint);flex-shrink:0;max-width:44%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mm-chan-msg.bad{color:var(--danger)}
.mm-chan-msg.ok{color:#22c55e}
.mm-chan-click{cursor:pointer;border-radius:8px;margin:0 2px;transition:background .12s}
.mm-chan-click:hover{background:var(--bg-hover)}
.mm-sec-title{padding:9px 10px 3px;font-size:11.5px;font-weight:600;color:var(--text-faint);letter-spacing:.03em}
.mm-fam{padding:7px 10px 3px 30px;font-size:11px;color:var(--text-faint)}
.mm-model-chan{margin-left:6px;font-size:10.5px;color:var(--text-faint);background:var(--bg-soft);border:1px solid var(--border);border-radius:6px;padding:1px 6px;vertical-align:1px}
/* v4.40.0：模型的**能力验证状态**图标（⏳ 未验证 / ✅ 已验证 / ❌ 失败）。
   新接入的模型默认没有 verify 记录 → 显示 ⏳ —— 用户要的「新模型默认未验证」就靠它一眼可见。 */
.mm-verify{flex-shrink:0;margin-right:4px;font-size:11px;font-style:normal;line-height:1;cursor:help}
.mm-verify.untested{opacity:.45}   /* 未测：弱化，别抢视线 */
.mm-verify.fail{opacity:.85}
/* v4.41.0：**模型能力对比表**（🧪 菜单 → 模型能力对比）。
   ✅ 已验证 / ❌ 失败 / ⏳ 已声明未测 / — 未声明。窄屏可横向滚动。 */
.ct-wrap{overflow-x:auto;margin-top:10px;-webkit-overflow-scrolling:touch}
.ct-table{width:100%;border-collapse:collapse;font-size:12.5px}
.ct-table th{text-align:center;font-weight:600;color:var(--text-sub);padding:6px 4px;
  border-bottom:1px solid var(--border);font-size:11.5px;white-space:nowrap}
.ct-table th:first-child{text-align:left}
.ct-table td{padding:7px 4px;text-align:center;border-bottom:1px solid var(--border-soft)}
.ct-table td.ct-model{text-align:left;line-height:1.35;word-break:break-all}
.ct-sub{display:block;font-size:10.5px;color:var(--text-faint);margin-top:2px}
.ct-none{color:var(--text-faint);opacity:.5}
.ct-un{opacity:.6}
.ct-btn{border:1px solid var(--border);background:var(--bg-soft);color:var(--text);
  border-radius:7px;padding:4px 10px;font-size:11.5px;cursor:pointer;white-space:nowrap}
.ct-btn:hover{border-color:var(--accent);color:var(--accent)}
/* v4.42.0：**用途推荐**（说一句要做的事 → 看该用哪个模型）。
   分段展示"已验证 / 未验证 / 自费"，让"该不该信它"一目了然；
   自费那段用虚线框 —— 与"推荐"在视觉上就该有区别。 */
.rec-box{margin-bottom:14px}
.rec-row{display:flex;gap:8px}
.rec-row input{flex:1;min-width:0;padding:8px 11px;border:1px solid var(--border);
  border-radius:8px;font-size:13px;background:var(--bg-input);color:var(--text)}
.rec-result{margin-top:10px}
.rec-loading{font-size:12.5px;color:var(--text-faint);padding:6px 2px}
.rec-cap{font-size:12.5px;color:var(--text-sub);margin-bottom:8px}
.rec-seg{margin-bottom:9px}
.rec-seg-t{font-size:11.5px;font-weight:600;color:var(--text-sub);margin-bottom:4px}
.rec-item{display:flex;align-items:baseline;gap:8px;padding:5px 8px;border-radius:7px;
  background:var(--bg-soft);margin-bottom:4px;font-size:12.5px;flex-wrap:wrap}
.rec-m{font-weight:600;color:var(--text)}
.rec-p{font-size:11px;color:var(--text-faint)}
.rec-w{font-size:11px;color:var(--text-faint);margin-left:auto}
.rec-note{font-size:12px;color:var(--text-sub);line-height:1.7;padding:6px 8px;
  background:var(--bg-soft);border-radius:7px}
.rec-seg.paid .rec-item{background:transparent;border:1px dashed var(--border)}
.mm-item{
  display:flex;align-items:center;gap:8px;padding:8px 10px 8px 30px;border-radius:8px;
  cursor:pointer;font-size:13px;color:var(--text);
}
.mm-item:hover{background:var(--bg-hover)}
.mm-item.active{background:var(--accent-soft);color:var(--accent);font-weight:600}
.mm-item .tick{margin-left:auto;flex-shrink:0;opacity:0}
.mm-item.active .tick{opacity:1}
.mm-empty{padding:16px 12px;text-align:center;font-size:12.5px;color:var(--text-faint)}
.dev-list{display:flex;flex-direction:column;gap:8px}
.dev-item{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px 12px;background:var(--bg-soft);border-radius:10px;border:1px solid var(--border)}
.dev-item .dev-left{min-width:0;flex:1}
.dev-item .dev-name{font-size:13px;font-weight:600;color:var(--text)}
.dev-item .dev-meta{font-size:11.5px;color:var(--text-faint);margin-top:2px;line-height:1.5;word-break:break-all}
.dev-item .tag-cur{font-size:11px;color:var(--accent);border:1px solid var(--accent);border-radius:6px;padding:1px 6px;margin-left:4px}
.dev-item .dev-note{font-size:11px;color:var(--text-faint);white-space:nowrap}
.btn-xs{padding:4px 10px;font-size:12px}

/* ===== 设置抽屉 ===== */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:50}
.drawer{
  position:fixed;top:0;right:0;bottom:0;width:480px;max-width:100vw;background:var(--bg);
  z-index:60;transform:translateX(100%);transition:transform .28s ease;display:flex;flex-direction:column;
  box-shadow:-8px 0 30px rgba(0,0,0,.18);
}
.drawer.open{transform:none}
.drawer-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border-soft)}
.drawer-head h3{font-size:17px;font-weight:700}
.drawer-body{flex:1;overflow-y:auto;padding:16px 20px 40px}
.tabs{display:flex;gap:6px;background:var(--bg-soft);border-radius:12px;padding:4px;margin-bottom:18px}
.tabs button{flex:1;padding:9px;border-radius:9px;font-size:13.5px;font-weight:600;color:var(--text-sub);min-height:42px}
.tabs button.active{background:var(--bg);color:var(--text);box-shadow:var(--shadow)}

/* 渠道卡片 */
.prov-card{border:1px solid var(--border);border-radius:14px;padding:14px 16px;margin-bottom:12px;background:var(--bg)}
.prov-row{display:flex;align-items:center;gap:10px}
.prov-dot{width:9px;height:9px;border-radius:50%;background:var(--text-faint);flex-shrink:0}
.prov-dot.on{background:#22c55e;box-shadow:0 0 6px rgba(34,197,94,.7)}
.prov-name{font-weight:700;font-size:14.5px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prov-acts{display:flex;gap:2px}
.prov-acts button{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--text-faint)}
.prov-acts button:hover{background:var(--bg-hover);color:var(--text)}
.prov-meta{font-size:12px;color:var(--text-faint);margin-top:6px;line-height:1.5;word-break:break-all}
.prov-models{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px;align-items:center}
.pm-label{font-size:11px;color:var(--text-faint);flex-shrink:0;margin-right:1px}
.model-chip{font-size:11px;line-height:1.6;padding:2px 7px;border-radius:6px;background:var(--bg-input);
  border:1px solid var(--border);color:var(--text-soft);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-family:ui-monospace,SFMono-Regular,Menlo,'Microsoft YaHei'}
.model-chip.muted{opacity:.55;font-style:italic}
/* v4.33.0：模型能力标签 —— 「模型名 识图·工具」一目了然 */
.cap-tag{font-style:normal;font-size:10px;margin-left:5px;padding:0 4px;border-radius:4px;
  background:var(--accent-soft);color:var(--accent)}
/* v4.35.0：自动同步标出的「上游不认这个模型」—— 删除线 + ⚠，但**不删**、仍可选中 */
.model-chip.retired{text-decoration:line-through;text-decoration-color:var(--danger);
  border-color:var(--danger);opacity:.85}
.dead-tag{font-style:normal;font-size:10px;margin-left:5px;padding:0 4px;border-radius:4px;
  background:transparent;color:var(--danger);border:1px solid var(--danger)}
/* v4.35.0：预算可信度提示（金额是估算的 / 哪些模型没价格） */
.budget-note{font-size:12px;line-height:1.7;color:var(--text-soft);margin-top:8px;
  padding:6px 9px;border-radius:6px;background:var(--bg-input);border:1px solid var(--border)}
.budget-note b{color:var(--text)}
/* v4.36.0：企微会话的"受保护"锁标（列表里一眼看出它不可删） */
.conv-item .im-lock{font-size:10px;line-height:1;margin-left:5px;opacity:.75;flex-shrink:0}
/* v4.33.0：模型能力勾选表（编辑渠道时） */
.caps-box{margin-top:6px;max-height:260px;overflow:auto}
.caps-row{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:3px 0}
.caps-model{font-size:11px;color:var(--text-soft);min-width:120px;max-width:46%;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,'Microsoft YaHei'}
.caps-chip{display:inline-flex;align-items:center;gap:3px;font-size:11px;line-height:1.6;padding:2px 7px;
  border-radius:6px;background:var(--bg-input);border:1px solid var(--border);color:var(--text-soft);
  cursor:pointer;user-select:none}
.caps-chip input{margin:0;width:12px;height:12px;cursor:pointer}
.caps-chip.on{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}
.chip-more{font-size:11px;padding:3px 9px;border-radius:6px;background:transparent;border:1px dashed var(--border);
  color:var(--accent);cursor:pointer;min-height:auto;width:auto}
.chip-more:hover{background:var(--accent-soft);border-style:solid}
.edit-form{margin-top:12px;padding-top:12px;border-top:1px dashed var(--border)}
.edit-form .field{margin-bottom:12px}
.edit-form .field input,.edit-form .field textarea{font-size:16px}
.models-editor textarea{min-height:72px;font-size:16px;font-family:ui-monospace,'Microsoft YaHei';line-height:1.6}
.hint{font-size:12px;color:var(--text-faint);line-height:1.6;margin-top:4px}
.form-actions{display:flex;gap:8px;margin-top:6px;flex-wrap:wrap}
.form-actions .btn{padding:10px 16px;min-height:42px;font-size:13.5px}
.link-btn{color:var(--accent);font-weight:600;font-size:13px;padding:8px 4px}
.sec-title{font-size:13px;font-weight:700;color:var(--text-sub);margin:20px 0 10px;text-transform:uppercase;letter-spacing:.5px}
.set-card{border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px}
.set-card h4{font-size:14.5px;margin-bottom:10px}
.set-card textarea{width:100%;min-height:110px;padding:12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-input);outline:none;font-size:16px;resize:vertical}
.set-card textarea:focus{border-color:var(--accent)}
/* 长期记忆 */
.mem-add-row{display:flex;gap:8px;margin-bottom:10px}
.mem-add-row input{flex:1;min-width:0;font-size:16px;padding:10px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-input);color:var(--text);outline:none}
.mem-add-row input:focus{border-color:var(--accent)}
.mem-add-row .btn{min-height:44px;white-space:nowrap}
.mem-list{display:flex;flex-direction:column;gap:6px}
.mem-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-soft)}
.mem-item .t{flex:1;min-width:0;font-size:13px;line-height:1.5;color:var(--text);word-break:break-all}
.mem-item .del{width:26px;height:26px;border-radius:7px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.mem-item .del:hover{color:var(--danger);background:var(--danger-soft)}
.mem-empty{font-size:12px;color:var(--text-faint);padding:6px 2px}
/* 记忆库增强：标签 / 搜索 / 置顶 / 启停 / 提炼 */
.mem-head{display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap}
.mem-hint-txt{flex:1;font-size:12px;color:var(--text-faint);line-height:1.6;min-width:200px}
.mem-extract-btn{display:inline-flex;align-items:center;gap:5px;padding:8px 12px;font-size:12.5px;
  border:1px dashed var(--accent);border-radius:9px;background:transparent;color:var(--accent);
  cursor:pointer;min-height:36px;transition:all .15s;white-space:nowrap}
.mem-extract-btn:hover{background:var(--accent-soft);border-style:solid}
.mem-extract-btn[disabled]{opacity:.5;cursor:default}
.mem-add-row select{flex-shrink:0;font-size:14px;padding:9px 10px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-input);color:var(--text);outline:none;min-height:44px;cursor:pointer}
.mem-add-row select:focus{border-color:var(--accent)}
.mem-filter-row{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
#mem-search{flex:1;min-width:140px;font-size:14px;padding:8px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-input);color:var(--text);outline:none;min-height:36px}
#mem-search:focus{border-color:var(--accent)}
#mem-search::placeholder{color:var(--text-faint)}
.mem-tags{display:flex;gap:6px;flex-wrap:wrap}
.mem-tag-chip{font-size:12px;padding:4px 11px;border-radius:999px;border:1px solid var(--border);
  background:var(--bg-soft);color:var(--text-sub);cursor:pointer;transition:all .15s;min-height:28px;line-height:1.4}
.mem-tag-chip:hover{border-color:var(--accent);color:var(--accent)}
.mem-tag-chip.on{background:var(--accent);border-color:var(--accent);color:#fff}
.mem-item{display:flex;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-soft);flex-wrap:wrap}
.mem-item .t{flex:1;min-width:0;font-size:13px;line-height:1.5;color:var(--text);word-break:break-all}
.mem-item.off{opacity:.62}
.mem-item.off .t{text-decoration:line-through}
.mem-item .ic{width:26px;height:26px;border-radius:7px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:13px;flex-shrink:0;display:flex;align-items:center;justify-content:center;line-height:1;padding:0}
.mem-item .ic:hover{background:var(--bg-hover);color:var(--text)}
.mem-item .ic.on{color:var(--accent)}
.mem-tag{font-style:normal;font-size:10.5px;line-height:1.6;padding:1px 7px;border-radius:6px;flex-shrink:0;font-weight:700}
.mem-tag.personal{color:#1d4ed8;background:rgba(96,165,250,.14);border:1px solid rgba(96,165,250,.38)}
.mem-tag.project{color:#7c3aed;background:rgba(139,92,246,.15);border:1px solid rgba(139,92,246,.42)}
.mem-tag.pref{color:#b45309;background:rgba(245,158,11,.16);border:1px solid rgba(245,158,11,.42)}
.mem-tag.work{color:#15803d;background:rgba(34,197,94,.14);border:1px solid rgba(34,197,94,.38)}
.mem-tag.other{color:#6b6b6e;background:rgba(128,128,128,.12);border:1px solid rgba(128,128,128,.3)}
.mem-src{font-size:10px;color:var(--text-faint);border:1px solid var(--border);border-radius:5px;
  padding:0 5px;line-height:16px;flex-shrink:0;white-space:nowrap}
.mem-src.auto{color:#7c3aed;border-color:rgba(139,92,246,.42);background:rgba(139,92,246,.12)}
.mem-mt{font-size:11px;color:var(--text-faint);flex-shrink:0}
.mem-foot{font-size:12px;color:var(--text-faint);margin-top:10px;line-height:1.6}
/* ===== Memory Manager 记忆管理器面板 ===== */
.mem-mask{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:85;display:none}
.mem-mask.open{display:block}
.mem-manager{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:86;
  width:min(860px,94vw);max-height:84vh;display:none;flex-direction:column;
  background:var(--bg);border:1px solid var(--border);border-radius:16px;
  box-shadow:0 24px 60px rgba(0,0,0,.25)}
.mem-manager.open{display:flex}
.mm-head{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border)}
.mm-head h3{margin:0;font-size:16px}
.mm-stats{margin-left:4px;font-size:12px;color:var(--text-faint);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mm-autolearn{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--text-sub);
  cursor:pointer;padding:6px 10px;border:1px solid var(--border);border-radius:999px;flex-shrink:0;user-select:none}
.mm-autolearn input{accent-color:var(--accent);cursor:pointer}
.mm-autolearn:has(input:checked){border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.mm-tabs{display:flex;gap:6px;padding:10px 18px 0;border-bottom:1px solid var(--border-soft)}
.mm-tabs button{padding:8px 16px;border-radius:9px 9px 0 0;font-size:13.5px;font-weight:600;
  color:var(--text-sub);border:1px solid transparent;border-bottom:none;min-height:38px}
.mm-tabs button.active{background:var(--bg-soft);color:var(--text);border-color:var(--border)}
.mm-tabs button:hover:not(.active){color:var(--text)}
.mm-toolbar{display:flex;gap:8px;align-items:center;padding:12px 18px 6px;flex-wrap:wrap}
.mem-search-box{position:relative;flex:1;min-width:160px}
.mem-search-box svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:13px;height:13px;color:var(--text-faint);pointer-events:none}
#mem-search{width:100%;padding:8px 12px 8px 30px;font-size:13.5px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-input);color:var(--text);outline:none;min-height:36px}
#mem-search:focus{border-color:var(--accent)}
.mm-acts{display:flex;gap:6px;flex-wrap:wrap}
.mm-act-btn{display:inline-flex;align-items:center;gap:4px;padding:7px 12px;font-size:12.5px;
  border:1px solid var(--border);border-radius:9px;background:var(--bg-soft);color:var(--text-sub);
  cursor:pointer;min-height:36px;transition:all .15s;white-space:nowrap}
.mm-act-btn:hover{border-color:var(--accent);color:var(--accent)}
.mm-act-btn.primary{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.mm-act-btn.primary:hover{background:var(--accent);color:#fff}
.mm-act-btn[disabled]{opacity:.5;cursor:default}
.mem-manager .mem-add-row{padding:10px 18px 2px}
.mem-manager .mem-add-row input{flex:1}
.mm-list{flex:1;overflow-y:auto;padding:10px 18px 8px;display:flex;flex-direction:column;gap:6px;min-height:180px}
.mm-list .mem-empty{text-align:center;padding:26px 10px;font-size:13px}
.mm-foot{display:flex;align-items:center;gap:10px;padding:10px 18px 14px;border-top:1px solid var(--border-soft)}
.mm-foot span{flex:1;font-size:12px;color:var(--text-faint)}
.mm-danger{font-size:12px;color:var(--danger);border:1px solid rgba(239,68,68,.4);border-radius:8px;
  padding:6px 12px;background:transparent;cursor:pointer;min-height:32px}
.mm-danger:hover{background:var(--danger-soft)}
.mem-short .mem-head{background:var(--bg-soft);border:1px solid var(--border);border-radius:10px;padding:10px 12px;margin-bottom:8px}
.mem-conv-badge{font-size:10px;color:var(--text-faint);border:1px solid var(--border);border-radius:5px;
  padding:0 5px;line-height:16px;flex-shrink:0;white-space:nowrap;max-width:160px;overflow:hidden;text-overflow:ellipsis}
/* 四层记忆：用户画像 / 项目记忆 / 压缩摘要 */
.mm-sec-label{font-size:11.5px;font-weight:700;color:var(--text-faint);letter-spacing:.4px;
  margin:12px 0 6px;display:flex;align-items:center;gap:6px}
.prof-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}
.prof-row input{flex:1;min-width:0;font-size:13.5px;padding:8px 11px;border:1px solid var(--border);
  border-radius:9px;background:var(--bg-input);color:var(--text);outline:none;min-height:38px}
.prof-row input:focus{border-color:var(--accent)}
.prof-row input.prof-key{flex:0 0 30%;max-width:180px;font-weight:600}
.prof-row .ic{width:30px;height:30px;border-radius:8px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:15px;flex-shrink:0;display:flex;align-items:center;justify-content:center;line-height:1;padding:0}
.prof-row .ic:hover{background:var(--bg-hover);color:var(--danger)}
.proj-card{border:1px solid var(--border);border-radius:12px;background:var(--bg-soft);padding:12px 14px;margin-bottom:10px}
.proj-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.proj-name{font-size:14px;font-weight:700;flex:1;min-width:120px;color:var(--text);cursor:pointer}
.proj-name:hover{color:var(--accent)}
.proj-desc{font-size:11.5px;color:var(--text-faint);line-height:1.5;margin-top:2px;flex-basis:100%;word-break:break-all}
.proj-items{margin-top:8px;display:flex;flex-direction:column;gap:4px}
.proj-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:8px;font-size:13px;color:var(--text-sub)}
.proj-item:hover{background:var(--bg-hover)}
.proj-item .t{flex:1;min-width:0;word-break:break-all}
.proj-item .x{width:22px;height:22px;border-radius:6px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.proj-item .x:hover{color:var(--danger);background:var(--danger-soft)}
.proj-add{display:flex;gap:6px;margin-top:8px}
.proj-add input{flex:1;min-width:0;font-size:13px;padding:7px 10px;border:1px dashed var(--border);
  border-radius:8px;background:var(--bg-input);color:var(--text);outline:none;min-height:34px}
.proj-add input:focus{border-color:var(--accent);border-style:solid}
.proj-add button{flex-shrink:0;min-height:34px}
.sum-item{border-left:3px solid var(--accent);background:var(--accent-soft);border-radius:0 10px 10px 0;
  padding:8px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px;font-size:13px;flex-wrap:wrap}
.sum-item .t{flex:1;min-width:0;word-break:break-all;color:var(--text)}
/* 移动端记忆条目：文本占满整行，标签/按钮折到下方，避免逐字换行 */
@media (max-width:600px){
  .mem-item .t,.sum-item .t{flex-basis:100%;margin-bottom:2px}
  .mem-item,.sum-item{gap:6px}
  .prof-row{flex-direction:column;align-items:stretch}
  .prof-row input.prof-key{flex:1;max-width:none}
  .prof-row .ic{align-self:flex-end}
}
.sum-item .ic{width:24px;height:24px;border-radius:6px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;padding:0}
.sum-item .ic:hover{background:var(--bg-hover);color:var(--text)}
.mm-note{font-size:11.5px;color:var(--text-faint);line-height:1.6;padding:8px 12px;background:var(--bg-soft);
  border:1px dashed var(--border);border-radius:10px;margin-bottom:8px}
/* ===== 知识库面板（RAG） ===== */
.kb-mask{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:85;display:none}
.kb-mask.open{display:block}
.kb-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:86;
  width:min(760px,94vw);max-height:84vh;display:none;flex-direction:column;
  background:var(--bg);border:1px solid var(--border);border-radius:16px;
  box-shadow:0 24px 60px rgba(0,0,0,.25)}
.kb-panel.open{display:flex}
.kb-head{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border)}
.kb-head h3{margin:0;font-size:16px}
.kb-toggle{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--text-sub);
  cursor:pointer;padding:6px 10px;border:1px solid var(--border);border-radius:999px;flex-shrink:0;user-select:none}
.kb-toggle input{accent-color:var(--accent);cursor:pointer}
.kb-toggle:has(input:checked){border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.kb-stats{margin-left:auto;font-size:12px;color:var(--text-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kb-body{flex:1;overflow-y:auto;padding:14px 18px 20px}
.kb-upload{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}
.kb-up-btn{display:inline-flex;align-items:center;gap:8px;justify-content:center;padding:13px 16px;
  font-size:14px;border:2px dashed var(--border);border-radius:12px;background:var(--bg-soft);
  color:var(--text-sub);cursor:pointer;transition:border-color .15s,color .15s;min-height:52px}
.kb-up-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.kb-hint{font-size:11.5px;color:var(--text-faint);text-align:center;line-height:1.6}
.upload-progress{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:220;
  background:var(--bg-input);border:1px solid var(--border);border-radius:14px;padding:14px 20px;
  box-shadow:var(--shadow);min-width:280px;max-width:calc(100% - 40px)}
.upload-progress .up-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.upload-progress .up-bar{width:100%;height:6px;background:var(--bg-soft);border-radius:3px;overflow:hidden}
.upload-progress .up-fill{height:100%;background:var(--accent);border-radius:3px;
  transition:width .2s ease;width:0%}
.upload-progress .up-info{display:flex;justify-content:space-between;align-items:center;
  margin-top:6px;font-size:11.5px;color:var(--text-faint)}
.upload-progress .up-pct{font-weight:600;color:var(--accent)}
.kb-search{display:flex;gap:8px;align-items:center;margin-bottom:10px}
.kb-search svg{flex-shrink:0;color:var(--text-faint)}
#kb-query{flex:1;min-width:0;font-size:13.5px;padding:9px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--bg-input);color:var(--text);outline:none;min-height:38px}
#kb-query:focus{border-color:var(--accent)}
.kb-result{margin-bottom:10px;display:flex;flex-direction:column;gap:6px}
.kb-hit{border:1px solid var(--border);border-radius:10px;background:var(--bg-soft);padding:9px 12px}
.kb-hit .h-name{font-size:12px;font-weight:700;color:var(--accent);margin-bottom:4px;display:flex;align-items:center;gap:6px}
.kb-hit .h-text{font-size:12.5px;color:var(--text-sub);line-height:1.6;max-height:96px;overflow:hidden;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical}
/* ★★★ v4.59.91 修（「知识库红字和白字重叠」的**根因之一**）：
   这一条是**手机端之前的默认（电脑端）规则**，用的是 `display:flex` + `gap:10px`。
   而手机端（≤768px）后来把 `.kb-file` 改成了 `display:grid` 并**写死 5 列**——
   ★ 但 **`gap:10px` 这个简写仍然生效**（grid 也认 `gap`）⇒ 与手机端的
     `column-gap:6px` 打架（两轴都被 10px 覆盖）⇒ 元素挤/错位。
   ⇒ 改成**只声明 flex 时代的行内间距用不上**的写法：把 `gap` 拆成两轴、
     并让手机端的 `!important` 能干净地覆盖它。
   ⚠️ **电脑端视觉不变**（原来就是 10px，现在仍是 10px）。 */
.kb-file{display:flex;align-items:center;column-gap:10px;row-gap:0;padding:10px 12px;border:1px solid var(--border);
  border-radius:11px;background:var(--bg-soft);margin-bottom:8px}
.kb-file .f-ic{width:34px;height:34px;border-radius:9px;background:var(--accent-soft);color:var(--accent);
  display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;flex-shrink:0}
.kb-file .f-name{flex:1;min-width:0;font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.kb-file .f-meta{font-size:11px;color:var(--text-faint);flex-shrink:0;white-space:nowrap}
/* v4.46.10：「疑似重复」角标（按内容指纹判定，与文件名无关） */
.kb-file .f-dup{font-size:10px;font-weight:700;padding:2px 6px;border-radius:6px;flex-shrink:0;
  background:var(--danger-soft);color:var(--danger);white-space:nowrap;cursor:help}
.kb-file .del{width:26px;height:26px;border-radius:7px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;font-size:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.kb-file .del:hover{color:var(--danger);background:var(--danger-soft)}
.kb-empty{text-align:center;color:var(--text-faint);font-size:13px;padding:26px 10px;line-height:1.7}
.kb-loading{text-align:center;color:var(--text-faint);font-size:13px;padding:20px 10px}
.kb-up-row{display:flex;gap:8px;align-items:stretch}
.kb-up-row .kb-up-btn{flex:1}
.kb-folder-select{font-size:12.5px;padding:0 8px;border:1px solid var(--border);border-radius:10px;
  background:var(--bg-input);color:var(--text);outline:none;min-width:90px;max-width:130px;cursor:pointer}
.kb-folder-select:focus{border-color:var(--accent)}
.kb-folder-new{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:42px;
  border:1px solid var(--border);border-radius:10px;background:var(--bg-soft);color:var(--text-sub);cursor:pointer;transition:.15s}
.kb-folder-new:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.kb-folder-group{margin-bottom:10px}
.kb-folder-header{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;
  background:var(--bg-soft);cursor:pointer;user-select:none;margin-bottom:6px}
.kb-folder-header:hover{background:var(--accent-soft)}
.kb-folder-header .fh-icon{flex-shrink:0;color:var(--accent);display:flex;align-items:center;transition:transform .15s}
.kb-folder-header.collapsed .fh-icon{transform:rotate(-90deg)}
.kb-folder-header .fh-name{flex:1;font-size:13px;font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.kb-folder-header .fh-count{font-size:11px;color:var(--text-faint);flex-shrink:0}
.kb-folder-header .fh-actions{display:flex;gap:2px;flex-shrink:0}
.kb-folder-header .fh-actions button{width:24px;height:24px;border-radius:6px;border:0;background:transparent;
  color:var(--text-faint);cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.6;transition:.15s}
.kb-folder-header .fh-actions button:hover{opacity:1;color:var(--accent)}
.kb-folder-header .fh-actions .fh-del:hover{color:var(--danger)}
.kb-folder-group .kb-file{margin-left:20px}
.kb-folder-group.collapsed .kb-folder-body{display:none}
.kb-file .f-move{width:26px;height:26px;border-radius:7px;border:0;background:transparent;color:var(--text-faint);
  cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;opacity:.5;transition:.15s}
.kb-file .f-move:hover{opacity:1;color:var(--accent)}

/* ══════════ v4.46.4：窄屏下知识库文件**改成两排** ══════════════════════════
   用户原话：「手机屏幕上显示的知识库上传文件都看不到名字，只能看到后面的日期
   和一些按钮。能不能做成两排显示，上面是名字，底下是日期和按钮？」

   根因不是"名字太长"，而是**这一行里唯一可压缩的只有名字**：
     · `.f-ic`      34px  flex-shrink:0
     · `.f-name`    flex:1, **min-width:0**   ← 只有它会被压
     · `.f-meta`    flex-shrink:0（"N 段 · NK 字 · 日期"，约 150px）
     · 三个按钮     各 26px，flex-shrink:0
   固定部分合计约 310px + 内边距，在 360px 的手机上留给名字的只有二十几像素
   ⇒ 省略号吃掉一切，看着就像"名字根本不存在"。

   修法：窄屏改用 **grid**，第一行「图标 + 名字」，第二行「段数/日期 + 三个按钮」。
   为什么用 grid 而不是继续 flex：要的是"名字独占第一行、其余整体落到第二行"，
   这在 flex 里得靠 order + flex-basis 硬凑，还容易和按钮顺序打架；
   grid 能把"谁在哪一行哪一列"直接写出来。 */
@media (max-width:640px){
  .kb-file{
    display:grid;
    grid-template-columns:30px minmax(0,1fr) 26px 26px 26px;
    align-items:center;
    column-gap:6px;
    row-gap:3px;
  }
  .kb-file .f-ic{grid-area:1/1/2/2;width:30px;height:30px;font-size:10px}
  .kb-file .f-name{
    grid-area:1/2/2/6;                 /* 第一行，横跨到最右 */
    white-space:normal;                /* 允许换行，不再只靠省略号 */
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
    overflow:hidden;line-height:1.35;
  }
  .kb-file .f-meta{grid-area:2/2/3/3;font-size:11px;min-width:0;
                   overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* 三个按钮落到第二行右侧（DOM 里它们是 1、2、3 个 button，故用 nth-of-type） */
  .kb-file .f-move:nth-of-type(1){grid-area:2/3/3/4}
  .kb-file .f-move:nth-of-type(2){grid-area:2/4/3/5}
  .kb-file .del{grid-area:2/5/3/6}
  /* 文件夹缩进在窄屏是纯浪费（20px 换不来可读性），减半 */
  .kb-folder-group .kb-file{margin-left:10px}
}
/* 任务中心 */
.task-mask{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:90;display:none}
.task-mask.open{display:block}
.task-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:91;width:min(680px,94vw);
  max-height:86vh;display:none;flex-direction:column;background:var(--bg-panel);border:1px solid var(--border);
  border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.25)}
.task-panel.open{display:flex}
.task-head{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border)}
.task-head h3{margin:0;font-size:16px}
.task-head .icon-btn{margin-left:auto}
.task-body{padding:14px 18px;overflow-y:auto}
.task-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.task-item{border:1px solid var(--border);border-radius:12px;padding:12px;background:var(--bg);cursor:pointer;
  text-align:left;transition:border-color .15s,background .15s;min-height:74px}
.task-item:hover{border-color:var(--accent);background:var(--bg-hover)}
.task-item b{display:flex;align-items:center;gap:6px;font-size:13.5px;margin-bottom:4px}
.task-item span{font-size:11.5px;color:var(--text-sub);line-height:1.5}
.task-item .t-dot{width:7px;height:7px;border-radius:50%;background:var(--accent);flex-shrink:0}
.task-out{margin-top:14px;border:1px solid var(--border);border-radius:12px;overflow:hidden}
.task-out-head{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--bg-soft);
  border-bottom:1px solid var(--border);font-size:13px}
.task-out-head b{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#task-out-pre{margin:0;padding:12px;font-size:12px;line-height:1.6;max-height:300px;overflow:auto;
  white-space:pre-wrap;word-break:break-all;color:var(--text);font-family:ui-monospace,Menlo,Consolas,'Microsoft YaHei'}
.task-out .form-actions{padding:10px 12px;border-top:1px solid var(--border)}
.task-ai{margin-top:10px}
/* 执行确认弹窗 */
.exec-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:95;display:none}
.exec-mask.open{display:block}
/* v4.55.9：这个弹窗以前**既没有高度上限、也没有滚动** ——
   它是「固定居中」的，所以内容一旦比视口高，就**上下两头同时被切掉**，
   而且**怎么都滚不动**（弹窗自己 fixed，背后页面滚了也不顶用）
   ⇒ 底部的「添加提醒」和列表最后一行永远够不到。
   用户原话：「我现在已经整个界面都被占满了，下面我要再添加新的，
             下面我就看不到了，拉不下去了」。
   真 Chrome 实测（`_test_v4559_fixes.py` 第 ① 节，修复前）：
     · 桌面 1280x800（视口高 649）：弹窗高 **845** ⇒ top=-98 / bottom=747
     · 手机档（视口高 489）：弹窗高 **864** ⇒ top=-187 / bottom=676，
       列表最后一行 bottom=**867**（在视口外，够不到）
   修法：给弹窗自己一个高度上限 + 内部滚动。 */
.exec-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:96;width:min(560px,92vw);
  max-height:calc(100vh - 28px);
  max-height:calc(100dvh - 28px);   /* 移动端用 dvh：不含地址栏，免得被浏览器工具栏再切一次 */
  overflow-y:auto;
  overscroll-behavior:contain;      /* 滚到底不再带动背后那一页 */
  -webkit-overflow-scrolling:touch;
  background:var(--bg-panel);border:1px solid var(--border);border-radius:16px;
  padding:20px;padding-bottom:calc(20px + env(safe-area-inset-bottom));
  box-shadow:0 24px 60px rgba(0,0,0,.3);display:none}
.exec-modal.open{display:block}
.exec-modal h4{margin:0 0 10px;font-size:16px}
.exec-modal pre{margin:0 0 14px;padding:12px;border:1px solid var(--border);border-radius:10px;
  background:var(--bg-soft);font-size:12.5px;line-height:1.6;white-space:pre-wrap;word-break:break-all;
  max-height:220px;overflow:auto;color:var(--text)}
/* v4.55.9：弹窗能滚之后，「添加提醒」会被下面那条列表顶到折叠线以下 ——
   实测手机档**刚打开面板时它就在视口外**（bottom 落在 475 之下），
   于是用户又会说一句"下面我看不到"。把它钉在弹窗底边：打开就看得见主操作。
   ⚠️ 只钉提醒这个弹窗（直接子选择器），别的弹窗的按钮行一字不动。
   ⚠️ 必须给底色，否则滚动时下面的列表会从字缝里透出来。 */
#rem-modal > .form-actions{position:sticky;bottom:0;z-index:2;
  background:var(--bg-panel);padding-top:10px}
/* VPS 状态概览 */
.vps-status{border:1px solid var(--border);border-radius:12px;padding:12px;background:var(--bg)}
.vs-head{display:flex;align-items:center;gap:8px;font-size:13.5px;margin-bottom:8px}
.vs-head b{font-weight:700}
.vs-refresh{width:26px;height:26px;border-radius:7px;border:1px solid var(--border);background:transparent;
  color:var(--text-sub);cursor:pointer;font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center}
.vs-refresh:hover{color:var(--accent);border-color:var(--accent)}
.vs-ts{margin-left:auto;font-size:11px;color:var(--text-faint)}
.vs-row{display:flex;align-items:center;gap:8px;font-size:12.5px;padding:3px 0}
.vs-label{color:var(--text-sub);flex-shrink:0;width:56px}
.vs-val{color:var(--text);word-break:break-all;line-height:1.5}
.vs-bar-row{display:flex;align-items:center;gap:8px;font-size:12.5px;padding:3px 0}
.vs-bar{flex:1;min-width:60px;height:8px;border-radius:99px;background:var(--bg-hover);overflow:hidden}
.vs-fill{height:100%;border-radius:99px;transition:width .4s}
.vs-loading{font-size:12.5px;color:var(--text-faint);padding:6px 0}
.danger-zone button{width:100%;margin-top:8px}

/* ===== Toast ===== */
#toasts{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:200;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none;width:calc(100% - 40px);max-width:420px}
.toast{
  background:var(--text);color:var(--bg);padding:11px 20px;border-radius:12px;font-size:13.5px;font-weight:500;
  box-shadow:var(--shadow);animation:toastIn .25s ease;max-width:100%;display:flex;align-items:center;gap:8px;
}
.toast.err{background:var(--danger);color:#fff}
.toast.ok{background:#16a34a;color:#fff}
@keyframes toastIn{from{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:none}}

.empty-list{text-align:center;color:var(--text-faint);font-size:13px;padding:30px 10px}

/* ===== 增强：会话搜索 / 重命名 / 编辑重发 / 折叠 / 拖拽上传 ===== */
.conv-search-wrap{position:relative;padding:2px 10px 8px}
.conv-search-wrap>svg{position:absolute;left:20px;top:calc(50% + 2px);transform:translateY(-50%);width:14px;height:14px;color:var(--text-faint);pointer-events:none}
#conv-search{width:100%;padding:9px 30px 9px 32px;border:1px solid var(--border);border-radius:10px;background:var(--bg-input);font-size:13px;outline:none;color:var(--text);min-height:38px;transition:border-color .15s}
#conv-search:focus{border-color:var(--accent)}
#conv-search::placeholder{color:var(--text-faint)}
.conv-search-clear{position:absolute;right:16px;top:calc(50% + 2px);transform:translateY(-50%);width:20px;height:20px;border-radius:50%;background:var(--bg-hover);color:var(--text-faint);font-size:12px;line-height:1;display:none;align-items:center;justify-content:center;padding:0}
.conv-search-wrap.has-val .conv-search-clear{display:flex}
.conv-search-clear:hover{color:var(--text)}
.conv-item .rn{opacity:0;width:28px;height:28px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--text-faint)}
.conv-item:hover .rn{opacity:1}
.conv-item .rn:hover{color:var(--accent);background:rgba(16,163,127,.12)}
/* ===== 多选模式 ===== */
.conv-item .ms-check{width:20px;height:20px;border-radius:50%;border:2px solid var(--border);display:none;align-items:center;justify-content:center;flex-shrink:0;transition:border-color .15s}
.conv-item .ms-check::after{content:"";width:10px;height:10px;border-radius:50%;background:var(--accent);transform:scale(0);transition:transform .15s}
.conv-item.ms-selected .ms-check{border-color:var(--accent)}
.conv-item.ms-selected .ms-check::after{transform:scale(1)}
body.ms-mode .conv-item .ms-check{display:flex}
body.ms-mode .conv-item .rn,body.ms-mode .conv-item .del{display:none !important}
body.ms-mode .conv-item.active{background:transparent}
.ms-bar{position:fixed;bottom:0;left:0;right:0;background:var(--bg-card);border-top:1px solid var(--border);display:none;align-items:center;gap:6px;padding:8px 12px;z-index:100;box-shadow:0 -2px 12px rgba(0,0,0,.12);max-width:380px}
body.ms-mode .ms-bar{display:flex}
body.ms-mode .side-foot{display:none}
.ms-bar .ms-count{font-size:13px;color:var(--text-sub);flex:1;white-space:nowrap}
.ms-bar button{padding:8px 14px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;border:none;white-space:nowrap}
.ms-bar .ms-btn-all{background:var(--bg-hover);color:var(--text)}
.ms-bar .ms-btn-del{background:rgba(239,68,68,.12);color:var(--danger)}
.ms-bar .ms-btn-del:disabled{opacity:.4;cursor:not-allowed}
.ms-bar .ms-btn-exp{background:var(--accent);color:#fff}
.ms-bar .ms-btn-exp:disabled{opacity:.4;cursor:not-allowed}
.ms-bar .ms-btn-cancel{background:var(--bg-hover);color:var(--text-sub)}
.msg.editing .bubble{outline:2px dashed var(--accent);outline-offset:2px}
.composer.drag-over .composer-box{border-color:var(--accent);box-shadow:0 0 0 3px rgba(16,163,127,.18)}
.code-acts{display:flex;gap:2px;align-items:center}
.desktop-only{display:none}
@media (min-width:769px){
  .desktop-only{display:inline-flex!important}
  .sb-collapsed .sidebar{width:0;border-right:none;overflow:hidden}
  .sb-collapsed .side-top,.sb-collapsed .side-foot{display:none}
}

/* ===== 响应式 ===== */
@media (max-width:768px){
  .sidebar{position:fixed;top:0;bottom:0;left:0;transform:translateX(-100%);box-shadow:8px 0 30px rgba(0,0,0,.2)}
  .sidebar.open{transform:none}
  .overlay.show{display:block}
  .drawer{width:100vw}
  /* ★ v4.57.1：**把正文列加宽**（字号只解决一半问题，另一半是"列太窄"）。
     实测（iframe 精确视口 390×844，工具 `_probe_mobile_nav.py`）：
       **改前** `.md` 可用宽 **292px**、汉字宽 14px ⇒ **每行 20.9 个汉字**。
       盒链：390 − 10（滚动条）− 20（`.chat-inner` 左右各 10）− 44（`.msg-body` 预留头像）
             − 24（`.bubble` 左右各 12） = **292**。
     ⇒ 三处各让 4px（10→6 / 44→36 / 12→8）⇒ 可用宽 292 → **316px**、每行 **22.6 个汉字（+8%）**。
     代价只是左右各少 4px 边距；头像 28px 仍放得下（预留 36px = 28 + 8px 间隙）。 */
  .chat-inner{padding:12px 6px 24px;gap:14px}
  .composer-wrap{padding:0 8px calc(8px + env(safe-area-inset-bottom))}
  .msg{gap:8px}
  .avatar{width:28px;height:28px;border-radius:7px;font-size:11px}
  .msg-body{max-width:calc(100% - 36px)}
  .bubble{padding:10px 8px}
  /* ★ v4.51.32：这里原来是 `.md{font-size:16px}` —— **比桌面的 15px 还大**，
     配合 em 阶梯把标题一起放大（实测手机 h1 21.6px > 桌面 20.25px）。
     用户原话「手机看着特别的大」，根因就是这一行（**不是** body 没降级：两端都是 15px）。
     现在对齐参照界面（DSH）的 14px/24px：同一段 160 字中文从 9 行 237px 降到 8 行 192px。
     ★ 实测坑：Windows 版 Chrome 的最小窗口宽被钳到 500px，
       `--window-size=390` 实际量到 500 —— 这条结论是用 iframe 精确视口量出来的。 */
  .md{font-size:14px;line-height:1.71}
  .topbar .brand span{display:none}
  .welcome h2{font-size:20px}
  .drawer-body{padding:14px 14px 40px}
  .code-block-wrap pre{font-size:12px}
  .msg-actions{opacity:1}   /* 移动端无 hover，复制/重试按钮永远显示 */
  /* 输入框更紧凑：缩小发送按钮、圆角与内边距，给正文多留空间 */
  /* v3.29: 消息操作按钮也永远显示 */
  .composer-box{border-radius:18px;padding:6px 8px;gap:6px}
  #input{font-size:16px;padding:6px 4px;min-height:22px}
  .send-btn{width:40px;height:40px}
  /* v4.31.0：最下面那一行 —— 用量总数 + 模型说明，**同一行、同一字号**。
     v4.30.0 我把渠道说明在手机上隐藏了、把用量挪到了上面那排；
     用户随后说明他要的是「token 放在最最下面，和显示的大模型放在一起，字体一样」——
     他说的「大模型显示的文字」就是这行渠道说明。所以现在：说明恢复显示，
     用量回到它旁边，两者共用 --note-fs 一个变量（改一处两边一起变）。 */
  .status-line{--note-fs:12px;margin-top:5px}
  .sl-row{gap:6px}
  .ub-line{padding:2px 9px;gap:4px}
  /* ★★ v4.59.162：这里原来是 `.ub-cache{display:none}` —— **功能被自己关掉了**。
     理由当时写的是"塞不下会被裁掉，不截半截数字"。但用户主要在手机上看
     ⇒ v4.59.161 上线后他当场报「没看到单对话缓存命中」：**代码全对，手机端整段不渲染**。
     ★ 教训：「为了不裁半截而整段隐藏」在一个**主要使用端**上等于「没做这个功能」。
       要退让就退让成"更短的形式"，不能退让成"不存在"。
     实测宽度够：390px 视口下 `本对话 1,234 tokens · 缓存 61%` ≈ 220px。
     万一真到极值溢出，`.ub-body{overflow:hidden}` 会自己裁掉**尾部**缓存段
     ⇒ 退化回"看不见"，但**不会**挤压左边那个总数（`flex:0 0 auto`）。 */
  .ub-unit{font-size:11px}
  .ub-detail{font-size:12.5px;padding:8px 10px}
  .ub-rk{min-width:78px}
  /* v4.31.1：手机端**恢复显示「检测」**。
     它原来是 `.recheck-btn{display:none}`（早在 v4.27.2 之前就这样），
     理由是"低频 + 省地方"。但用户找不到它了：
       「手机上那个大模型的『检测』按钮没有了，我不知道是你设置了什么不需要了还是怎么样的」
     —— 一个按钮悄悄消失，比它占的那点地方更让人不安。
     而且当初挤的原因已经不存在：用量胶囊搬去了最下面那一行，
     现在这一排是 [模型] [检测] [联网]，实测宽度够（模型名"Auto"时约 220px / 可用 344px）。
     「复制会话」仍然隐藏 —— 它真的低频，而且手机上有"导出/打印"的替代路径。 */
  .model-btn{max-width:min(240px,58vw)}
  /* v4.31.3：手机端这一排只留「模型 / 检测 / 联网」。
     上面 v4.31.1 那句注释写着「复制会话仍然隐藏」，但它当时是被
     `.recheck-btn{display:none}` 顺带藏掉的 —— v4.31.1 删掉那条规则后，
     它跟着回来了，这一排就成了 4 个按钮；而 `.model-bar` 是 flex-wrap:wrap，
     窄屏上会**折成两行**，把用户要的正文空间又吃回去。
     现在按原意收掉它，同时**在 ⋯ 菜单里补了「复制当前会话（纯文本）」**，
     所以这是"挪个位置"，不是"按钮凭空消失"（历史上正是后者让用户以为坏了）。 */
  #copy-conv-btn{display:none}
  /* v4.29.0 曾在这里把「自动/强制/关闭」角标藏掉（理由：信息「联网」按钮上已经有了，
     一行里出现两个「自动」既占地方又容易看混）。
     v4.46.4：那个角标已经**彻底并进「联网」按钮**（二合一），所以这条规则连同
     角标本身一起去掉了 —— 桌面端同样受益（用户 2026-09-17 报的正是桌面端）。 */
  /* 模式栏一行横向滑动，不换行省高度 */
  .mode-bar{margin:6px 2px 4px;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .mode-bar::-webkit-scrollbar{display:none}
  .mode-chip{flex-shrink:0}
  /* ★★★ v4.59.164：**「报警分析」固定在最左**。
     病：它是 `.mode-bar` 里的**第 9 个**元素（6 个模式 + 分隔符 + 它），而在窄屏上
     `flex-wrap:nowrap` + 隐藏滚动条 ⇒ **它在屏幕右边外面，没有任何提示说还能往右滑**。
     用户原话：「报警分析那个功能好像没有用……好像从来没用到过实践过」——
     功能一直在，只是**在主要使用端上不可发现**。
     ★ 与 `.ub-cache`（v4.59.162）同一族：**"存在"不等于"看得见"**。
     `order:-1` 把它提到最前，横滑时第一眼就是它；`margin-right:auto` 把其余模式推到右边。 */
  .mode-bar .alarm-chip{order:-1;margin-right:auto}
}
@media (min-width:769px){ .desktop-hide{display:none!important} }
/* v3.29: 触屏设备（手机/平板/触屏笔记本）无 hover 能力，操作按钮永远可见 */
@media (hover: none){ .msg-actions{opacity:1!important} }
/* ---- 记忆提炼结果通知（对话内） ---- */
.mem-result-notice {
  display:flex; gap:10px; align-items:flex-start;
  margin:12px 8px; padding:12px 14px;
  background:var(--bg-soft); border:1px solid var(--border);
  border-radius:10px; font-size:13px;
  animation:memFadeIn .3s ease;
}
@keyframes memFadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
.mem-notice-icon { font-size:20px; line-height:1.4; }
.mem-notice-body { flex:1; min-width:0; }
.mem-notice-title { font-weight:600; margin-bottom:6px; }
.mem-notice-list { color:var(--text-gray); line-height:1.6; }
.mem-notice-list .mem-tag {
  display:inline-block; font-size:11px; padding:0 5px;
  border-radius:3px; background:var(--bg-hover); margin-right:2px;
}
.mem-notice-acts { margin-top:8px; display:flex; gap:10px; }
/* ---- 扁平化记忆列表 ---- */
.mem-item { display:flex; align-items:center; gap:8px; padding:8px 10px; border-bottom:1px solid var(--border); font-size:13px; }
.mem-item.disabled { opacity:0.5; }
.mem-item.pinned { background:var(--bg-hover); }
.mem-type-tag { display:inline-block; font-size:10px; padding:1px 6px; border-radius:3px; color:#fff; white-space:nowrap; }
.mem-type-tag.tag-profile { background:#4a90d9; }
.mem-type-tag.tag-preference { background:#7c5cbf; }
.mem-type-tag.tag-project { background:#2ea97a; }
.mem-type-tag.tag-auto { background:#d9a44a; }
.mem-text { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mem-ts { font-size:11px; color:var(--text-gray); white-space:nowrap; }
.mm-empty { text-align:center; padding:30px 16px; color:var(--text-gray); font-size:14px; }

/* ═══════════════════════ v3.43.0 追加 ═══════════════════════ */

/* iOS：输入框字号 <16px 时，聚焦会触发整页缩放。仅在触摸设备上放大，桌面保持紧凑。 */
@media (pointer: coarse){
  input, textarea, select{font-size:16px}
}

/* 顶部安全区：index.html 用了 viewport-fit=cover，但原先只处理了底部安全区 */
.topbar{padding-top:env(safe-area-inset-top)}

/* 深色模式下让原生控件（checkbox / select / 滚动条）跟随主题 */
:root{color-scheme:light}
[data-theme="dark"]{color-scheme:dark}

/* 打印 / 存 PDF：只留对话正文（手机端由系统打印面板生成 PDF，零依赖） */
@media print{
  .topbar, .sidebar, .ms-bar, .composer-wrap, .composer, .overlay,
  .alarm-mask, .task-mask, .mem-mask, .kb-mask, .exec-mask, .drawer-mask,
  .ol-fab, .ol-panel, .more-menu{display:none !important}
  html, body, #view-app, .app-body{height:auto !important; overflow:visible !important}
  .chat-scroll{overflow:visible !important; height:auto !important}
  .msg-actions, .code-copy{display:none !important}
}

/* ═══════════ v3.44.0 ═══════════ */
/* 回到顶部：fixed 定位，不依赖父级布局；滚过 600px 才出现 */
/* ⚠️ v4.46.14：bottom 不再写死 104px。
   真 Chrome 实测（`_probe_geom.py`）：平板 834 宽下箭头 (766,819,38×38) 与
   发送按钮 (741,856,44×44) **真的交叠 19×1 px**；手机 390 宽下虽未交叠也只差
   18px，输入框一长就压上去 —— 用户原话「发送按钮和置顶箭头重合在一起了」。
   根因：箭头钉在视口底边 104px，而输入区实测高 163~220px，箭头整颗**落在输入区里**。
   改成跟着输入区真实高度走：JS 把 .composer-wrap 的高度写进 --composer-h。 */
.to-top-btn{position:fixed;right:14px;bottom:calc(var(--composer-h,104px) + 14px);z-index:60;width:38px;height:38px;border-radius:50%;
  background:var(--bg-card);border:1px solid var(--border);color:var(--text);font-size:17px;line-height:1;
  box-shadow:0 2px 10px rgba(0,0,0,.16);display:none;cursor:pointer}
.to-top-btn.on{display:block}
.to-top-btn:active{transform:scale(.94)}

/* ═══════════ v3.45.1 ═══════════ */
/* 消息时间戳：只在有新消息的 ts 时出现，旧消息没有就不显示 */
/* v4.46.11：order:99 —— 时间戳排到按钮行最后（它是最先被塞进 .msg-actions 的，
   原来按钮隐身时看不出来，现在按钮常显，时间排在前面就碍眼了）。 */
.msg-time{font-size:11px;color:var(--text);opacity:.5;margin-left:8px;align-self:center;order:99;
  font-variant-numeric:tabular-nums;white-space:nowrap}
@media print{ .msg-time{opacity:.7} }

/* ═══════════════════════════════════════════════════════════════════════
   v4.31.3 — 交互反馈 / 可访问性 / 移动端打磨
   ───────────────────────────────────────────────────────────────────────
   这一段的规则**只做新增**，不改写上面任何既有选择器的行为：
   动效、焦点环、原生控件配色、手机手势与安全区。逐条都写了"为什么"。
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. 统一的按下反馈 ────────────────────────────────────────────────
   原来只有 `.send-btn` / `.to-top-btn` 写了 `:active`（它们出现得早），
   其余一排按钮按下去**毫无反应**：手机上手指点完没有任何确认感，
   于是会怀疑"我到底点上没有"而重复点。
   ⚠️ 必须把 transition 写全（transform/opacity + 原有的 border/color/background），
   只写自己那一份会把组件原有的过渡**整条顶掉**（后写的 transition 是覆盖而非追加）。 */
.btn, .icon-btn, .ol-fab, .to-top-btn,
.recheck-btn, .web-btn, .img-btn, .model-btn, .mode-chip, .send-btn, .mm-item{
  transition:transform var(--dur-1) var(--ease-std),
             opacity var(--dur-1) var(--ease-std),
             background-color var(--dur-1) var(--ease-std),
             border-color var(--dur-1) var(--ease-std),
             color var(--dur-1) var(--ease-std),
             box-shadow var(--dur-1) var(--ease-std);
}
.btn:active, .icon-btn:active, .ol-fab:active, .to-top-btn:active,
.recheck-btn:active, .web-btn:active, .img-btn:active, .model-btn:active,
.mode-chip:active, .send-btn:active{
  transform:scale(.94);          /* 与既有的 .send-btn:active / .to-top-btn:active 同款 */
}
/* 菜单项是整行，缩放在整行上很怪 —— 用背景变深代替 */
.mm-item:active{background:var(--bg-hover)}

/* ── 2. 键盘焦点可见 ──────────────────────────────────────────────────
   桌面端用 Tab 走一圈时，按钮只有浏览器默认的黑色轮廓（和主题不搭）；
   而一批输入框是 `outline:none` + 只把边框染成主题色，**太淡**。
   这里统一成主题色焦点环。
   ⚠️ 输入区（.composer-box）例外：它已有 `:focus-within` 光圈，
   再叠一层就是双环 —— 所以把里面的元素排除掉。 */
.btn:focus-visible, .icon-btn:focus-visible, .ol-fab:focus-visible,
.to-top-btn:focus-visible, .recheck-btn:focus-visible, .web-btn:focus-visible,
.model-btn:focus-visible, .mode-chip:focus-visible, .mm-item:focus-visible,
input:focus-visible:not(#input), select:focus-visible,
textarea:focus-visible:not(#input){
  outline:2px solid var(--accent);
  outline-offset:2px;
}
.composer-box :focus-visible{outline:none}   /* 容器光圈已经足够，避免双环 */

/* ── 3. 选中文字用主题色（原来是系统蓝，深色主题里很跳） ───────────── */
::selection{background:var(--accent-soft);color:var(--text)}

/* ── 4. 滚动条 ────────────────────────────────────────────────────────
   WebKit 那两条早就有了；补 `scrollbar-width/scrollbar-color`，
   让 Firefox 与新一代内核也用细滚动条，而不是默认的粗灰条。 */
*{scrollbar-width:thin;scrollbar-color:rgba(128,128,128,.45) transparent}

/* ── 5. 「减少动态效果」 ──────────────────────────────────────────────
   只关**装饰性**动效（进出场、滑动、缩放），
   **保留**信息性动画 —— `.status-dot.checking` 的呼吸点表示"正在检测"，
   `.typing-cursor` 的闪烁表示"正在输出"，关掉它们等于把状态藏起来。 */
@media (prefers-reduced-motion: reduce){
  .msg, .mem-result-notice, .toast, .drawer, .sidebar, .ol-panel, .modal,
  .msg-actions, .composer-box, .recheck-btn, .mode-chip, .img-btn, .send-btn{
    animation-duration:.001ms !important;
    transition-duration:.001ms !important;
  }
}

/* ── 6. 手机端手势与安全区 ──────────────────────────────────────────── */
@media (max-width:768px){
  /* 双击放大在手机上会把"连点两下"变成缩放；去掉它（双指缩放仍可用），
     顺手消掉 300ms 点击延迟 —— 按钮"按下去跟手"的体感来自这一条。 */
  button, [data-act], [data-act-change], .mode-chip, .mm-item, .conv-item,
  .recheck-btn, .web-btn, .model-btn{
    touch-action:manipulation;
  }
  /* iOS 横屏会把正文自动放大，同一份 CSS 在竖屏/横屏宽度对不上 —— 钉住 100% */
  html{-webkit-text-size-adjust:100%}
  /* 刘海屏横屏时左右也要留安全区，否则输入区贴边、圆角被切 */
  .composer-wrap{
    padding-left:calc(8px + env(safe-area-inset-left));
    padding-right:calc(8px + env(safe-area-inset-right));
  }
}

/* ── 7. 地址栏收起/展开时的高度 ───────────────────────────────────────
   手机浏览器上下滑动会隐藏地址栏，`100%` 不跟着变，于是底部常被切掉一截
   （输入框只露一半）。`dvh` 会跟着可视视口走；不支持的浏览器忽略这条，
   仍用上面原有的 `height:100%`。 */
@supports (height:100dvh){
  html, body{height:100dvh}
}

/* ═══════════════════════════════════════════════════════════════════════
   v4.46.14
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. 多选入口常显（原来只有 ⋯ 菜单里那一项 + 长按会话项） ─────────────
   用户原话：「手机和平板上没有这个功能，我没法针对我发的一些对话，挨个快速选中」。
   查完：功能在（`enterMultiSelect`），但两条入口一条要滚 ⋯ 菜单、一条是**没人知道的长按**
   —— 功能存在但发现不了 = 功能不存在（v4.46.11 立的判据）。
   这条按钮放在会话搜索框正下方，一开侧栏就看得见。 */
.ms-entry-btn{
  display:flex;align-items:center;justify-content:center;gap:7px;
  width:calc(100% - 20px);margin:0 10px 8px;padding:9px 12px;
  border:1px dashed var(--border);border-radius:10px;background:transparent;
  color:var(--text-sub);font-size:13px;font-weight:600;cursor:pointer;
  min-height:40px;transition:background .15s,color .15s,border-color .15s;
}
.ms-entry-btn:hover{background:var(--bg-hover);color:var(--text);border-color:var(--accent)}
.ms-entry-btn:active{transform:scale(.98)}
body.ms-mode .ms-entry-btn{display:none}   /* 已在多选态，入口自身让位给底部操作条 */

/* ── 2. 「上一轮被中断」提示条 ────────────────────────────────────────────
   用户原话：「生成对话的时候，如果切换模式（比如从手机模式换成桌面模式），
             生成就会中断，不给结果」。
   查完：这不是服务端的问题 —— 浏览器**换视图/刷新会重载页面**，进行中的 SSE 流被掐断，
   服务端日志原话：`[relay] client disconnected mid-stream — 前端未收完整轮，消息可能残缺`
   （今天 4 次，全部 content_chars=0，即只收到思考、正文一个字没到）。
   页面重载拦不住（浏览器行为），但可以让它**看得见、一键接上**，而不是悄悄留半句。 */
.interrupt-bar{
  display:flex;align-items:center;gap:8px;margin:0 0 8px;padding:9px 11px;
  border:1px solid rgba(217,119,6,.45);border-radius:10px;
  background:rgba(217,119,6,.10);color:var(--text);font-size:13px;line-height:1.45;
}
.interrupt-bar[hidden]{display:none}
.interrupt-bar .ib-text{flex:1;min-width:0}
.interrupt-bar .ib-btn{
  flex-shrink:0;padding:7px 13px;border:none;border-radius:8px;cursor:pointer;
  background:var(--accent);color:#fff;font-size:13px;font-weight:600;white-space:nowrap;
}
.interrupt-bar .ib-btn:active{transform:scale(.96)}
.interrupt-bar .ib-x{
  flex-shrink:0;width:24px;height:24px;padding:0;border:none;border-radius:50%;
  background:transparent;color:var(--text-sub);font-size:15px;line-height:1;cursor:pointer;
}
.interrupt-bar .ib-x:hover{background:var(--bg-hover);color:var(--text)}
@media (max-width:768px){
  .interrupt-bar{font-size:12.5px;padding:8px 9px;gap:6px}
  .interrupt-bar .ib-btn{padding:7px 10px;font-size:12.5px}
}

/* ==================== v4.59.14（W2）：🌐 翻译对照 ====================
   布局：**PC ≥900px 左右两栏** ／ **手机上下堆叠**（用媒体查询，不用 JS 量宽度）。
   颜色只用现有 CSS 变量（跟着主题走，不写死色值）。 */
.tr-in{width:100%;box-sizing:border-box;height:120px;resize:vertical;
  padding:8px 10px;border:1px solid var(--line,#e5e5e5);border-radius:8px;
  background:var(--bg,#fff);color:var(--fg,#222);font-size:13px;line-height:1.6}
.tr-in:focus{outline:none;border-color:var(--accent,#4a7)}
.tr-out{margin-top:8px}
.tr-row{display:flex;flex-direction:column;gap:2px;padding:8px 0;
  border-top:1px solid var(--line,#eee)}
.tr-row:first-child{border-top:none}
.tr-en{font-size:13px;line-height:1.7;opacity:.72;cursor:pointer;
  word-break:break-word;white-space:pre-wrap}
/* ★ v4.59.16：`pre-wrap` —— 段内有换行时原样显示（"整段回退"那条路要靠它保住换行，
   否则多行原文会被折叠成一行，看起来像"内容丢了"）。 */
.tr-zh{font-size:13.5px;line-height:1.8;word-break:break-word;white-space:pre-wrap}
.tr-en.tr-hide{display:none}
/* 手机：上下堆叠（默认就是 column）*/
@media (min-width:900px){
  .tr-row{flex-direction:row;gap:14px;align-items:flex-start}
  .tr-en{flex:1 1 50%;min-width:0}
  .tr-zh{flex:1 1 50%;min-width:0;border-left:1px solid var(--line,#eee);padding-left:14px}
}
@media (max-width:899px){
  .tr-in{height:80px}
}

/* ★ v4.59.38（云盘批1）：回收站 */
.dm-trash{display:flex;align-items:center;gap:6px;width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:8px;background:transparent;color:var(--text);font-size:13px;cursor:pointer;margin-top:6px}
.dm-trash:hover{border-color:var(--accent);color:var(--accent)}
.dm-trash-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;font-size:13px}
.dm-trash-item:hover{background:var(--accent-soft)}
.dm-trash-item .tname{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dm-trash-item .twhen{font-size:11px;color:var(--text-faint);flex-shrink:0}
.dm-trash-item .tbtn{flex-shrink:0;padding:3px 10px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--accent);font-size:12px;cursor:pointer}
.dm-trash-item .tbtn:hover{background:var(--accent-soft)}

/* ★ v4.59.63：积分行的「↻ 刷新」（当场重查三个号） */
.wb-refresh{margin-left:4px;padding:0 6px;border-radius:999px;font-size:11px;cursor:pointer;
  color:var(--text-faint);border:1px solid var(--border-soft,rgba(127,127,127,.25));text-decoration:none}
.wb-refresh:hover{color:var(--accent,#3b82f6);border-color:currentColor;background:var(--bg-hover)}

/* ★★★ v4.59.64（2026-10-13）：**工具调用卡片**（照 DSH 的做法。
   收起 = 一行摘要（不占地方）；点开 = 逐项明细（工具/记忆/调度/耗时/检索词/引擎）。
   ⚠️ 全部用主题变量（暗/亮自适应）；.tool-card-body 默认 display:none。*/
.tool-card{margin-top:6px;border:1px solid var(--border-soft,rgba(127,127,127,.25));
  border-radius:8px;overflow:hidden;background:var(--bg-soft,transparent);max-width:100%}
.tool-card-sum{display:flex;align-items:center;gap:6px;padding:5px 9px;cursor:pointer;
  font-size:12px;color:var(--text-faint);user-select:none}
.tool-card-sum:hover{background:var(--bg-hover)}
.tool-card-sum .tc-txt{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tool-card-sum .tc-chev{flex:0 0 auto;opacity:.7}
.tool-card-body{display:none;border-top:1px solid var(--border-soft,rgba(127,127,127,.2));
  padding:6px 9px}
.tool-card.open .tool-card-body{display:block}
.tc-row{display:flex;gap:8px;font-size:12px;line-height:1.7}
.tc-k{flex:0 0 52px;color:var(--text-faint)}
.tc-v{flex:1 1 auto;color:var(--text);word-break:break-word}
.router-tag{font-size:11px;padding:1px 6px;border-radius:999px;background:var(--bg-hover);
  color:var(--text-faint);margin-right:4px}


/* ══════════════════════════════════════════════════════════════════════════
   ★★★ v4.59.75（2026-10-14，用户："手机版…太臃肿了，是不是字体可以调小一点，
        还是说整体框架，还是说有些功能用得少就移除掉，或者说做一些折叠"）
   ⇒ 我做三档（**只加小屏规则，桌面档一个字不动** ⇒ 零回归风险）：

   【A】字号统一：原来手机档只改了 body(14px)，各处仍写死 11/12/12.5/13.5/15px ⇒ 视觉"乱"
        现在收敛成 **3 档**：11.5（辅助）/ 13（正文/菜单）/ 14（标题）
   【B】顶栏折叠：小屏藏掉 5 个按钮 —— 复制会话 / 联网 / 图片 / 文档 / 视频
        ★★ 判据（已核过，不是猜）：这 5 个的 `data-act` 在 `#more-menu` 里**都有等价入口**
          ⇒ 隐藏**不丢功能**，只是把入口从"顶栏"挪到"⋯ 菜单"
        ★ 保留：模型选择（核心）、检测（常用）
   【C】紧凑化：输入区/菜单/消息间距收紧，一屏能多看几行
   ⚠️ 真浏览器量不了（要登录）⇒ 判据用"静态契约 + 已有的手机导航套件"钉住：
      `_test_v4570_mobilenav.py`（31/0）+ 本套件新增的隐藏清单断言。
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width:520px){
  /* ── A 字号统一（3 档） ── */
  body{font-size:13px}
  .mm-item{font-size:13px}
  .mm-head b{font-size:14px}
  .mm-sec-title,.mm-note,.mm-fam{font-size:11.5px}
  .mm-cap,.mm-ctx,.mm-rate,.mm-fee,.mm-nocache{font-size:10.5px}
  .ol-item,.ol-head b{font-size:12.5px}
  .msg .bubble{font-size:14px;line-height:1.62}
  .composer-box textarea,#composer{font-size:15px}   /* 输入框保持大字号：iOS 上 <16px 会触发缩放 */
  /* ── B 顶栏折叠：4 个按钮收进 ⋯ 菜单 ──
     ★★★ v4.59.92 修（用户 2026-10-16 23:0x：「联网按钮没了？」）：
       原来这条注释写着「5 个按钮收进 ⋯ 菜单（功能都在菜单里，**已核对**）」——
       ★ **那句"已核对"是错的**：`#web-btn-toggle`（**联网**）在 ⋯ 菜单里**没有**等价入口
         （设置里那个 `#mm-web-enabled` 在**另一块区域**，而且手机端还要多翻一层）。
       ⇒ 用户（手机 ≤520px）**找不到联网开关**。
       ⇒ 已把 `#web-btn-toggle` **移出隐藏名单**（放回顶栏，位置在「检测」右侧，
         与 v4.31.3「手机端只留 模型/检测/联网」的原设计一致），
         并**删掉设置里那个重复入口**（同一个动作 `toggleWebSearch`，两处会口径不一致）。
     ⚠️ 教训：写「已核对」之前要**逐个**核 —— 当时 4 个真的在菜单里，联网那个不在。 */
  .model-bar > #copy-conv-btn,
  .model-bar > #img-btn,
  .model-bar > #doc-btn,
  .model-bar > #video-btn{display:none !important}
  /* ── C 紧凑化 ── */
  .chat-inner{padding:12px 6px 18px;gap:12px}
  .model-bar{gap:4px;padding:6px 8px}
  .model-bar > button{padding:5px 8px}
  .recheck-btn span{display:none}          /* "检测"两字省掉，只留图标（仍在，title 里有） */
  .msg{gap:8px}
  .avatar{width:26px;height:26px}          /* 头像略小 ⇒ 正文多一列宽 */
  .msg-body{max-width:calc(100% - 26px)}
  .composer-wrap{padding:0 6px calc(6px + env(safe-area-inset-bottom))}
  .conv-item{padding:8px 10px}
  .mm-item{padding:6px 9px;border-radius:6px}
}


/* ══════════════════════════════════════════════════════════════════════════════
   ★★★ v4.59.90 · 手机端布局优化（用户 2026-10-16 19:4x 报的三处）

   ⚠️ **只放在 ≤768px 里** —— 用户明确说"这都是手机版的问题，电脑版你都看不出来"
      ⇒ 电脑版样式一个字节都不动。

   三处病根（我实测量到的，不是猜）：
     ① **云盘 `.dm-item`**：单行 flex，`.n`（字数·大小·状态）是 `flex-shrink:0`
        ⇒ **它不肯压缩** ⇒ `.t`（文件名）被挤成省略号 ⇒ **名字露不出来**（用户原话
        「名字要展开显示嘛，他现在没有展开…后面这些放不开，建议你是换行」）
        ⇒ 改成**两行网格**：名字占满第一行（可换 2 行），元信息+按钮挪第二行。
     ② **知识库 `.kb-file`**：手机端**两套布局打架** —— 老规则写了 `display:flex` +
        `gap:10px`，新规则又写 `display:grid` + `column-gap:6px`；flex 的 gap **仍然生效**
        ⇒ 行内元素被两个 gap 同时推 ⇒ 用户看到「**那些都重复在一起了**」。
        ⇒ 手机端**只留 grid**，把 flex 遗留属性显式归零。
     ③ **会话 `.conv-item`**：`.rn`/`.del` 各 28×28，默认 `opacity:0` 但**仍然占位**
        （56px + 2×8px gap ≈ 72px）⇒ 手机窄屏（~360px）标题只剩 ~210px（约 14 个中文字）。
        ⇒ 默认 **width:0 + padding:0**（完全不吃宽度），行被按下/选中时才展开。

   ★ 判据（`_test_v45990_mobile_layout.py`）：
     · 本段必须在 `@media (max-width:768px)` 内（**电脑版不受影响**）
     · `.dm-item` 手机端必须是 grid 且名字行能换行（不再是 nowrap+ellipsis）
     · `.kb-file` 手机端 flex 遗留属性被清掉
     · `.conv-item .rn/.del` 默认不占宽
   ══════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  /* ── ① 云盘：两行网格，把文件名还给用户 ────────────────────────────── */
  .dm-item {
    display: grid !important;
    grid-template-columns: 22px minmax(0, 1fr) 22px 22px !important;
    grid-template-rows: auto auto !important;
    align-items: center !important;
    column-gap: 6px !important;
    row-gap: 2px !important;
    padding: 7px 8px !important;
    min-height: 0 !important;
  }
  /* 勾选框：第一行第一列，纵向居中跨两行更好看 */
  .dm-item .dm-ck { grid-area: 1 / 1 / 3 / 2 !important; align-self: center !important; }
  /* ★ 文件名：占满第一行剩余宽度，**允许换 2 行**（不再 nowrap+省略号） */
  .dm-item .t {
    grid-area: 1 / 2 / 2 / 3 !important;
    flex: none !important;
    white-space: normal !important;
    overflow: hidden !important;
    text-overflow: clip !important;
    display: -webkit-box !important;
    -webkit-line-clamp: 2 !important;
    -webkit-box-orient: vertical !important;
    word-break: break-all !important;
    line-height: 1.3 !important;
  }
  /* ★ 元信息（字数 · 大小 · 状态）：挪到**第二行**，允许换行，不再抢名字的地方 */
  .dm-item .n {
    grid-area: 2 / 2 / 3 / 3 !important;
    flex-shrink: 1 !important;
    white-space: normal !important;
    line-height: 1.25 !important;
    overflow: hidden !important;
  }
  /* 下载 / 删除：第二行右侧两个小按钮 */
  .dm-item .dl  { grid-area: 2 / 3 / 3 / 4 !important; }
  .dm-item .del { grid-area: 2 / 4 / 3 / 5 !important; }

  /* ── ② 知识库：清掉 flex 遗留，只留 grid（修"重复在一起"）──────────── */
  .kb-file {
    display: grid !important;
    /* ★★★ v4.59.91 修：原来写 `gap: 0 !important`（简写 = **两轴都清 0**）——
       它本意是"清掉 flex 时代的遗留 gap"，但把 **grid 自己的列间距也清掉了**
       ⇒ 元素**贴在一起** ⇒ 用户看到「红字和白字**重叠**」更严重。
       ⇒ 改成**只声明两轴的值**（不用 `gap` 简写 ⇒ 不会覆盖掉下面两行）。 */
    column-gap: 6px !important;
    row-gap: 3px !important;
    padding: 9px 10px !important;
  }
  .kb-file > * { min-width: 0 !important; }

  /* ★★★ v4.59.91：**给"红字"（`.f-dup`）明确位置**。
     病（既有缺陷，被这次放大）：`.f-dup` 没有 `grid-area`
       ⇒ grid **自动放置**把它塞进下一个空位 ⇒ **抢掉名字/按钮的格子** ⇒ 红白重叠。
     而它是**条件渲染**的（有「重复 ×N」或「仅知识库·原件缺失」才出现）
       ⇒ 平时看不见、一出现就乱 —— 典型的"只在特定数据下才犯的错"。
     ⇒ 修法：让它**独占一整行**（`grid-column: 1 / -1`）⇒ 自动落到新行，
       永远不会和别的元素抢格子。视觉上像"名字上面的一行小字"。
     ★ 判据（`_test_v45990_mobile_layout.py`）：`.f-dup` 必须有 `grid-column` 定位。 */
  .kb-file .f-dup {
    grid-column: 1 / -1 !important;
    font-size: 11px !important;
    line-height: 1.35 !important;
    white-space: normal !important;
  }

  /* ── ③ 会话行：按钮默认**不占宽**，省出标题空间 ─────────────────── */
  .conv-item { min-height: 38px !important; padding: 6px 8px !important; gap: 6px !important; }
  .conv-item .rn,
  .conv-item .del {
    width: 0 !important;
    height: 26px !important;
    padding: 0 !important;
    overflow: hidden !important;
    opacity: 0 !important;
    transition: width .12s, opacity .12s !important;
  }
  /* 手指按着那一行、或它已被选中/当前会话 ⇒ 才把按钮展开 */
  .conv-item:active .rn, .conv-item:active .del,
  .conv-item.active .rn, .conv-item.active .del,
  .conv-item.ms-selected .rn, .conv-item.ms-selected .del {
    width: 26px !important;
    opacity: 1 !important;
  }
  /* 多选模式下按钮本来就该藏（勾选才是主操作）—— 保持既有规则 */
  body.ms-mode .conv-item .rn, body.ms-mode .conv-item .del {
    width: 0 !important; display: none !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   ★★★ 2026-10-19（用户报的）：**手机端提醒弹窗可读性**
   用户原话：「我用手机看提醒界面时，有些东西都小得没法看，这个排版你调整一下吧。
             可以换行，不一定非得在同一行。具体你自己定吧」

   病（实测样式）：
     · `.btn-xs` 只有 padding:4px 10px / font-size:12px ⇒ 触控区约 30px
       （苹果/谷歌都建议 ≥44px，低于这个手指点不准）
     · 时间两个 select 固定 width:58px / height:34px ⇒ 又小又难点
     · hint 12px、天气列表 12.5px、天气小标题 11.5px ⇒ 手机上看着费劲
     · 我 2026-10-19 为"省高度"还把快捷按钮压成 nowrap + 横向滚
       ⇒ 手机上既难点又看不出还有几颗

   修法（**只加 CSS，不动结构**；只在 ≤640px 生效，桌面端排版不变）：
     · 触控目标一律 ≥44px
     · 字号：正文 ≥14px、输入 ≥16px（<16px 时 iOS 会自动放大整页，很晃）
     · 快捷按钮**允许换行**（用户明确说可以），不再横向滚
     · 时间两个 select 放大，日期控件也加高
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  /* 弹窗：占满更宽、内边距舒服、仍可滚 */
  #rem-modal.exec-modal {
    width: min(560px, 96vw) !important;
    max-height: 92vh;
  }
  #rem-modal .exec-head h3,
  #rem-modal .exec-head h4 { font-size: 17px; }

  /* 输入类：16px 字号（防 iOS 自动缩放）+ 44px 高 */
  #rem-modal input[type="text"],
  #rem-modal input[type="date"],
  #rem-modal input:not([type]),
  #rem-modal textarea {
    font-size: 16px;
    min-height: 44px;
    padding: 10px 12px;
  }

  /* 时间：两个 select 放大（原来 58×34 太难点） */
  #rem-modal select#rem-dt-hh,
  #rem-modal select#rem-dt-mi {
    width: 68px !important;
    height: 44px !important;
    font-size: 17px !important;
    padding: 0 6px !important;
  }
  /* 重复周期 select */
  #rem-modal select#rem-every {
    font-size: 16px;
    min-height: 44px;
  }

  /* ★ 快捷时间按钮：**允许换行**（用户说可以），每颗 ≥44px 高、字大一点 */
  #rem-modal .form-actions,
  #rem-modal div[style*="remQuick"] {
    flex-wrap: wrap !important;
    overflow-x: visible !important;
  }
  #rem-modal button[data-act^="remQuick|"] {
    min-height: 44px;
    padding: 10px 14px;
    font-size: 14.5px;
    flex: 1 1 calc(50% - 5px);   /* 一行两颗，整齐好点 */
    white-space: nowrap;
  }

  /* 手写折叠区标题 */
  #rem-modal summary { font-size: 14px; padding: 8px 0; }

  /* 添加/关闭按钮（本来 42px，抬到 46） */
  #rem-modal .form-actions .btn {
    min-height: 46px;
    font-size: 15px;
  }

  /* 提示文字 */
  #rem-modal .hint { font-size: 13.5px; line-height: 1.65; }

  /* 提醒列表：字号 + 行距 */
  #rem-list { font-size: 14px !important; line-height: 1.95 !important; }

  /* 天气块：标题 15px、说明 13.5px、列表 14px、加按钮 44px */
  #wx-card > div:first-child > span:first-child { font-size: 15px !important; }
  #wx-card > div:first-child > span:last-child { font-size: 12.5px !important; }
  #wx-list { font-size: 14px !important; line-height: 2.1 !important; }
  #wx-card input#wx-city { font-size: 16px !important; min-height: 44px; }
  #wx-card button#wx-add {
    min-height: 44px;
    padding: 10px 18px;
    font-size: 15px;
    flex: 0 0 auto;
  }
  /* 天气列表里每行的「删」按钮 */
  #wx-list button {
    min-height: 40px;
    min-width: 52px;
    font-size: 14px;
  }
  #wx-card .hint { font-size: 13px; }
}

/* 超窄屏（≤380px，iPhone SE 那种）：再收一点，别让两列挤坏 */
@media (max-width: 380px) {
  #rem-modal button[data-act^="remQuick|"] {
    flex: 1 1 100%;              /* 一颗一行，最好点 */
  }
  #rem-modal select#rem-dt-hh,
  #rem-modal select#rem-dt-mi {
    width: 62px !important;
  }
}

/* ★ 2026-10-19：**每日背单词提醒**的时间选择器也要手机友好
   （同一个"小得没法看"的问题 —— 那几个 select 原来也是 58×34） */
@media (max-width: 640px) {
  #mmr-start-hh, #mmr-start-mi,
  #mmr-stop-hh, #mmr-stop-mi {
    width: 68px !important;
    height: 44px !important;
    font-size: 17px !important;
    padding: 0 6px !important;
  }
  #mmr-interval { font-size: 16px; min-height: 44px; }
  /* 那一排「开始 / 结束 / 间隔」在窄屏上**换行**，不要硬挤三列 */
  .set-card .field[style*="display:flex"] {
    flex-wrap: wrap !important;
  }
  .set-card .field[style*="display:flex"] > div {
    flex: 1 1 140px !important;
    min-width: 140px;
  }
  #mmr-state { font-size: 13.5px; line-height: 1.7; }
  /* 保存按钮 */
  .set-card .form-actions .btn { min-height: 46px; font-size: 15px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   ★★★ 2026-10-19：**主操作固定底部**（解决「字大了 ⇒ 按钮被挤出视口」）
   
   背景：用户先说「有些东西小得没法看」，又说「可以换行」。
        字号放大后内容高 1146px，而手机视口只有 450px
        ⇒ 「添加提醒」被挤出（真 Chrome 几何判据抓到：滚到顶时可见=False）
   
   ★ 这不是二选一：**内容滚动 + 主操作固定** = 移动端表单标准做法。
     · 字号该多大就多大（可读性）
     · 「添加提醒 / 关闭」始终在弹窗底部可见（可达性）
   
   实现：`position: sticky; bottom: 0` 作用在 .form-actions 上
     · **不改 DOM 结构**（风险最低）
     · 滚动时贴在滚动容器底部
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  #rem-modal .form-actions {
    position: sticky;
    bottom: 0;
    z-index: 3;
    background: var(--bg-elev, var(--bg, #fff));
    margin-left: -4px;
    margin-right: -4px;
    padding: 10px 4px calc(10px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border, #3332);
    box-shadow: 0 -6px 16px rgba(0, 0, 0, .06);
  }
  /* 底栏里的按钮再明确一点 */
  #rem-modal .form-actions .btn {
    min-height: 46px;
    font-size: 15px;
    flex: 1 1 auto;
  }
}

/* ★ 2026-10-19：天气块副标题（原来靠内联 12.5px，手机上太小；
   现在桌面 12.5px、手机 12.5px→继承，由下面的 @media 决定） */
#wx-card > div:first-child > span:last-child { font-size: 12.5px; }

/* ══════════════════════════════════════════════════════════════════════════
   ★★★ 2026-10-19：提醒弹窗的**页签**（提醒 / 天气地址）

   为什么要有它（真 Chrome 6 种屏实测）：
     iPhone SE 375×667 → 视口 516，天气卡片 top=613 ⇒ **屏幕外，要滚 177px**
     500×640          → 视口 489，天气卡片 top=612 ⇒ **屏幕外，要滚 203px**
     iPhone 12 / 安卓 / 平板 / 桌面 → 可见
   ⇒ 病根是「两个功能纵向堆在一起」，小屏放不下。
   ⇒ 改成页签：各自**独占整个弹窗高度**，两边都不用滚就能看全。
     用户要的「做成一个界面」也保住了（还是同一个弹窗）。
   ══════════════════════════════════════════════════════════════════════════ */
.rem-tabs {
  display: flex;
  gap: 6px;
  margin: 0 0 12px;
  padding: 4px;
  background: var(--bg-soft, var(--bg, #f5f5f5));
  border-radius: 10px;
}
.rem-tab {
  flex: 1 1 0;
  min-height: 40px;
  padding: 8px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-sub);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.rem-tab.active {
  background: var(--bg-elev, #fff);
  color: var(--text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .08);
}
.rem-tab:hover:not(.active) { color: var(--text); }
/* 页签内容：一次只显示一个 */
.rem-pane { display: block; }

/* 手机：页签更大、更好点 */
@media (max-width: 640px) {
  .rem-tab {
    min-height: 46px;
    font-size: 15px;
    padding: 10px 14px;
  }
  .rem-tabs { margin-bottom: 14px; }
}
