/* style.css —— adapter-web 的样式。
 *
 * ★ 2026-10-10 重构：配色改用 DeepSeek Harness 的设计 token（vendor/ 里那两份）。
 *   原先的 --bg/--fg/--ai/--me 是粗粒度手调的，现在换成它的三层体系：
 *     --dsw-static-*    原始色板
 *     --dsw-alias-*     语义（bg-layer / label / border / state）
 *     --dsw-specific-*  组件级（bubble / input-major / sidebar / menu）
 *   ★ 好处：暗/亮两套它都备好了，我们不用自己调第二遍。
 *
 * 布局是三栏（学它的 ui-layout）：
 *   侧栏 260px（收起 56px）| 主内容 | 弹窗
 *   ★ 窗口 <1024px 侧栏变抽屉；手机同理。
 */

/* ─────────── 我们自己的尺寸变量（颜色都用 --dsw-*）─────────── */

:root {
  --sb-w: 260px;          /* 侧栏展开宽（它默认 280，我们窄一点够用） */
  --sb-w-collapsed: 56px; /* 收起后留一条控制栏 */
  --rsb-w: 340px;         /* 右侧边栏宽（2026-10-15）—— 放设置那类面板，比左侧栏宽一点 */
  /* ★ 弹框宽度（2026-10-19 主人：「PC 弹框宽度能统一调嘛？有点小气，再宽一半」）
   *   1100 → 1650。以前 #modal 和 #spanel 各写一份 1100，调一次要改两处 —— 提出来。
   *   ★ 实际宽度 = min(这个值, 100%)，而 100% = 屏幕 - #mask/#smask 两侧 24px 的 padding。
   *     所以：屏幕 ≥1698px 时是 1650；1440 笔记本会是满宽(1392)；手机上照旧满宽。
   *   ★ 定义在 :root 安全 —— 它是纯长度字面量，没有 var(--dsw-*) 引用（那种会在这儿求值断掉）。 */
  --modal-w: 1650px;
  --radius: var(--dsw-radius-md, 12px);
  --radius-lg: var(--dsw-radius-lg, 16px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  --ease: var(--ds-ease-in-out, cubic-bezier(.4, 0, .2, 1));
  --dur: var(--ds-transition-duration, .2s);

  /* ★ 输入区两层底色（主人 2026-10-12：想单独调，所以提出来）
   *   --composer-bg  卡片【外面】那一圈（#composer 的底）
   *   --card-bg      卡片【本身】的底
   * ★ 主人 10-12 定的分工（浏览器实测确认）：
   *   卡片外那一圈要【透明】、卡片自己要【保留白底】。
   *   所以 --card-bg 不能是 transparent —— 那会让卡片也变透（亮色下就成一片白）。
   * ★ 暗色下 --dsw-specific-input-major = bluish-850（比页面底 bluish-950 亮一档）；
   *   亮色下 = bluish-00 = 纯白 —— 跟页面底同色，所以亮色下卡片靠【边框】显形。
   */
  --composer-bg: transparent;
  --card-bg: var(--dsw-specific-input-major);

  /* ★ 面板内部的「软色块」（技能描述/正文、记忆条目、按钮 hover）—— 2026-10-21 新建。
   *
   *   来历：这五处原来写的是 `var(--dsw-alias-bg-secondary, #f5f6f8)`，
   *   而 `--dsw-alias-bg-secondary` 在 vendor/design-platform.css 里【根本不存在】
   *   （190 个 --dsw-* 里没有这个名字 —— 上游改过名，我们没跟上）。
   *   后果：暗色主题下取到 fallback #f5f6f8（近白），而正文色是 rgb(249,250,251)
   *   → 【白底白字，技能详情整块看不见】。10-21 晚上实测确认。
   *
   *   ★ 为什么不改成 --dsw-alias-bg-layer-1/2/3：
   *     那三个在【亮色下全是纯白】，换过去就是把"暗色看不见"变成"亮色看不见"。
   *   ★ 为什么不用 --dsw-alias-bg-multi-select（亮色 245,246,247 数值最接近）：
   *     语义是"多选标签底"，借来当内容块底，下次上游再改还会断。
   *
   *   所以自己定义，用半透明 —— 不依赖父层是什么底色：
   *     暗色 = 白 5.5% 提亮 · 亮色 = 黑 4% 压暗（≈ 原来的 #f5f6f8）
   *   ★ 纯 rgba 字面量，没有 var(--dsw-*) 引用 → 定义在 :root 安全（同上面 --modal-w 那条注记）。 */
  --soft-bg: rgba(255, 255, 255, .055);
}

html[data-theme="light"] {
  --soft-bg: rgba(0, 0, 0, .04);
}

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

html, body {
  height: 100%; margin: 0; overflow: hidden;
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
  overscroll-behavior: none;
}

body {
  background: var(--dsw-alias-bg-base);
  color: var(--dsw-alias-label-primary);
  font-family: var(--dsw-font-family);
  font-size: 15px; line-height: 1.6;
}

input, textarea, button, select { font-family: inherit; }
input, textarea { font-size: 16px; }   /* ★ ≥16px：不然 iOS 聚焦时会自动放大整页 */

button { cursor: pointer; border: 0; background: none; color: inherit; }

/* ─────────── 通用小件 ─────────── */

.btn {
  padding: 7px 12px; border-radius: var(--dsw-radius-sm, 8px);
  font-size: 13px; color: var(--dsw-alias-label-secondary);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
.btn.primary { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-inverted); }
.btn.primary:hover { background: var(--dsw-alias-button-primary-hover); }
/* ★ 危险动作（退出 / 删除那种不可逆的）—— 2026-10-14 加，第一个用户是侧栏的「退出」。
 *   用【状态红】而不是主色：主色是"主要动作"（保存/确认），这里要的是"这条回不去"。
 *   hover 用 brightness 而不是另一个 token —— 红这套在亮/暗两个主题下的 hover 值不一样，
 *   filter 一处就对两边都成立（换 token 得写两条规则、还得记住同步）。 */
.btn.danger { background: var(--dsw-alias-state-error-primary); color: #fff; }
.btn.danger:hover { filter: brightness(1.1); color: #fff; }
.btn.icon { padding: 7px 9px; font-size: 15px; line-height: 1;
            display: inline-flex; align-items: center; justify-content: center; }
.btn.icon .ic-i { width: 16px; height: 16px; display: block; }

/* ─────────── 三栏骨架 ─────────── */

#app { display: flex; height: 100vh; }
@supports (height: 100dvh) { #app { height: 100dvh; } }

/* ── 侧栏 ── */
#sidebar {
  flex: none; width: var(--sb-w);
  display: flex; flex-direction: column;
  background: var(--dsw-specific-sidebar-fill);
  border-right: 1px solid var(--dsw-alias-border-l1);
  padding-top: var(--safe-t);
  transition: width var(--dur) var(--ease);
  overflow: hidden;
}
#app.collapsed #sidebar { width: var(--sb-w-collapsed); }

.brand {
  display: flex; align-items: center; gap: 9px;
  padding: 16px 16px 12px; font-size: 15px; font-weight: 600;
  white-space: nowrap;
}
.brand .logo {
  flex: none; width: 26px; height: 26px; border-radius: 8px;
  background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
  display: grid; place-items: center; color: #fff; font-size: 13px;
}
#app.collapsed .brand span { display: none; }

/* ★ 2026-10-23：flex column —— 为了让末尾的 .sb-ver 能 "margin-top:auto 贴底"。
 *   内容少（侧栏高）→ auto 把它推到最下面；
 *   内容多（侧栏矮）→ 没有剩余空间，auto 退化成 0，它紧跟最后一项、一起滚。
 *   ★ 这是唯一能让同一个元素"有时贴底、有时跟内容"的写法；
 *     position:absolute / sticky / flex:none 都只能做到"永远贴底"。 */
.nav { flex: 1; overflow-y: auto; padding: 4px 8px;
       display: flex; flex-direction: column; }
.nav .lbl {
  font-size: 11px; color: var(--dsw-alias-label-caption);
  /* ★ 上大下小（2026-10-12 分组之后调的）：
     分组标签要"跟上一组断开、跟下面这组贴近"—— 那才是小标题的读法。
     上面 14px（隔开上一组）+ 下面 4px（贴住自己那组）。 */
  padding: 14px 8px 4px; white-space: nowrap;
  /* ★ 2026-10-16 第二轮：组头现在由【槽位自己造】—— `SLOTS.place()` 里
     createElement('div')，不是扩展、也不是 <button>。
     → 上一轮为 <button> 外壳补的那四条（display:block / width:100% /
       text-align:left / pointer-events:none）在这里【没有对象】了，删掉。
       （div 天然 block、天然撑满；没有 onClick 绑在它身上，也不需要拦点击。） */
}
#app.collapsed .nav .lbl { opacity: 0; }

.item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 10px; margin-bottom: 2px;
  border-radius: var(--dsw-radius-sm, 8px);
  font-size: 13.5px; color: var(--dsw-alias-label-secondary);
  white-space: nowrap; text-align: left;
  transition: background var(--dur) var(--ease);
}
.item:hover { background: var(--dsw-alias-interactive-bg-hover); }
.item.active { background: var(--dsw-specific-sidebar-nav-item-active); color: var(--dsw-alias-label-primary); }

/* ★ 归属标记（2026-10-16）—— 不是宿主自己带的那几项，标出它是【哪个插件】提供的。
 *
 *   为什么用 ::after（伪元素）而不是往里 append 一个 <span>：
 *     ★ 插件 mount 里是 `el.innerHTML = ...` —— append 进去的子节点会被它擦掉，
 *       而伪元素挂在【元素自己】身上，擦不掉。宿主就不用管"插件什么时候重画"。
 *   margin-left:auto 把它推到最右（.item 是 flex，见上面那条）。
 *   收起成图标条时不显示 —— 跟 .item span 同一个待遇（56px 里塞不下）。 */
.nav .item[data-ext]::after {
  content: attr(data-owner);
  margin-left: auto; padding-left: 6px; flex: 0 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
  font-size: 10px; opacity: .5; letter-spacing: 0;
}
#app.collapsed .nav .item[data-ext]::after { display: none; }

.item .ic { flex: none; width: 18px; height: 18px; display: block;
            color: currentColor; opacity: .9; }
.item.active .ic { opacity: 1; }
#app.collapsed .item span { display: none; }

/* ★ 版本号（2026-10-22）—— 侧栏底部、设置那一行【上方】、靠左。
 *
 * 「不要太显眼但要能看到」：
 *   · 三级文字色（最淡的那档）+ 11px + opacity .7
 *   · 等宽字体（版本串里的数字要对齐，看着才像"编号"而不是"一句话"）
 *   · ★ 靠左（不居中）—— 它是一行元信息，居中会像"标语"
 *
 * ★ user-select: text —— 别人看到版本号会想复制，禁选等于逼他手打。
 *   （而它是少数"该被选中"的界面元素：命令行的 -v 也是给人贴到对话里的。）
 *
 * ★ :empty 隐藏 —— 拿不到版本时不留空条。
 *   但 "unknown" 本身是有内容的，会显示 —— 那是对的：
 *   一个 unknown 比一片空白更能说明"这个二进制没嵌 VCS"。
 */
.sb-ver {
  /* ★ 2026-10-23：它是 nav 的最后一项（视觉上），但【DOM 里是第一个】。
   *
   *   为什么不能靠 DOM 顺序：侧栏那些菜单项不是写死在 HTML 里的，是
   *   `SLOTS['sidebar.nav.item'].place()` 用 `nav.appendChild()` / `insertAdjacentElement`
   *   一个个插进来的 —— 它们全部追加在原有的子元素【后面】。
   *   所以只要 sb-ver 是 HTML 里唯一那个 div，它就会永远停在 index 0（实测确认过）。
   *
   *   → 用 flex 的 order 把它排到最后（DOM 不动，键盘 tab 顺序也不受影响 —— 它不可聚焦）。
   *   margin-top:auto =「有剩余空间就贴底，没有就跟前面一项挨着」
   *   —— 侧栏高（菜单排完还有空）时被推到最下面；
   *      侧栏矮（手机，菜单排不下）时 auto 退化成 0，它紧跟最后一项、跟着一起滚。
   *   ★ 别写 position:absolute / sticky / flex:none：那会变成"永远贴底"，菜单一多就像压住内容。 */
  order: 999;
  margin-top: auto;
  flex: none;
  /* ★ 横向 6px 而不是 14px：它现在在 .nav 里，而 .nav 自己 padding 左右各 8px，
     8 + 6 = 14 —— 跟原来（在 aside 下、直接 14px）的视觉左边距一致。 */
  padding: 6px 6px 6px;
  font-size: 11px;
  line-height: 1.4;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--dsw-alias-label-tertiary);
  opacity: .7;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  user-select: text;
}
.sb-ver:empty { display: none; }
/* ★ 收起成图标条时不显示 —— 56px 放不下 "26.10.22.e3f5"，
   而省略号截出来的 "26.1…" 比不显示更糟（看着像坏了）。 */
#app.collapsed .sb-ver { display: none; }

.sb-foot {
  flex: none; padding: 8px;
  border-top: 1px solid var(--dsw-alias-border-l1);
  padding-bottom: calc(8px + var(--safe-b));
  /* ★ 2026-10-14：这行是三个按钮（设置 / 重启 / 关闭）—— 主人要的「设置那一行最右边加个重启、退出」。
   *   10-14 当天他又要了【等宽】→ 三个都是 flex: 1（各占 1/3）。
   *
   *   算过账（别凭感觉）：260px 侧栏 → 内宽 244px；减 2×4px gap 剩 236px → 每个 78.7px。
   *   一个按钮的内容 = padding 20 + 图标 18 + gap 9 + 两个汉字 27 = 74px → 余量 4.7px。
   *   ★ 三处文案都是两个字时刚好；将来谁写三个字，就靠下面那条 ellipsis 兜（别让它把行撑破）。 */
  display: flex; gap: 4px;
}
.sb-foot .item { margin-bottom: 0; flex: 1; min-width: 0; }   /* 行内布局：不要那个 2px 下边距 */
.sb-foot .item span { overflow: hidden; text-overflow: ellipsis; }
/* ★ 收起成图标条时：三个【竖着排】—— 跟上面 nav 一致（那儿也是一列图标）。
   ★ 不竖排会横向溢出：56px 里塞不下三个 38px 的按钮。 */
#app.collapsed .sb-foot { flex-direction: column; gap: 2px; }
/*    ★ 收起态只要把 `flex: 1` 按回去（改成 none）—— `width: 100%` 不用重复写：
 *      .item 基础规则本来就是 width: 100%，而 column 方向下它管的是【宽度】（交叉轴），
 *      跟 flex-basis（那是高度）不打架。★ 这条只在横向（上面那条 flex:1）才有陷阱。 */
#app.collapsed .sb-foot .item { flex: none; padding-left: 10px; padding-right: 10px; }

/* ── 主区 ── */
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* ── 右侧边栏（主人 2026-10-15）──
 *
 * 右上角那个齿轮开的就是它（原来是 openSettings 弹窗，入口已换掉）。
 *
 * ★★ 它凭什么"把聊天往左推"—— 就是这条，没有第二套逻辑：
 *   #app 是 flex row，它是【第三个子项】（侧栏 | 主区 | 右侧栏），
 *   宽度 0 ↔ --rsb-w。而 #main 是 flex:1 → 【自己变窄】→
 *   #main 上的 --pad-x（max(18px, (100% - 1000px)/2)）跟着重算
 *   → 居中的聊天列向左滑。宽度一动，消息列 / 输入卡片 / 页签栏一起动。
 *
 * ★ 不用 transform: translateX(100%)：那只是"滑出来【盖】住主区"，
 *   主区一个字不动 —— 主人要的是【推】不是【盖】。
 *   （窄屏反过来，见文件末尾的 @media：那时主区本来就没多少地方，
 *     再推就压没了，所以改回覆盖式抽屉。）
 *
 * ★ 左边界用 inset box-shadow，不用 border-left：
 *   元素 width:0 时 border 仍会占 1px（box-sizing: border-box 也压不掉），
 *   合上时会留一条孤零零的竖线；inset 阴影没有面积就画不出来。
 *
 * ★ 内容【延迟】一点淡入：宽度还没展开就显出文字的话，
 *   会看到字在窄缝里被挤着变宽 —— 主人说的"突兀"就是它。 */
#rsidebar {
  position: relative;              /* .rs-grip 的定位基准（接缝贴在 left:0） */
  flex: none; width: 0; min-width: 0;
  display: flex; flex-direction: column;
  background: var(--dsw-specific-sidebar-fill);
  box-shadow: inset 1px 0 0 var(--dsw-alias-border-l1);
  overflow: hidden;
  transition: width var(--dur) var(--ease);
}
#app.rs-open #rsidebar { width: var(--rsb-w); }
#rsidebar > * { opacity: 0; transition: opacity var(--dur) var(--ease); }
#app.rs-open #rsidebar > * { opacity: 1; transition-delay: .08s; }

/* ── 拖动接缝（2026-10-15，主人要的）──
 *
 * 在聊天区和工作区之间。拖它 = 改 --rsb-w（实时）。
 *
 * ★ 8px 的命中区 + 2px 的显示线：好抓，但平时不显眼 ——
 *   分界线本身已经有了（那条 inset shadow），不该再多一条常态的线。
 *   只在 hover / 键盘聚焦 / 拖动时亮起来。
 *
 * ★ touch-action:none —— 少了它，触屏上是"滚页面"而不是"拖接缝"。 */
.rs-grip {
  position: absolute; left: 0; top: 0; bottom: 0; width: 8px;
  cursor: col-resize; z-index: 2; touch-action: none;
}
.rs-grip::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  /* ★ 2026-10-21 修：原来是 var(--dsw-alias-brand-primary-new-color) —— 那个名字
   *   【不存在】（vendor 里只有 ...-new-colorprimary-new-color，上游模板拼重了）。
   *   而且这行【没写 fallback】→ 拖拽柄悬停时整条是空的（opacity 只在 hover 才出现，
   *   所以平时看不见，一直没人发现）。
   *   ★ 改用 static 色板 —— 它是三层里最底层、最不会被改名的：
   *     --dsw-static-*（原始色板）> --dsw-alias-*（语义）> --dsw-specific-*（组件级） */
  background: var(--dsw-static-deepseek-500, #4176e6);
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.rs-grip:hover::before,
.rs-grip:focus-visible::before,
#app.rsb-dragging .rs-grip::before { opacity: .5; }

/* 拖动中：① 去掉宽度过渡 —— 不然它慢半拍，跟不上手
 *        ② 全页换成拖动光标 + 禁选（否则会一路把文字刷蓝） */
#app.rsb-dragging #rsidebar { transition: none; }
#app.rsb-dragging, #app.rsb-dragging * { cursor: col-resize; user-select: none; }

.rs-head {
  flex: none; display: flex; align-items: center; gap: 8px;
  height: 52px; padding: 0 10px 0 16px;   /* 52px = #topbar 的高度，两栏齐平 */
}
.rs-head > b { flex: 1; font-size: 14px; font-weight: 600; white-space: nowrap; }
.rs-body { flex: 1; overflow-y: auto; padding: 8px 16px 16px; }

.rs-wip {
  height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px;
  text-align: center; color: var(--dsw-alias-label-caption);
}
.rs-wip > .ic-i { display: block; width: 28px; height: 28px; opacity: .5; }
.rs-wip > b { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-secondary); }
.rs-wip > span { font-size: 12px; }

/* ─────────── 工作区里的东西（2026-10-16：payload 渲染）───────────
 *
 * 一条带 payload 的 tool_result 往这儿放一张卡片（见 app.js 的 wsAdd）。
 *
 * ★ 颜色用 --dsw-alias-* / --dsw-specific-* —— 它们定义在【body】上，
 *   而 .rs-body 在 body 里 → 继承得到。
 *   ★★ 别用 --card-bg：它在 :root（html）上求值，而 --dsw-* 在 body 上
 *     → 整条静默失效（2026-10-16 实测，见本文件 1825 行那条长注释）。
 */
/* ─────────── 工作区：上面横条（缩略图）+ 下面详情（2026-10-16 主人定）───────────
 *
 *   ┌────────────────────┐
 *   │ [缩][缩][缩…]  →    │  .ws-strip   新的在【最左】（app.js 用 prepend）
 *   ├────────────────────┤
 *   │   选中项的详情       │  .ws-detail  默认选中【最新一条】
 *   └────────────────────┘
 * ★ 横条的好处【不是省地方】（340px 只放得下 4~5 个，比竖排还少）——
 *   是【列表和详情同屏】：点开看，不用先返回列表。
 *
 * ★ 颜色用 --dsw-alias-* —— 它们定义在【body】上，而 .rs-body 在 body 里 → 继承得到。
 *   ★★ 别用 --card-bg：它在 :root（html）上求值，而 --dsw-* 在 body 上
 *     → 整条静默失效（2026-10-16 实测，见本文件 1825 行那条长注释）。
 */
.rs-body.has-ws { display: flex; flex-direction: column; overflow: hidden; padding: 0; }

.ws-strip {
  flex: none; display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; overflow-x: auto; overflow-y: hidden;
  border-bottom: 1px solid var(--dsw-alias-border-l1);
}
.ws-strip::-webkit-scrollbar { height: 6px; }

/* ★★ "加载更早的文件"按钮（2026-10-23）——★ 当天改过：现在在 .ws-strip 【里面】，
 *   是条上的最后一个格子，尺寸跟文件卡片一致（168×56）。
 *
 *   为什么不放外面了：条的方向本来就是【从新到旧、往右看】，按钮在最右端
 *   = "更老的在后面，还有就点我" —— 位置本身就是语义。放外面得靠
 *   align-self + margin 硬掰到右边，跟条不在一根轴线上。
 *
 *   ★★ "prepend 新卡片时要不要把按钮挪回末尾"这个顾虑，用 order 解决：
 *     order:9999 让它【视觉上永远最后】，无论 DOM 里排第几个。
 *     （今早侧栏版本号用的同一招：order:999999。）
 *
 *   ★ 虚线边框：跟文件卡片的实线区分开 —— "这不是一个文件，是个动作"。
 *     只用 ＋ 号也能懂，但虚线让它在扫视时不会跟文件混成一片。
 */
.ws-more {
  order: 9999;                       /* ★ 永远排在最后（不靠"每次插完再挪"） */
  flex: none;
  width: 168px; height: 56px; padding: 0;
  border-radius: 8px;
  border: 1px dashed var(--dsw-alias-border-l2, #ccc);
  background: transparent;
  color: var(--dsw-alias-text-l3, #999);
  font-size: 20px; line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  opacity: .9;
  transition: opacity .15s, border-color .15s, color .15s;
}
.ws-more:hover:not(:disabled) {
  opacity: 1; color: var(--dsw-alias-text-l2, #666);
  border-color: var(--dsw-alias-border-l3, #bbb);
}
.ws-more:disabled { cursor: default; opacity: .5; }
.ws-more[hidden] { display: none; }

/* ★ 2026-10-19 主人要的：文件列表改成【卡片】—— 左图标、右简要信息。
 *   ★ 同一天第二轮改成：只排【一行】、横着滑动（最新的在左，app.js 用 prepend）——
 *     换行网格会让"文件一多"变成一段越来越高的墙，把下面的详情挤没。
 *   ★ 卡片宽 168：316 的可用宽里放得下 1.87 张 —— 第二张会被切掉一点，
 *     那不是没算好，是【故意的】：露出半张才看得出"还能往右滑"。
 *     正好装满（如 152×2+8=312）反而看不出可滑。
 *   ★ height 固定 56：图片/文件两种都靠它对齐；内容再多也只占两行（ellipsis）。 */
.ws-thumb {
  flex: none; width: 168px; height: 56px; padding: 0; cursor: pointer;
  display: flex; align-items: center; text-align: left;
  border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px;
  background: var(--dsw-specific-input-major); overflow: hidden;
  transition: border-color var(--dur) var(--ease);
  position: relative;              /* ★ 临时条目那个 ✕ 要 absolute 定位（2026-10-23） */
}
/* 图标块：图片铺满；文件退化成扩展名（wsThumb 里塞 .ws-ext） */
.ws-ico {
  flex: none; width: 44px; align-self: stretch;
  display: flex; align-items: center; justify-content: center;
  background: rgba(128, 128, 128, .10); overflow: hidden;
  font-size: 10px; font-weight: 600; letter-spacing: .04em;
  color: var(--dsw-alias-label-tertiary);
}
.ws-ico img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* 右侧两行：名字 + 元信息
 * ★ min-width:0 不给，flex 子项就不会缩到内容宽度以下 → ellipsis 永远不触发 */
.ws-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; padding: 0 10px; }
.ws-t, .ws-s-txt { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-t { font-size: 12px; color: var(--dsw-alias-label-primary); }
.ws-s { font-size: 11px; color: var(--dsw-alias-label-caption); }
/* 第二行 = 文本 + 下载角标（2026-10-19 主人：角标放「xx KB 右边」）
 * ★ 文字用 flex:1 + min-width:0 自己省略，角标 flex:none 永远在 ——
 *   不能反过来让整行 ellipsis，那样角标会被裁掉。 */
.ws-s { display: flex; align-items: center; gap: 4px; }
.ws-s-txt { flex: 1; min-width: 0; }

/* 卡片上的下载角标（2026-10-19）。★ 那天先放在图标块上、主人说不够明显，
 * 于是挪到「xx KB 右边」= 卡片右下角。
 * ★ 再改一次（同日）：主人说【只要图标，不要那圈黑色包裹】，颜色用【发送按钮那个蓝】。
 *   所以背景透明、图标直接是 --dsw-static-deepseek-500（跟 #send 同一个变量，
 *   实测 rgb(65,118,230)）。
 * ★ hover / 选中时才变实心（蓝底白图标）—— 反馈还在，但平时不占视觉重量。
 * ★ 14px 图标（原 12px）：去掉 18px 的包裹方块后，12 显得太小、跟 11px 的文字不成比例。 */
.ws-dl-btn {
  flex: none; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 4px;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  background: transparent;
  color: var(--dsw-static-deepseek-500);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.ws-dl-btn svg { width: 14px; height: 14px; display: block; }
/* ★ 2026-10-19 主人定：选中态【不】跟未选中区分 —— 只有鼠标悬停才变实心。
 *   理由：选中是"看详情"的状态，下载是另一个动作，不该因为选中就跟着变样。
 *   （原来的 .ws-thumb.on .ws-dl-btn 那条已删） */
.ws-dl-btn:hover,
.ws-thumb:hover .ws-dl-btn { background: var(--dsw-static-deepseek-500); color: #fff; }
/* ★ 窄屏卡片只有 56px（.ws-meta 整个藏掉），角标跟着 meta 一起没了。
 *   手机上的下载入口是【详情区那个大链接】（点开卡片就在下面）。 */
@media (max-width: 1024px) { .ws-dl-btn { display: none !important; } }

.ws-thumb:hover { border-color: var(--dsw-alias-label-tertiary); }
/* 选中态：用【前景色】描边 —— 它亮暗两套主题下都稳 */
.ws-thumb.on {
  border-color: var(--dsw-alias-label-primary);
  box-shadow: 0 0 0 1px var(--dsw-alias-label-primary);
}

/* 手机：只留图标格，跟原来一样（主人 2026-10-19 定） */
@media (max-width: 1024px) {
  .ws-thumb { width: 56px; height: 56px; }
  /* ★ ＋ 按钮跟着卡片一起收窄 —— 窄屏卡片是 56 的方格，
     它要是还保持 168 就比卡片宽出三倍，一眼看出"这不是同类"。（2026-10-23） */
  .ws-more { width: 56px; height: 56px; }
  .ws-ico { width: 100%; }
  .ws-meta { display: none; }
}

.ws-detail { flex: 1; overflow-y: auto; padding: 12px; }

.ws-name {
  font-size: 13px; font-weight: 500;
  color: var(--dsw-alias-label-primary);
  overflow-wrap: anywhere;        /* 长文件名 / 没空格的长串要能折行，别把侧栏撑宽 */
}
.ws-sub {
  margin-top: 4px; font-size: 12px;
  color: var(--dsw-alias-label-caption);
  overflow-wrap: anywhere;
}
.ws-dl {
  display: inline-block; margin-top: 8px; font-size: 12px;
  color: var(--dsw-alias-label-secondary);
}
.ws-dl:hover { color: var(--dsw-alias-label-primary); }

/* 详情里的大图（★ 点它 → .ws-lb 浮层，不跳新页面） */
.ws-big {
  display: block; width: 100%; height: auto; border-radius: 8px; cursor: zoom-in;
  background: rgba(128,128,128,.12);   /* 图还没加载出来时不至于一片空白 */
}

/* ── JSON 解析展示器（2026-10-23）──────────────────────────────
 *
 * 结构：.jt（一个块）
 *         ├ .jt-bar   工具条：JSON 标签 · [渲染|原始] · 展开 · 折叠 · 复制
 *         └ .jt-body  内容区：.jt-pane-render（树） / .jt-pane-raw（原文），二选一显示
 *
 * ★ 2026-10-23 08:29 主人改的：原方案是左右分栏（树 | 原文），后来他说
 *   「感觉可以做成一个块，上面加几个按钮？切换（原始、渲染）、展开、折叠、复制」。
 *   改的理由成立：PC 档右栏默认只有 340px（--rsb-w），内容剩 316px，
 *   分两栏每边 155px，key 稍长就折行 —— 比不分还难读。
 *   单栏块 + 工具条在窄栏下更舒服，而且「展开 / 折叠」这种动作分栏里根本放不下。
 *
 * ★★ 颜色【不用】--dsw-alias-state-*：那套是给【色块】用的实色，
 *   green-500 = rgb(34,197,94)，在白底上对比度只有 1.9:1 —— 当文字色根本看不清。
 *   （同族的坑：--dsw-alias-brand-primary 是【文字色】，我拿它当过背景。）
 *   这里自己定四个值，亮暗各一套，都是按 WCAG 挑过的（≥4.5:1）。
 */
html[data-theme="light"] body,
:root {
  --jt-str: #1a7f37;      /* 字符串 · 深绿 */
  --jt-num: #0550ae;      /* 数字   · 深蓝 */
  --jt-bool: #8250df;     /* 布尔   · 紫 */
  --jt-null: #6e7781;     /* null   · 中灰 */
}
html[data-theme="dark"] body {
  --jt-str: #7ee787;
  --jt-num: #79c0ff;
  --jt-bool: #d2a8ff;
  --jt-null: #8b949e;
}

/* 框：工具条 + 内容区拼成一块（圆角靠外层 overflow:hidden 裁，两块自己不写 radius） */
.jt {
  margin-top: 10px;
  border: 1px solid var(--dsw-alias-border-l1);
  border-radius: 8px;
  overflow: hidden;
  background: var(--dsw-alias-bg-layer-1);
}

/* ── 工具条 ── */
.jt-bar {
  display: flex; align-items: center; gap: 4px;
  padding: 3px 6px 3px 10px;
  background: var(--dsw-alias-markdown-code-block-banner, rgba(127, 127, 127, .09));
  border-bottom: 1px solid var(--dsw-alias-border-l1);
  flex-wrap: wrap;              /* 极窄时不至于把按钮挤出去 */
}
.jt-tag {
  font-size: 10.5px; letter-spacing: .03em;
  color: var(--dsw-alias-label-caption);
  margin-right: 2px;
}

/* yaml 那个 ? —— 解释"为什么树里的值和文件里不完全一样"（隐式类型转换）。
   ★ 不能省：那几类变形改不掉，不给入口就等于让人以为树就是原文。 */
.jt-hint {
  flex: none;
  width: 15px; height: 15px; line-height: 15px;
  text-align: center; border-radius: 50%;
  font-size: 10px; cursor: help;
  color: var(--dsw-alias-label-tertiary);
  border: 1px solid var(--dsw-alias-border-l2);
}
/* 空文件（内容是纯注释 / 空白也算）：说清是"读到了但没有值"，不是解析失败 */
.jt-empty {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--dsw-alias-label-tertiary);
}

/* 渲染 / 原始 —— 分段切换（两个按钮贴在一起，选中的那个浮起来） */
.jt-sw {
  display: flex; gap: 1px; flex: none;
  padding: 1px; border-radius: 6px;
  background: var(--dsw-alias-bg-layer-2);
  border: 1px solid var(--dsw-alias-border-l1);
}
.jt-sw > button {
  appearance: none; border: 0; cursor: pointer;
  font: inherit; font-size: 11px; line-height: 1;
  padding: 4px 8px; border-radius: 5px;
  background: transparent;
  color: var(--dsw-alias-label-secondary);
}
.jt-sw > button:hover { color: var(--dsw-alias-label-primary); }
.jt-sw > button.on {
  background: var(--dsw-alias-bg-layer-1);
  color: var(--dsw-alias-label-primary); font-weight: 600;
  box-shadow: 0 0 0 1px var(--dsw-alias-border-l1);
}

.jt-sp { flex: 1 1 auto; }      /* 把右边那组按钮顶到最右 */

/* 工具条右边的动作按钮（展开 / 折叠 / 复制）—— 照抄 .md-codefoot > .md-copy 的观感 */
.jt-bar > button[data-act] {
  appearance: none; cursor: pointer;
  font-family: inherit; font-size: 11px; line-height: 1;
  padding: 3px 8px; border-radius: 5px;
  border: 1px solid transparent; background: transparent;
  color: var(--dsw-alias-label-secondary);
  transition: background .12s, color .12s, border-color .12s;
}
.jt-bar > button[data-act]:hover:not(:disabled) {
  background: var(--dsw-alias-bg-layer-1);
  border-color: var(--dsw-alias-border-l2);
  color: var(--dsw-alias-label-primary);
}
.jt-bar > button[data-act].ok {
  color: var(--dsw-alias-state-success-primary);
  border-color: currentColor;
}
.jt-bar > button[data-act].err {
  color: var(--dsw-alias-state-error-primary);
  border-color: currentColor;
}
/* ★ 「原始」视图下展开/折叠没有对象 —— 真 disabled，不是只灰一下 */
.jt-bar > button[data-act]:disabled {
  opacity: .35; cursor: default;
}

/* ── 内容区：两块叠在同一个位置，靠 hidden 二选一 ──
 * ★ 用 [hidden] 而不是 display:none 直接写死：作者写了 display 会压过 [hidden]
 *   （memory 里那条「凡写 display 就补 [hidden]」）。这里两块都写了 display，所以补。 */
.jt-pane {
  max-height: 60vh; overflow: auto;    /* 展开全部也不把详情区撑到几千像素 */
}
.jt-pane[hidden] { display: none; }

.jt-pane-render {
  padding: 8px 10px;
  font-family: var(--ds-font-family-code, monospace);
  font-size: 12px; line-height: 1.75;
}
.jt-pane-raw pre {
  margin: 0; padding: 8px 10px;
  font-family: var(--ds-font-family-code, monospace);
  font-size: 11.5px; line-height: 1.6;
  white-space: pre;
}

/* markdown 正文（工作区，2026-10-23）——
 * ★ 不复用 .jt-pane-render：那条带着 JSON 树的等宽小字（font-family: code / 12px），
 *   而 md 正文要正文字体。换一个 pane 名就绕开，不用堆特异性去打。
 * ★ padding 交给里面的 .b.md（它本来就有），这里只管大小和滚动。 */
.jt-pane-md {
  max-height: 60vh; overflow: auto;
  font-size: 13px;
}
.jt-pane-md .b.md { padding: 12px 14px; }

/* 一行：key 按钮 + 值 */
.jt-row { display: flex; align-items: baseline; gap: 4px; min-width: 0; }
.jt-v { overflow-wrap: anywhere; }     /* 超长字符串（token / base64）别撑破栏 */

/* key 是 <button>（可 Tab、可点击复制路径），但长得像行内文字 */
.jt-k {
  appearance: none; border: 0; background: none; padding: 0 2px;
  font: inherit; line-height: inherit;
  color: var(--dsw-alias-label-primary);
  cursor: pointer; border-radius: 3px; flex: none;
}
.jt-k::after { content: ':'; color: var(--dsw-alias-label-caption); }  /* 冒号不进按钮文本 */
.jt-k:hover { background: var(--dsw-alias-interactive-bg-hover); }
.jt-k.ok { color: var(--jt-str); }     /* 复制成功——绿一下 */
.jt-k.err { color: var(--dsw-alias-state-error-primary); }

.jt-str { color: var(--jt-str); }
.jt-num { color: var(--jt-num); }
.jt-bool { color: var(--jt-bool); }
.jt-null { color: var(--jt-null); font-style: italic; }

/* 折叠节点：.jt-node 是 <details>，summary 里是 key + {n}/[n] */
.jt-node { margin: 0; }
.jt-node > summary {
  cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: 4px;
  min-width: 0;
}
.jt-node > summary::-webkit-details-marker { display: none; }
/* 三角：用伪元素，比原生 marker 好对齐（原生那个在 flex 里会歪） */
.jt-node > summary::before {
  content: '▸'; flex: none; width: 10px; font-size: 9px; line-height: 1;
  color: var(--dsw-alias-label-caption);
  transform: translateY(-1px);
}
.jt-node[open] > summary::before { content: '▾'; }
.jt-node > summary:hover .jt-k { background: var(--dsw-alias-interactive-bg-hover); }

.jt-kids { padding-left: 14px; border-left: 1px solid var(--dsw-alias-border-l1); margin-left: 4px; }

.jt-n { font-size: 10.5px; color: var(--dsw-alias-label-caption); flex: none; }
.jt-more { color: var(--dsw-alias-label-caption); font-size: 11px; font-style: italic; }
.jt-err {
  color: var(--dsw-alias-state-error-primary); font-size: 11.5px;
  font-family: inherit; white-space: pre-wrap; overflow-wrap: anywhere;
}

/* ─────────── LaTeX 源码高亮（2026-10-23）───────────
 *
 * 主人 13:58 问 LaTeX 后缀、14:00「先做 0+1」。
 *   → 前端不能编译 LaTeX（要 TeX 引擎），这里只做「把源码看清楚」：
 *     上色 + 保缩进 + 等宽。一个字都不改。
 *
 * ★ 色复用上面 JSON 展示器那套 --jt-*（都按 WCAG ≥4.5 挑过）。
 *   注释和花括号另加两档 —— 它们要「能看清但退到后面」，
 *   跟 --jt-null 同一个视觉层级，但不想跟 JSON 的 null 绑在一起。
 */
:root,
html[data-theme="light"] body {
  --tx-cmt: #666d75;      /* 注释 %…  —— 实测亮色 5.01:1（原来 #6e7781 只有 4.35，差一点不到 AA） */
  --tx-brk: #57606a;      /* 花括号 { } —— 比注释略深（结构比注释重要） */
  --tx-lit: #953800;      /* verbatim 里的原文 —— 暖棕，跟命令区分开 */
}
html[data-theme="dark"] body {
  --tx-cmt: #8b949e;
  --tx-brk: #adbac7;
  --tx-lit: #ffa657;
}

.tex-hl { line-height: 1.55; }
.tex-hl .texv-cmd { color: var(--jt-num); }        /* \section \begin … */
.tex-hl .texv-env { color: var(--jt-bool); }       /* {document} 里的名字 */
.tex-hl .texv-math { color: var(--jt-str); }       /* $…$ / $$…$$ */
.tex-hl .texv-cmt { color: var(--tx-cmt); font-style: italic; }
.tex-hl .texv-brk { color: var(--tx-brk); }
.tex-hl .texv-lit { color: var(--tx-lit); }        /* \verb|…| 的内容 */

/* ── 页面内看大图（★ z-index 60 > #mask 的 50 —— 弹窗开着时点图也得能看） */
.ws-lb {
  position: fixed; inset: 0; z-index: 60; cursor: zoom-out;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(0, 0, 0, .82);
}
.ws-lb img { max-width: 100%; max-height: 100%; border-radius: 8px; }

#topbar {
  flex: none; height: 52px; display: flex; align-items: center; gap: 6px;
  padding: 0 12px; padding-top: var(--safe-t);
  border-bottom: 1px solid var(--dsw-alias-border-l1);
}
#topbar .title { flex: 1; font-size: 13px; color: var(--dsw-alias-label-tertiary); }
#topbar .title b { color: var(--dsw-alias-label-primary); font-weight: 500; }
#dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--dsw-alias-state-idle-primary); margin-right: 7px;
  transition: background var(--dur) var(--ease);
}
#dot.on { background: var(--dsw-alias-state-success-primary); }

/* ─────────── 工作区有东西没看的小红点（2026-10-21 主人要的）───────────
 *
 * 场景：有文件发进工作区，但工作区【没打开】—— 这时候顶栏那个按钮上要是没提示，
 *   人就不知道右边有东西（页面其他地方一个字都不会变）。
 *
 * ★ 判据（app.js 的 updateRsBadge）：WS_ITEMS 非空 且 没有 rs-open。
 *   "看过就清"没做 —— 打开工作区那一下红点自己就消失（rs-open 一加上就不满足）。
 *   关掉之后如果里面还有东西，红点会回来 —— 那是字面意思（有东西、没打开）。
 *
 * ★ 用 ::after 而不是加一个 <i>：不用动 HTML 结构，一个 class 就够。
 * ★ 外面那圈 box-shadow 是【底色描边】—— 不然红点压在图标笔画上分不清边界。
 *   ★★ 底色变量是 `--dsw-alias-bg-base`（rgb(21,21,23) = body 背景）。
 *      我第一版想当然写成了 `--dsw-alias-bg-l1` —— **那个名字不存在**
 *      （真正的叫 bg-layer-1 / bg-base），于是 box-shadow 静默变成 transparent，
 *      红点直接压在图标笔画上。★ 这是今天第二次栽同一个坑（早上是 --ua-c-hit
 *      写在 .ua 上却用在 .ws）—— 变量名写错【不报错】，只是那条声明消失。
 *      验法只有一条：写完 getComputedStyle(el, '::after') 看一眼实际值。
 */
#rset-btn { position: relative; }
#rset-btn.has-new::after {
  content: '';
  position: absolute; top: 4px; right: 4px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--dsw-alias-state-error-primary);
  box-shadow: 0 0 0 2px var(--dsw-alias-bg-base, transparent);
  pointer-events: none;   /* 别挡住按钮自己的点击 */
}

/* ws-tip —— 工作区有东西时的【气泡】（主人 2026-10-21 16:12：「红点不是特别明显，
 * 能不能弹个小气泡、可关的」）。
 *
 * ★ 跟红点【分层】而不是二选一：
 *     气泡 = 强提示（第一次有东西时提醒）
 *     红点 = 弱提示（气泡被关掉后，"里面还有东西"这个事实仍在）
 *   两个同时出现反而是噪音 —— 所以 app.js 里 showTip 时把 has-new 摘掉。
 *
 * ★ 气泡挂在【按钮内部】（absolute 溢出到它下面），所以点气泡本身
 *   = 点按钮 = 打开工作区。这是想要的：看到提示，点它就去看。
 *   唯一例外是那个 ×（它 stopPropagation，只关提示、不打开）。
 *
 * ★ 定位：top: calc(100% + 9px) · right: 0 —— 从按钮右下角垂下来，
 *   因为按钮在顶栏最右，往左展开才不会出屏。
 */
.ws-tip {
  position: absolute; top: calc(100% + 9px); right: 0; z-index: 70;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px 7px 11px;
  background: var(--dsw-alias-bg-layer-1, #26272b);
  border: 1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));
  border-radius: 9px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .28);
  font-size: 12px; line-height: 1.3; font-weight: 400;
  color: var(--dsw-alias-label-primary, #e8eaed);
  white-space: nowrap;
  cursor: pointer;          /* 点它 = 开工作区，给人这个预期 */
}
/* 上方小三角，指向按钮（用 border 画，避免再要一个元素） */
.ws-tip::before {
  content: '';
  position: absolute; top: -5px; right: 11px;
  width: 8px; height: 8px;
  background: inherit;
  border-left: 1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));
  border-top: 1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));
  transform: rotate(45deg);
}
.ws-tip-t b { color: var(--dsw-alias-brand-text, #6f9dfb); font-weight: 600; }
/* ★★ [hidden] 必须补回 display:none（2026-10-21 16:18 主人报「没文件的也有那个气泡」）。
 *   同一天上午在 .cm-stat 上踩过【一模一样】的：
 *   `.ws-tip { display: flex }` 是【作者样式】，会压过浏览器 UA 的
 *   `[hidden] { display: none }` —— 于是 hidden 属性设了、元素照样显示。
 *   ★ 我还在 HTML 里硬编码了「1 个文件」当占位，所以看到的是
 *     「工作区有 1 个文件」—— 一个从来不存在的东西。
 *   判据：凡是给元素写了 display，就必须同时写 `[hidden]` 的 display:none。 */
.ws-tip[hidden] { display: none; }
.ws-tip-x {
  display: flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex: none;
  border: 0; border-radius: 5px; padding: 0;
  background: transparent; cursor: pointer;
  color: var(--dsw-alias-label-tertiary, #9aa0a6);
  font-size: 14px; line-height: 1;
  user-select: none;
}
.ws-tip-x:hover {
  background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,.08));
  color: var(--dsw-alias-label-primary, #e8eaed);
}
/* 手机上气泡别贴边（顶栏右边距小，right:0 会顶到屏幕边缘） */
@media (max-width: 620px) {
  .ws-tip { right: -6px; font-size: 11px; }
}


/* ─────────── 内容列限宽（主人 2026-10-12）───────────
 *
 * 「手机端 100% 没问题，PC 端 100% 有点过于宽了，压到大概 1000px」
 *
 * 为什么：PC 上 #main 会跟着窗口一路变宽（1920 屏就是 ~1660px），
 * 而一行字拉到 1600px —— 眼睛读到行尾要回头找下一行，很累。
 * 1000px 是主流聊天界面的量级。
 *
 * ★ 用 padding 做居中，【不动 DOM】：
 *   加一层容器会牵动 app.js 里对 #list 的引用（多处）。
 *   padding 是纯视觉的，JS 一个字不用改。
 * ★ 顶栏【不跟着缩】—— 它该铺满（状态条/连接点在最右边）。
 * ★ 手机上自动失效：100% - 1000px 是负数，max() 取 18px。
 */
/* ★ 锚点两个数，别混（2026-10-15 拆的）：
 *     --anch-w   = 条【自己】多宽（含缝）        → #anchors 宽度 / #anchtip 的 right
 *     --anch-pad = 为它【留】多少位（吃消息列宽） → #list / #composer / #tobottom 三处
 *   ★ 为什么拆：手机上条还在（浮在消息上面），但**不该再占宽度**（主人 10-15：
 *     "现在确实占了一点宽度，不太好"）→ 只要 `--anch-pad: 0`，三处留白一起消失，
 *     而消息列与输入卡片仍【等宽】（它们读的是同一个数）。
 *   ★ 写死 26 的话，改宽度时漏一个就又错位（同族：#composer 的 --cm-pad）。 */
#main { --col-w: 1000px; --pad-x: max(18px, calc((100% - var(--col-w)) / 2)); --anch-w: 26px; --anch-pad: var(--anch-w); }

/* ─────────── 页签（主人 2026-10-13）───────────
 *
 * 一条页签 = 一条【独立的消息流】：
 *   主页签         —— 和我对话
 *   子 agent 页签   —— 直接跟它对话（发消息自动带 /sub <id> 前缀）
 *
 * ★ 为什么放 #list 上面、不进侧栏：它是"看哪条流"的开关 —— 该跟着内容走；
 *   侧栏是"去哪"（功能区）。而且手机上侧栏默认收起，放里面就用不上了。
 * ★ 一直显示（哪怕只有主页签）：位置稳定，眼睛不用每次重新找。
 * ★ 窄屏横滑（overflow-x）—— 页签会变多，让它滚而不是挤成一坨。
 */
#tabbar-wrap {
  display: flex; align-items: flex-end; flex: none;
  padding: 6px var(--pad-x) 0;
  /* ★ 底线（主人 2026-10-13："页签栏不明显，加个底边线"）——
   *   它把"页签区"和"消息区"分开，也是当前页签那条高亮线的落点。
   *   ★ 10-13 挪到 wrapper 上：这样那条线也盖到右边的 ＋（否则 ＋ 底下缺一截）。 */
  border-bottom: 1px solid var(--dsw-alias-border-l1);
}
#tabbar {
  display: flex; align-items: flex-end; gap: 2px;
  flex: 1; min-width: 0;              /* ★ min-width:0 才能让下面的 overflow 生效（flex 子项默认不肯缩） */
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
#tabbar::-webkit-scrollbar { display: none; }

/* ＋ —— 页签栏右侧那个"打开对话"（主人 2026-10-13）。
 * ★ 尺寸跟一个页签等高（28px = .tab 的 border 1 + padding 7+7 + 行高 13）——
 *   不然它会比一排页签矮一截，看着像另一个控件。 */
#tabadd {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 1px solid transparent; border-bottom: 0;
  border-radius: 9px 9px 0 0;
  background: none; color: var(--dsw-alias-label-secondary);
  font: inherit; cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
#tabadd:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
#tabadd .ic-i { width: 15px; height: 15px; }

.tab {
  position: relative;                 /* ★ ::after 的定位父（那条高亮线） */
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  padding: 7px 10px;
  /* ★ 边框（主人要的）—— 用 transparent 占位，激活时换色 → 不跳。
   *   底边留 0：它跟 #tabbar 的底线重合，那条由 ::after 画。 */
  border: 1px solid transparent; border-bottom: 0;
  background: none; cursor: pointer;
  border-radius: 9px 9px 0 0;
  font: inherit; font-size: 13px; line-height: 1;
  color: var(--dsw-alias-label-secondary);
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tab:hover { background: var(--dsw-alias-interactive-bg-hover); }

/* ★ 当前页签（主人："分不清当前是哪个页签"）—— 三件事一起做，亮暗两色都分得出：
 *   ① 底色（暗色有效：卡片色比页面底浅一档）
 *   ② 文字转主要色（两种主题都有效）
 *   ③ 底下一条高亮线（两种主题都有效 —— 这条最硬）
 *   ★ 只靠底色的话亮色下等于没变（input-major 和页面底都是白的）。
 *   ★ 边框也一起给：不然激活时"只有底下有标记"，看着像半个控件。 */
.tab.on {
  background: var(--dsw-specific-input-major);
  border-color: var(--dsw-alias-border-l2);
  color: var(--dsw-alias-label-primary);
}
.tab.on::after {
  content: ''; position: absolute; left: 8px; right: 8px; bottom: 0;
  height: 2px; border-radius: 2px 2px 0 0;
  background: var(--dsw-alias-state-primary, #3b82f6);
}
.tab .tn { max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
.tab .ic-i { width: 14px; height: 14px; flex: none; opacity: .85; }

/* 关闭按钮：只有【当前页签】上才有它（主人 2026-10-13）
 *
 * ★ 这条修了三次，前两次都错在"藏起来 ≠ 点不到"：
 *   ① `opacity: 0` + `pointer-events: none`                → 主人："不行"
 *   ② ② + 把 :hover 关进 `@media (hover: hover)`           → 主人："还是不行"
 *      原因：手机上"点一下"会触发 :hover，而**部分手机浏览器照样匹配 hover:hover**
 *      （华为/平板的浏览器为了显示桌面版站点，会这么报）→ 那条 hover 规则还在生效。
 * ★ 主人的正解：`display: none` —— **不在渲染树里，就绝对不可能被点到**。
 *   它不依赖 pointer-events、不依赖媒体查询、不依赖 :hover 怎么算。**最不容易错。**
 * ★ 因此不做"悬停显示"（就是主人要的"统一"）：桌面和手机一样，两步 ——
 *   先点它（成了当前页签，× 才出来）→ 再点 ×。
 * ★ 代价（说清）：切页签时 × 的出现/消失会让页签宽度变 ~23px（一排标签动一下）。
 *   要消掉这个抖动得改成"绝对定位 + 右边固定留白"，但那又绕回去了 —— 先按主人说的来。
 */
.tab .tx {
  display: none;                      /* ★ 非当前页签：这个元素根本不存在 */
  align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: none; padding: 0;
  border: 0; background: none; cursor: pointer;
  border-radius: 4px;
  color: inherit;
}
.tab.on .tx { display: inline-flex; opacity: .6; }   /* ★ 只有当前页签：显示它、也才能点它 */
.tab .tx:hover { opacity: 1; background: var(--dsw-alias-interactive-bg-hover); }
.tab .tx .ic-i { width: 11px; height: 11px; }

/* ── 页签状态（主人 2026-10-13 要的三态）──
 *   转圈 = 这一轮在跑 · 红点 = 有未读 · 绿点 = 空闲可用
 * ★ 一个元素 + 三个变体类（不是三套元素）：位置固定，眼睛不用重新找。
 * ★ 主页签也带点 —— 它同样"在跑 / 有未读"，语义一致。
 */
.tab .ts { width: 7px; height: 7px; flex: none; border-radius: 50%; }
.tab .ts.idle  { background: var(--dsw-alias-state-success-primary, #22c55e); }
.tab .ts.unread{ background: var(--dsw-alias-state-error-primary); }
.tab .ts.run {
  background: none; width: 12px; height: 12px;
  border: 1.5px solid var(--dsw-alias-border-l3, rgba(128,128,128,.4));
  border-top-color: var(--dsw-alias-label-secondary);
  animation: tabspin .7s linear infinite;
}
@keyframes tabspin { to { transform: rotate(360deg); } }

/* ★ 10-13（主人要的右侧锚点条）：给 #list 包了一层 #listwrap。
 *   只为给锚点条一个【并排的列】，#list 自己一个字没改（flex/overflow/padding 照旧），
 *   只是换了个父节点 —— 它照旧撑满、照旧自己滚。 */
#listwrap { flex: 1; min-height: 0; position: relative; display: flex; }

/* ★ min-width: 0 是必需的：flex item 的默认 min-width 是 auto（不肯窄过内容），
 *   不加的话 #list 会顶着不缩。
 * ★ padding-right 多留 --anch-pad：锚点条坐在那儿，不留位消息文字会伸到它下面。
 * ★ 同时 #composer 也留同样多（见下）—— 两边都留，消息列和输入卡片才【等宽】
 *   （主人 10-13："div id=list 比输入框窄？这两者等宽才好看"）。
 *
 * ★★ 减 --sbw（10-15 主人报"卡片右边留白比左边宽一点"时改的）：
 *   滚动条【只坐在这一列里】，会把内容区吃掉 sbw。原来我们是让【卡片】也往左让 sbw
 *   —— 卡片内容跟消息列对齐了，但【卡片自己的右边留白】变成 12+sbw（左 12 / 右 20）。
 *   现在改成让【这一列自己】把 sbw 吃进 padding：
 *       视觉右边留白 = (pad − sbw) + sbw = pad     ← 跟左边的 pad 一样
 *   而卡片不再让 sbw → 卡片右缘 = V − pad = 消息列内容右缘 ✓ 两边都对上。
 *   ★ max(0px, …)：滚动条比留白还宽时（经典滚动条 17px > 手机 12px）别算出负数
 *     —— 负 padding 非法，整条声明会被丢掉。 */
#list {
  flex: 1; min-width: 0; overflow-y: auto;
  padding: 18px var(--pad-x);
  padding-right: max(0px, var(--pad-x) + var(--anch-pad) - var(--sbw, 0px));
  -webkit-overflow-scrolling: touch;
}

/* ── 右侧锚点条（主人 2026-10-13）──
 * 每条【我发的消息】一个短横线：点一下跳过去，悬浮看个大概。
 *
 * ★ 位置（主人 10-13 第二轮）：坐在「#list 内容区的右边、滚动条的左边」。
 *   第一版是 flex 的兄弟列 → 它占了最右，滚动条被挤到中间，
 *   主人："滚动条出现在锚点条的左边了，看着像把页面分成三块，很割裂"。
 *   → 改成绝对定位 + right 让开滚动条宽度：滚动条回到最右（原生位置不动），
 *   锚点条贴着消息区右缘（主人："放 class=stream 这个 div 的右边"）。
 * ★ right 用 --sbw（JS 量出来的滚动条宽度）：
 *   Chrome 15 / Firefox 17 / Mac 覆盖式 0 —— 硬编码一个数，换浏览器就压住滚动条。
 * ★ 固定不动 —— 不能跟着消息一起滚（像书的索引签）。
 * ★ 多了就自己滚（主人："多了就长一点就行"）—— 所以是 overflow-y: auto。
 */
#anchors {
  position: absolute; top: 0; bottom: 0;
  right: var(--sbw, 0px);
  width: var(--anch-w);
  /* ★ 左右不设 padding：内容区要【正好容得下】20px 的按钮 ——
   *   设了左 padding 内容区就只剩 18px，按钮 20px 会溢出被裁（align-items:center 会两头切）。 */
  padding: 18px 0;
  display: flex; flex-direction: column; align-items: center;
  overflow-y: auto; overflow-x: hidden;
  z-index: 20;
  /* ★ 条本身不拦点击 —— 它盖着 #list 的右 padding（那儿本来没内容），
   *   但保险起见：条 none、锚点 auto。（没有 :hover 规则会把它打开 —— 见 .tab .tx 那次的教训。） */
  pointer-events: none;
}
#anchors[hidden] { display: none; }
#anchors::-webkit-scrollbar { width: 0; height: 0; }

/* ★ 上下居中（主人 10-13）—— 用 auto 外边距，【不用】justify-content: center。
 *   后者在"条数比容器高"的时候会把顶部挤出可滚区域（滚不上去，看不到第一条）；
 *   auto 边距在没富余空间时自动退化成 0 → 条数多了自然铺满，两种情形都对。 */
.anch:first-child { margin-top: auto; }
.anch:last-child  { margin-bottom: auto; }

.anch {
  flex: none; width: 20px; height: 13px;
  padding: 0; margin: 0; border: 0; background: none;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  pointer-events: auto;      /* ★ 父级 #anchors 是 none，锚点自己打开（不然点不到） */
}
/* ★ 显眼一点（主人 10-13「可以做得再显眼一点」）：
 *   默认色从 border-l3（16% 白，太淡）换成 label-tertiary（实体灰），
 *   宽度 7→9、高度 2→3。三档递进：tertiary → secondary → primary。 */
.anch::before {
  content: ''; display: block;
  width: 9px; height: 3px; border-radius: 1.5px;
  background: var(--dsw-alias-label-tertiary);
  transition: width .15s var(--ease, ease), background .15s var(--ease, ease);
}
.anch:hover::before { width: 14px; background: var(--dsw-alias-label-secondary); }
/* ★ 当前正在看的那条：最长最亮 ——
 *   不加这个，点完跳过去就不知道自己在对话的哪一段了。 */
.anch.on::before { width: 17px; height: 4px; background: var(--dsw-alias-label-primary); }

/* 悬浮预览（消息前几十个字 + 时间）
 * ★ right 要跟着锚点条的位置走：--sbw（滚动条）+ 26（条宽）+ 4（缝）—— 不然会盖在条上。
 * ★ 底色【不透明】（主人 10-13）：原来用 --dsw-specific-menu → 底下是
 *   --dsw-menu-surface-fill = rgba(…,0.45)（半透明，字透过去糊）。换成 bg-layer-3（实色）。
 * ★ 保留原文换行（主人 10-13）：第一行用 white-space: pre-wrap ——
 *   文本侧 anchMsgText 现在也不再把 \n 压成空格了。 */
#anchtip {
  position: absolute; right: calc(var(--sbw, 0px) + var(--anch-w) + 4px); max-width: 260px;
  padding: 6px 9px; border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-alias-bg-layer-3, #353638);
  border: 1px solid var(--dsw-alias-border-l2);
  color: var(--dsw-alias-label-primary);
  font-size: 12px; line-height: 1.45;
  transform: translateY(-50%); overflow-wrap: anywhere;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .3);
  pointer-events: none; z-index: 30;
}
#anchtip .tip-x { white-space: pre-wrap; }
#anchtip[hidden] { display: none; }
#anchtip .tip-t { color: var(--dsw-alias-label-caption); font-size: 11px; margin-top: 3px; }

.m { display: flex; margin-bottom: 16px; }
/* ★★ 2026-10-12 修（主人报"手机端有左右滚动条，这个不好"）：
 *
 *   `.m` 是 flex 容器，它的直接子 div 是 flex item ——
 *   而 flex item 的【默认 min-width 是 auto】= 不许收缩到比内容窄。
 *   → 气泡里有长内容（深路径 D:\super-no-die-team\... / 长 URL / 长代码词）时，
 *     那个 div 被撑到 581px，把整个 #list 顶出横向滚动条。
 *
 *   实测：修之前 div 宽 581 / 父 364（超出 217px）；min-width: 0 之后自动收缩。
 *
 * ★ 顺带把宽度限制挪到这一层 —— 原来 `.b { max-width: 76% }` 是相对【那个 flex item】
 *   的宽度算的，而那个宽度本身由内容决定（自相矛盾：内容越宽、76% 越大）。
 *   限在这一层，才是"气泡最多占容器的 76%"。
 */
.m > div { min-width: 0; max-width: 76%; }
.m .b {
  /* ★ 左右 14px → 4px → 8px（主人 2026-10-14 连着两次调：「现在是 14px，换成 4px」
   *   → 试完「4px 又有点小，改成 8px」）。上下仍是 10px。
   *   ★ 原来那点左右内边距的理由（"不然文字跟头像/边缘贴死"）已被取代 —— 主人要更宽的正文。
   *   ★ 连带：气泡里的 markdown 子元素左右 margin 都是 0（见下面 .b.md p / ul 那几条）
   *     → 容器 padding 就是实际留白，不会再被 margin 补回来。 */
  padding: 10px 8px; border-radius: var(--radius-lg);
  /* ★ 主人 10-12：「agent 回复气泡的背景色调成透明/或者和背景色一致，
     看着大气点」。 ── 对：agent 的回话本来就长（表格/代码/分点），
     给它一块底色反而显得像"引用块"。透明之后它只是【正文】，读起来松。
     ★ 左右内边距原来是"不然文字跟头像/边缘贴死"，10-14 定到 8px（见上）。 */
  background: transparent; white-space: pre-wrap;
  word-break: break-word; overflow-wrap: anywhere;
  /* ★ 行高 1.58 → 1.10 → 1.30（主人 2026-10-14：先要 1.1，试完"有些地方又有点挤"，改 1.3）。
   *
   *   ⚠️ 这个值【整条气泡都继承】：agent 的、我自己的（.m.me）、
   *      以及里面的 markdown（p / li / blockquote / 表格 / 行内 code / 纯文本）。
   *      不受影响的只有自带行高的那几处：.md-code（代码块 1.5）·.tr-v（折叠块 1.5）·
   *      .m-notice（1.5）· #in（输入框 1.5）。
   *
   *   ⚠️ 行内代码那块底的算术（见 skill ㉒）——它比行盒高就会贴到上一行：
   *       底 = 12.5×lh + 3(上下 padding)   行盒 = 15×lh
   *       临界：12.5lh + 3 > 15lh  ⇔  lh < 1.2
   *     · 1.58 → 22.75 vs 23.70   不贴（余 0.95）
   *     · 1.10 → 16.75 vs 16.50   ★ 贴 —— 主人说的"有点挤"就是这儿
   *     · 1.30 → 19.25 vs 19.50   不贴，但【只剩 0.25px】—— 再往下调就又要贴了 */
  line-height: 1.3; user-select: text; -webkit-user-select: text;
}
/* agent 的气泡填满内容列（主人 10-12：「agent 的回复气泡宽度拉成 100%」）。
 * ★ 用 :not(.me) 而不是改默认值 —— 我自己那条要留着窄（短句多，铺满反而难读）。
 * ★ 手机上也铺满：聊天区本来就 100%，多留白只会让长消息更高。
 *
 * ★★ 2026-10-21：只有 `max-width: 100%` **不够**（主人报「短回复时气泡不是 100%」）。
 *   实测：416/702=59% · 513/702=73% · 702/702=100% —— 越短越窄。
 *   因为 `.m > div` 是 flex item，它的宽度【由内容决定】，max-width 只是上限。
 *   → 必须显式 `width: 100%` 才真是"占满"。
 *   ★ `.m.me` 那条不受影响：它没有 width，仍按 max-width:76% + 内容收缩。
 */
.m:not(.me) > div { max-width: 100%; width: 100%; }

.m.me { justify-content: flex-end; }
/* ★ 我的话气泡：浅底深字 / 深底浅字（主人 2026-10-13 定的两个值）
 *
 *   亮  #edf3fe   ← 就是 --dsw-static-deepseek-50（rgb 237,243,254，一字不差）
 *   暗  #2c2c2e   ← 中性深灰
 *
 * ★ 字色只用 --dsw-alias-label-primary 一个 token —— 它天然两副面孔：
 *     亮 → bluish-1000（近黑 #0f1115）在 #edf3fe 上 = 17.0:1
 *     暗 → bluish-50  （近白 #f9fafb）在 #2c2c2e 上 = 13.3:1
 *   都远超 AA（4.5:1）。所以"文字深浅"这件事交给设计系统，不写死。
 *
 * ★★ 为什么换掉旧的"品牌蓝 + 白字"——不只是不好看，是那儿有个真 bug：
 *     旧底 = deepseek-500，而【亮色主题的链接色也正好是 deepseek-500】
 *     → 对比度 1.00:1，我在亮色下发的链接【跟底一个颜色，等于隐形】。
 *     （旧注释只算了正文"白字 4.6:1"，没算链接。）换浅底深字顺带修了这个：3.8:1。
 *
 * ⚠️ 副作用（已量，见 skill）：代码块/行内代码的底（亮 #f9fafb / #fafafa，
 *   暗 #292929）跟新气泡底几乎一模一样（1.04~1.23:1，原来 4.1:1 是清楚的）
 *   → 代码块在新气泡里"不像一块代码"。要不要给气泡内的代码单独加底，主人定。
 */
.m.me .b {
  background: var(--dsw-static-deepseek-50); /* 亮 —— 原来写死 #edf3fe，正好等于 deepseek-50 的默认值；换成变量皮肤才跟得上 */
  color: var(--dsw-alias-label-primary);
  border-bottom-right-radius: 6px;
}
html[data-theme="dark"] .m.me .b { background: var(--dsw-static-neutral-bluish-850); }   /* 暗 —— 原来写死 #2c2c2e，= bluish-850 */
.m:not(.me) .b { border-bottom-left-radius: 6px; }
.m .meta { font-size: 11px; color: var(--dsw-alias-label-caption); margin-top: 5px; padding: 0 4px; }

/* ── 本地回显的状态标（2026-10-15）──
 *
 * 挂在气泡 meta 的尾巴上：发送中（转圈）/ 未回显（灰）/ 发送失败（红）。
 * ★★ 红只有【push 失败】那一种 —— 断链（SSE 断但 push 成功）走灰，
 *   因为那条消息其实【已经进了 core】（见 app.js 本地回显那节）。
 *   标红会让人去重发 → 重复。
 */
.m .meta .echo { display: inline-flex; align-items: center; gap: 3px; }
.m .meta .echo .ic-i { width: 11px; height: 11px; }
.m .meta .echo.st-unacked { color: var(--dsw-alias-label-caption); }
.m .meta .echo.st-failed { color: var(--dsw-alias-state-error-primary); }
.m .meta .echo.st-sending .ic-i { animation: echo-spin 1s linear infinite; }
@keyframes echo-spin { to { transform: rotate(360deg); } }
/* 尊重系统的"减少动态"设置 —— 不转也能看懂（旁边有"发送中"三个字） */
@media (prefers-reduced-motion: reduce) { .m .meta .echo.st-sending .ic-i { animation: none; } }

/* 占位整体淡一点：它还没被 SSE 认领（回显一到就撤掉、换成真的）。
   ★ 失败态不淡 —— 那是要让人看见的。 */
.m.is-echo .b { opacity: .7; }
.m.is-echo.is-failed .b {
  opacity: 1;
  box-shadow: inset 0 0 0 1px var(--dsw-alias-state-error-border, rgba(229, 72, 77, .30));
}

/* ── 通知 / 错误（主人 2026-10-13）──
 *
 * ★ 它们是 core 外发的 8 种 type 里最容易被漏掉的两种
 *   （前端原来落到最后那句 return 上，【静默丢了】）。
 *   notify = 主动告知（soulnote 生成 / memory 注入）；error = 出错。
 *
 * ★ 它们【不是对话里的一句话】—— 所以居中、不给气泡、不署名，
 *   跟左右两边的对话气泡在视觉上分开（一眼看出"这是一件事"不是"谁说的"）。
 */
.m-notice {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 9px auto; padding: 4px 10px;
  max-width: 92%; width: fit-content;
  font-size: 12px; line-height: 1.5; text-align: center;
  border-radius: 6px;
}
.m-notice .ic-i { width: 12px; height: 12px; flex: none; }
.m-notice .nc-emoji { flex: none; }

.m-notice.is-notify {
  color: var(--dsw-alias-label-caption);
  opacity: .85;
}

/* error：红字 + 淡红底 + 细边 —— 要一眼看见（出错是最该被知道的事） */
.m-notice.is-error {
  color: var(--dsw-alias-state-error-primary);
  background: var(--dsw-alias-state-error-bg, rgba(229, 72, 77, .10));
  border: 1px solid var(--dsw-alias-state-error-border, rgba(229, 72, 77, .30));
}
/* 文件改动清单（2026-10-23）—— 比普通 notify 稍微显眼一点：
 * 它不是"顺带说一声"，是"这一轮改了什么"，而且用户可能想回头看。
 * ★ 不用强调色（免得像错误），只加一点底色 + 更实的字色。
 * ★ 2026-10-23 二次改版后它已经不用这条了（改用下面的 .m-chg）——
 *   保留着当【没有 payload 时的退路】（addChanges 会退回 addNotice）。 */
.m-notice.is-change {
  color: var(--dsw-alias-label-secondary, #4b5563);
  background: var(--dsw-alias-bg-subtle, rgba(0, 0, 0, .035));
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .07));
  cursor: default;
}

/* ── 文件改动清单 · 可展开版（2026-10-23，主人当天二次要的）──
 *
 * 结构（三层，全用原生 <details>，零 JS 状态）：
 *
 *   <details class="m-chg">
 *     <summary>📝 改了 4 个文件（4 增）</summary>      ← 一层：摘要，默认折叠
 *     <div class="m-chg-body">
 *       <details class="m-chg-f is-added">
 *         <summary>+ scripts/x/y.txt   46 B</summary>   ← 二层：文件列表
 *         <pre class="m-chg-diff">…</pre>               ← 三层：diff
 *       </details>
 *       <div class="m-chg-foot">…</div>
 *     </div>
 *   </details>
 *
 * ★ 它跟 notify/error 一样是"发生了一件事" —— 所以仍然居中、不给气泡。
 * ★ 但展开后要给列表留宽度：折叠时 width:fit-content（像一行通知），
 *   展开时 100%（不然列表被 fit-content 挤成一列竖条）。
 * ★ 颜色走 GitHub 那套值（我 10-23 在 JSON 展示器里量过对比度：
 *   亮色 add 5.1 / del 5.4 / mod 7.6，暗色 8+，都过 WCAG AA 4.5）。
 *   ★ 不能用 --dsw-alias-state-* —— 那是实色，当文字色对比度只有 1.9。
 */
/* ★★ 这几个变量【两处共用】：聊天区的改动消息（.m-chg）、侧栏里的 diff 预览（.ws-diff）。
 *
 * 为什么必须共用（2026-10-23 主人：「侧栏没有样式？」）：
 *   它们原来只定义在 .m-chg 上。而「把 diff 推到工作区看」之后，
 *   那张左右对照表住在 .ws-detail 里 —— 没有 .m-chg 祖先 →
 *   var(--chg-add) 之类【全都取不到】→ 绿/红/灰退回继承色（就是正文黑）。
 *   ★ 变量丢了不报错、不白屏，只是颜色变黑 —— 所以看着像"没样式"。
 */
.m-chg, .ws-diff {
  --chg-add: #1a7f37;
  --chg-del: #cf222e;
  --chg-mod: #0550ae;
  /* 次要文字（大小 / note / 脚注 / diff 文件头）。
   * ★★ 不能用 --dsw-alias-label-caption —— 实测亮色下 rgb(173,178,184)，
   *    叠在这条消息的浅底上只有 **2.13:1**，WCAG AA 要 4.5，等于看不清。
   *    这一档是我按对比度挑的（亮 5.59 / 暗 6.01，都量过）。 */
  --chg-dim: #5c636a;
  /* 「改」这个标的颜色（橙）。★ 特意【不复用】--chg-mod ——
   * --chg-mod 是 diff 里 hunk 头的蓝，两处语义不同：一个是"这个文件被改了"，
   * 一个是"这段是 hunk 头"。混用的话，改一个色会连带把 diff 也染色。 */
  --chg-op-mod: #bc4c00;
}

/* .m-chg 自己的布局（那些 margin / width / border 不该给 .ws-diff） */
.m-chg {
  margin: 10px auto;
  /* ★ 2026-10-23 主人要「宽度 100%、大气一点」：
   *   原来是 width:fit-content（像一行通知），现在整条占满 ——
   *   因为展开后里面是文件列表 + 左右对照表，撑满才好读。 */
  width: 100%;
  font-size: 12.5px;
  line-height: 1.55;
  border-radius: 7px;
  color: var(--dsw-alias-label-secondary, #4b5563);
  background: var(--dsw-alias-bg-subtle, rgba(0, 0, 0, .035));
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .07));
}
html[data-theme="dark"] .m-chg,
html[data-theme="dark"] .ws-diff {
  --chg-add: #7ee787;
  --chg-del: #ff7b72;
  --chg-mod: #79c0ff;
  --chg-dim: #9aa3ae;
  --chg-op-mod: #ffa657;
}
/* （原来这里有一条 .m-chg[open]{width:100%} —— 现在一直 100%，它没用了） */

.m-chg > summary {
  display: flex; align-items: center; gap: 10px;
  /* ★ 高度拉起来（4px → 11px）：它是这条消息的"标题行"，太薄不像一条消息 */
  padding: 11px 16px;
  font-size: 13px;
  cursor: pointer;
  list-style: none;
  border-radius: 6px;
}
/* ★ 2026-10-23 主人定的形状：左边一个圆角方块（白底 + 小图标），右边两行。
 *   为了它把 summary 从「居中一行」改成「靠左、文字块吃掉剩余宽度」——
 *   最后那个箭头（::after）靠 .m-chg-hd 的 flex:1 顶到右边。 */
.m-chg-ico {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 8px;
  display: grid; place-items: center;
  background: #fff;
  color: #3f4650;                     /* 白底上必须用深色——见下 */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08);
}
.m-chg-ico .ic-i { width: 16px; height: 16px; display: block; }
/* 暗色下纯白 30px 会刺眼 → 压成浅灰（仍属于"白底"，图标还是深色） */
html[data-theme="dark"] .m-chg-ico { background: #e8eaed; color: #24292f; }
.m-chg-hd {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 1px;
  text-align: left;
}
.m-chg-l1 { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); line-height: 1.35; }
.m-chg-l2 { font-size: 11.5px; color: var(--chg-dim); line-height: 1.3; font-variant-numeric: tabular-nums; }
.m-chg > summary::-webkit-details-marker { display: none; }
/* 用 ::after 画箭头（不动 DOM —— summary 里塞 span 会影响居中） */
.m-chg > summary::after {
  content: '▸'; font-size: 9px; opacity: .5; flex: none;
}
.m-chg[open] > summary::after { content: '▾'; }
.m-chg > summary:hover { background: var(--dsw-alias-interactive-bg-hover); }
/* （原来这里有一条 .m-chg .nc-emoji —— 2026-10-23 头改成图标方块后它没用了。
 *   addNotice 那边还有 .m-notice .nc-emoji，那条要留着。） */

.m-chg-body { padding: 0 8px 7px; }

/* 一个文件一行。整行是个 <details> —— 点开是 diff。 */
.m-chg-f {
  border-top: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .06));
}
.m-chg-f > summary {
  display: flex; align-items: flex-start; gap: 7px;
  padding: 6px 6px;
  cursor: pointer;
  list-style: none;
  border-radius: 4px;
  min-width: 0;
}
.m-chg-f > summary::-webkit-details-marker { display: none; }
.m-chg-f > summary::before {
  content: '▸'; font-size: 8px; opacity: .45; flex: none;
}
.m-chg-f[open] > summary::before { content: '▾'; }
.m-chg-f > summary:hover { background: var(--dsw-alias-interactive-bg-hover); }

/* 动作标：增（绿）/ 改（橙）/ 删（红）。
 * ★ 2026-10-23 从 + ~ - 换成汉字 —— 符号要认一下才知道是哪个，一个字不用。
 *   宽度跟着从 12px 放到 15px：汉字比符号宽，12px 会把它压扁/换行。
 *   字数固定是 1，所以定宽仍然对得齐（不用改成 auto —— 那会让各行的
 *   路径起点参差不齐，扫一列文件时反而慢）。 */
.m-chg-op {
  flex: none;
  width: 15px; text-align: center;
  font-weight: 700;
  font-size: 11.5px;
  line-height: 1.45;
  font-family: var(--ds-font-family-text, -apple-system, sans-serif);
}
.m-chg-f.is-added .m-chg-op { color: var(--chg-add); }
.m-chg-f.is-modified .m-chg-op { color: var(--chg-op-mod); }
.m-chg-f.is-deleted .m-chg-op { color: var(--chg-del); }

/* 路径：★ 【完整绝对路径，不缩不省略】（2026-10-23 主人：「展示完整路径就好了」）。
 *
 * 放不下就换行 —— ★ 不用 text-overflow:ellipsis：那会切掉文件名，而
 * "改的是哪个文件"就是这条消息的全部信息量。
 * overflow-wrap:anywhere 让它优先在自然断点（\ / .）折行，实在没有断点才硬断。
 *
 * ★ 这里曾经有一套「前段可缩、后段保最后两级」的 flex 方案（chgSplitPath）——
 *   已删。原因记在 app.js chgSplitPath 原位置：那是按 6.9px/字符估算出来的需求，
 *   实测 366px 下 6 行一行都没被截。**没量过就设计，白费了一轮。** */
.m-chg-path {
  flex: 1 1 auto; min-width: 0;
  text-align: left;
  overflow-wrap: anywhere;
  font-family: var(--ds-font-family-code, monospace);
  font-size: 11.5px;
  line-height: 1.45;
}

.m-chg-size {
  flex: none;
  font-size: 10.5px;
  color: var(--chg-dim);
  font-family: var(--ds-font-family-code, monospace);
}

/* 「打开」（推到工作区看 diff）—— 2026-10-23 主人要的：hover / 展开时才露出来。
   ★ 手机上没 hover：由 media 查询改成常显（同 .m-chg-mgr 那套）。
   ★ 2026-10-24 从 20×20 图标方块改成文字按钮：图标要猜，文字不用猜；
     尺寸/描边照 .m-chg-mgr（同一个 summary 里长出来的，视觉得一致）。 */
.m-chg-push {
  flex: none;
  margin-left: auto;              /* 贴到这一行最右 */
  padding: 2px 8px;
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .12));
  border-radius: 5px;
  background: transparent;
  color: var(--chg-dim);
  font: inherit;
  font-size: 11px;
  line-height: 1.2;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s, background .12s, color .12s, border-color .12s;
}
.m-chg-f:hover > summary .m-chg-push,
.m-chg-f[open] > summary .m-chg-push,
.m-chg-push:focus-visible { opacity: 1; }
.m-chg-push:hover {
  background: var(--dsw-alias-interactive-bg-hover);
  color: var(--dsw-alias-link);
  border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, .22));
}
/* ★ 手机上没 hover —— 不常显就永远摸不到（同 .m-chg-mgr 那条） */
@media (hover: none) {
  .m-chg-push { opacity: 1; }
}

/* 临时条目上的 ✕（只在 data.temp 的卡片上出现，见 wsThumb） */
.ws-temp-x {
  position: absolute;
  top: 2px; right: 2px;
  display: grid;
  place-items: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--dsw-alias-bg-layer-2);
  color: var(--dsw-alias-label-secondary);
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s, color .12s;
  z-index: 1;
}
.ws-temp-x svg { width: 10px; height: 10px; }
.ws-thumb:hover .ws-temp-x, .ws-temp-x:hover { opacity: 1; }
.ws-temp-x:hover { color: var(--dsw-alias-state-error-primary); }

/* diff：整块自己滚（一个文件的 diff 可能几百行，不能把聊天流撑爆） */
.m-chg-diff {
  margin: 3px 0 6px;
  max-height: 380px;
  overflow: auto;
  border-radius: 5px;
  background: var(--dsw-alias-bg-base, rgba(0, 0, 0, .02));
  font-family: var(--ds-font-family-code, monospace);
  font-size: 11px;
  line-height: 1.5;
  text-align: left;
}

/* ── 左右对照表（2026-10-23）──
 *
 *   | 旧行号 | 旧内容 ‖ 新行号 | 新内容 |
 *
 * ★ 为什么定 min-width：这里【横向对齐承载信息】—— 左右两行必须一一对上才看得出
 *   "这几行被换成了那几行"。窄了每列只剩几个字，左右就对不上了。
 *   跟同一天 配置面板(620) / 钩子·界面(560) 是【同一个判据】：横向对齐承载信息的，
 *   定宽 + 外层横滚；表单类不承载对齐的，才让宽度自适应。
 *
 * ★ border-collapse:separate：collapse 会把相邻格边框合并，左右两半看着像一整块。
 * ★ tab-size 放在 table 上，td 里 pre 才认（行首缩进才显示成 4 空格宽）。
 */
.m-chg-split {
  border-collapse: separate;
  border-spacing: 0;
  min-width: 560px;
  width: 100%;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  tab-size: 4;
}
.m-chg-split td {
  padding: 1px 7px;
  vertical-align: top;
  white-space: pre;
}
/* 行号列：右对齐、窄、不参与换行 —— 也不参与选中（复制 diff 时不该带行号） */
.m-chg-split td.ln {
  width: 1%;
  min-width: 28px;
  text-align: right;
  font-size: 10px;
  color: var(--chg-dim);
  opacity: .72;
  user-select: none;
  white-space: nowrap;
}
.m-chg-split td.val { width: 50%; }
/* ★ 左半的右边框 = 左右分界线。只给第 2 列（左内容），不给行号列 */
.m-chg-split td.val:nth-child(2) {
  border-right: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .1));
}

.m-chg-split td.val.is-add { color: var(--chg-add); background: rgba(26, 127, 55, .07); }
.m-chg-split td.val.is-del { color: var(--chg-del); background: rgba(207, 34, 46, .07); }
/* 对面没有对应行的那一侧：压得更淡，一眼看出"这行是单边新增/删除" */
.m-chg-split td.val.is-none { background: rgba(128, 128, 140, .055); }
.m-chg-split tr.is-hunk td { color: var(--chg-mod); opacity: .85; background: rgba(5, 80, 174, .05); }
.m-chg-split tr.is-meta td { color: var(--chg-dim); }

html[data-theme="dark"] .m-chg-split td.val.is-add { background: rgba(126, 231, 135, .1); }
html[data-theme="dark"] .m-chg-split td.val.is-del { background: rgba(255, 123, 114, .1); }
html[data-theme="dark"] .m-chg-split td.val.is-none { background: rgba(160, 160, 170, .07); }
html[data-theme="dark"] .m-chg-split tr.is-hunk td { background: rgba(121, 192, 255, .08); }

/* ── 侧栏里的 diff 预览容器（2026-10-23）──
 *
 * 为什么它得单独一块：.m-chg-split 里写的是 font-family:inherit / font-size:inherit
 * —— 它假设自己住在 .m-chg-diff（聊天区那个 <pre>）里面。
 * 侧栏里没有那层，继承下来是【正文字体 13px】，表格会宽得离谱、还不对齐。
 * 所以这里把「等宽 + 字号 + 行距 + 横滚」补上，表格那套规则原样复用。
 *
 * ★ .m-chg-split 自己带 min-width:560px —— 侧栏 340px 里会横滚。
 *   这是判据内的行为（横向对齐承载信息），见上面那段注释。
 */
.ws-diff {
  overflow: auto;
  margin: 0 0 8px;
  border-radius: 6px;
  background: var(--dsw-alias-bg-base, rgba(0, 0, 0, .02));
  font-family: var(--ds-font-family-code, monospace);
  font-size: 11.5px;
  line-height: 1.5;
  text-align: left;
}
/* 表格自带 spacing/padding，容器只给一圈余量，别把行挤在一起 */
.ws-diff .m-chg-split { margin: 4px 0; }


/* 没有 diff 的解释（"不在 git 仓里" ≠ "这个文件没变"） */
.m-chg-note {
  padding: 2px 4px 6px 12px;
  font-size: 11px;
  font-style: italic;
  color: var(--chg-dim);
  text-align: left;
}

/* 脚：被截断的数量 / 扫描范围 / core 的补充说明 */
.m-chg-foot {
  margin-top: 4px;
  padding: 5px 4px 0;
  border-top: 1px dashed var(--dsw-alias-border-subtle, rgba(0, 0, 0, .09));
  font-size: 10.5px;
  color: var(--chg-dim);
  text-align: left;
}

/* ── 改动卡片上的【管理】按钮（2026-10-23 主人要的）──
 *
 * ★ 默认几乎看不见（opacity 0），hover 卡片或者卡片展开时才出来 ——
 *   它是个"想管的时候才找"的动作，不该跟"改了 5 个文件"抢注意力。
 * ★★ 但手机上【没有 hover】：所以 @media (hover:none) 里常显。
 *   不留这个例外的话，手机上这个按钮永远摸不到（同族：气泡里的 × 那次）。
 */
.m-chg-mgr {
  flex: none;
  margin-left: auto;
  padding: 2px 8px;
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .12));
  border-radius: 5px;
  background: transparent;
  color: var(--chg-dim);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s;
}
.m-chg > summary:hover .m-chg-mgr,
.m-chg[open] .m-chg-mgr { opacity: 1; }
.m-chg-mgr:hover {
  color: var(--dsw-alias-label-primary, #111);
  border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, .22));
}
.m-chg-mgr:focus-visible { opacity: 1; outline: 2px solid var(--dsw-alias-brand-primary, #4176e6); outline-offset: 1px; }
@media (hover: none) {
  .m-chg-mgr { opacity: 1; }
}

/* ── 忽略清单的「管理」弹窗 ──
 *
 * ★ 全部用 chgm- 前缀：插件/第三方库的样式是全局的，撞名不报错只是不生效
 *   （今天已经栽过两次：.mclose 被 KaTeX 抢走、.tx 撞了页签关闭按钮）。
 */
.chgm { font-size: 12.5px; }

/* tab 条 */
.chgm-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 10px;
  border-bottom: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .09));
}
.chgm-tab {
  padding: 6px 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--dsw-alias-label-secondary, #4b5563);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.chgm-tab b { font-weight: 600; opacity: .75; }
.chgm-tab:hover { color: var(--dsw-alias-label-primary, #111); }
.chgm-tab.on {
  color: var(--dsw-alias-label-primary, #111);
  border-bottom-color: var(--dsw-alias-brand-primary, #4176e6);
  font-weight: 600;
}

/* 一行结果提示（刚加/刚删/失败了） */
.chgm-msg {
  margin: 0 0 8px;
  padding: 6px 9px;
  border-radius: 5px;
  font-size: 11.5px;
  background: rgba(26, 127, 55, .09);
  color: #1a7f37;
  word-break: break-all;
}
.chgm-msg.bad { background: rgba(207, 34, 46, .09); color: #cf222e; }
html[data-theme="dark"] .chgm-msg { background: rgba(63, 185, 80, .13); color: #3fb950; }
html[data-theme="dark"] .chgm-msg.bad { background: rgba(248, 81, 73, .13); color: #f85149; }

/* ── tab1：文件行 ── */
.chgm-list { display: flex; flex-direction: column; gap: 1px; }
.chgm-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  border-radius: 5px;
}
.chgm-row:hover { background: var(--dsw-alias-bg-subtle, rgba(0, 0, 0, .035)); }
.chgm-row.is-done { opacity: .55; }
.chgm-p {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--ds-font-family-code, ui-monospace, monospace);
  font-size: 11.5px;
  word-break: break-all;   /* 完整路径不缩、不省 —— 窄了就换行 */
}
.chgm-done {
  flex: none;
  font-size: 10.5px;
  color: #1a7f37;
}
html[data-theme="dark"] .chgm-done { color: #3fb950; }

/* 每行右侧的 ⋯（原生 details，零 JS 状态） */
.chgm-more { flex: none; position: relative; }
.chgm-more > summary {
  list-style: none;
  width: 24px;
  height: 22px;
  line-height: 20px;
  text-align: center;
  border-radius: 5px;
  border: 1px solid transparent;
  color: var(--chg-dim);
  cursor: pointer;
  opacity: .55;
  transition: opacity .12s;
}
.chgm-more > summary::-webkit-details-marker { display: none; }
.chgm-row:hover .chgm-more > summary { opacity: 1; border-color: var(--dsw-alias-border-subtle, rgba(0, 0, 0, .12)); }
@media (hover: none) { .chgm-more > summary { opacity: 1; } }

/* 菜单浮在行上，不把行撑高 */
.chgm-menu {
  position: absolute;
  right: 0;
  top: 24px;
  z-index: 5;
  min-width: 200px;
  padding: 4px;
  border-radius: 7px;
  background: var(--dsw-alias-bg-layer-2, #fff);
  border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, .14));
  box-shadow: 0 6px 18px rgba(0, 0, 0, .16);
}
.chgm-menu button {
  display: block;
  width: 100%;
  padding: 6px 9px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--dsw-alias-label-primary, #111);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}
.chgm-menu button:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06)); }

.chgm-hint {
  margin-top: 8px;
  padding-top: 7px;
  border-top: 1px dashed var(--dsw-alias-border-subtle, rgba(0, 0, 0, .09));
  font-size: 10.5px;
  color: var(--chg-dim);
}

/* ── tab2：规则行 ── */
.chgm-rrow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border-radius: 5px;
}
.chgm-rrow:hover { background: var(--dsw-alias-bg-subtle, rgba(0, 0, 0, .035)); }
.chgm-rmain { flex: 1 1 auto; min-width: 0; }
.chgm-rtext {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--dsw-alias-label-primary, #111);
}
.chgm-raw {
  margin-top: 1px;
  font-family: var(--ds-font-family-code, ui-monospace, monospace);
  font-size: 10.5px;
  color: var(--chg-dim);
  word-break: break-all;
}
/* 【危】的标记：这条没有路径，匹配任何位置 */
.chgm-warn {
  flex: none;
  padding: 0 5px;
  border-radius: 3px;
  font-size: 10px;
  background: rgba(207, 34, 46, .12);
  color: #cf222e;
}
html[data-theme="dark"] .chgm-warn { background: rgba(248, 81, 73, .16); color: #f85149; }
.chgm-del {
  flex: none;
  padding: 3px 9px;
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .12));
  border-radius: 5px;
  background: transparent;
  color: var(--chg-dim);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s;
}
.chgm-rrow:hover .chgm-del { opacity: 1; }
@media (hover: none) { .chgm-del { opacity: 1; } }
.chgm-del:hover { color: #cf222e; border-color: rgba(207, 34, 46, .5); }

/* ── 二次选择视图 ── */
.chgm-ask { display: flex; flex-direction: column; gap: 8px; }
.chgm-ask-t { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #111); }
.chgm-ask.is-warn .chgm-ask-t { color: #cf222e; }
html[data-theme="dark"] .chgm-ask.is-warn .chgm-ask-t { color: #f85149; }
.chgm-code {
  padding: 1px 5px;
  border-radius: 4px;
  font-family: var(--ds-font-family-code, ui-monospace, monospace);
  font-size: 12px;
  background: var(--dsw-alias-bg-subtle, rgba(0, 0, 0, .06));
}
.chgm-big {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 12px;
  border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, .14));
  border-radius: 7px;
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.chgm-big b { font-size: 12.5px; color: var(--dsw-alias-label-primary, #111); }
.chgm-big span {
  font-family: var(--ds-font-family-code, ui-monospace, monospace);
  font-size: 10.5px;
  color: var(--chg-dim);
  word-break: break-all;
}
.chgm-big:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .05)); }
/* 【危】的那条：不填满红底（深底上大面积暖色烧眼睛），只描边 */
.chgm-big.is-danger { border-color: rgba(207, 34, 46, .45); }
.chgm-big.is-danger b { color: #cf222e; }
.chgm-big.is-danger:hover { background: rgba(207, 34, 46, .07); }
html[data-theme="dark"] .chgm-big.is-danger b { color: #f85149; }
.chgm-back {
  align-self: flex-start;
  margin-top: 2px;
  padding: 5px 12px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--chg-dim);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.chgm-back:hover { color: var(--dsw-alias-label-primary, #111); }

/* ── 命令结果底部的入口按钮（CMD_MORE，2026-10-23）──
 *  一个就够，所以做得像「下一步」而不是次要操作：描边 + 强调色字。
 *  ★★ 强调色用 --dsw-alias-link（vendor 里 = --dsw-static-deepseek-500，随皮肤变）。
 *    不要用 --dsw-alias-brand-primary —— 那个是【文字色】（= label-primary）！
 *    我第一版就写错了，实测取到 #101820（正文色），描边跟文字一样看不见。
 *    （10-22 记过这条，今天又踩一次 —— 所以这里把"为什么"写下来。）
 *  ★ hover 底色走中性的 interactive-bg-hover，不写死 rgba(蓝) ——
 *    皮肤换掉强调色之后，那块蓝底会跟整页不搭。
 */
.cmd-more { display: flex; justify-content: flex-end; margin-top: 14px; }
.cmd-more button {
  padding: 6px 14px;
  border: 1px solid var(--dsw-alias-link, #4176e6);
  border-radius: 6px;
  background: transparent;
  color: var(--dsw-alias-link, #4176e6);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.cmd-more button:hover {
  background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, .08));
}

.chgm-dirbox {
  padding: 7px 9px;
  border-radius: 6px;
  background: var(--dsw-alias-bg-subtle, rgba(0, 0, 0, .04));
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .08));
  word-break: break-all;
}
.chgm-dirrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.chgm-up {
  flex: none;
  padding: 3px 9px;
  border: 1px solid var(--dsw-alias-border-subtle, rgba(0, 0, 0, .12));
  border-radius: 5px;
  background: transparent;
  color: var(--dsw-alias-label-secondary, #4b5563);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.chgm-up:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .05)); }
.chgm-ruleprev {
  font-family: var(--ds-font-family-code, ui-monospace, monospace);
  font-size: 10.5px;
  color: var(--chg-dim);
  word-break: break-all;
}
.chgm-ruleprev.is-wide { font-size: 12px; color: var(--dsw-alias-label-primary, #111); }
.chgm-confirm-note { font-size: 11.5px; color: var(--chg-dim); line-height: 1.6; }


/* 转发来的（这句话不是在这个平台上说的）—— 左边一条细线，跟本地消息分开。
   ★ 标头（[来自 x] / [x 处理]）已经在显示时剥掉了，所以要有别的东西说明来处。 */
.m.fw .b { border-left: 2px solid var(--dsw-alias-border-l2); }

/* ★ 亮色下它对 AI 气泡给的是 deepseek-50（rgb 237,243,254）——
   跟白底几乎分不开 —— 于是原来"换成软灰，看得见又不抢眼"。
   ★ 2026-10-12 17:53（snow）：447618f 已把 agent 气泡统一定成透明
   （上面 .m .b 那条），但这条亮色覆盖忘了同步 —— 亮色下还留着灰底。
   这里补齐，让两套主题都是透明。
   回退：把 background 改回 var(--dsw-static-neutral-bluish-75)。 */
html[data-theme="light"] .m:not(.me) .b { background: transparent; }

/* ─────────── 「agent 回复气泡」开关（2026-10-20，主人要的）───────────
 *
 * 「设置 → 基础」里那个开关。默认【关】= 上面那套（透明，当正文读，大气）。
 * 打开 → 给 agent 的回复一块浅底 + 圆角，让它显成一个"泡"。
 *
 * ★ 为什么用 <html data-agentbubble> 而不是给每个气泡加 class：
 *   气泡是【流式渲染】的，一个个加 class 会漏（新节点、回放、合并块都走不同路径）。
 *   挂在 documentElement 上，CSS 一条规则管全部，JS 只改一个属性。
 *
 * ★ 特异性 (0,3,0)，跟上面那条亮色覆盖【一样】—— 所以这条必须在它【后面】。
 *   挪位置会让亮色下 switch 失效（同特异性下源码顺序后写的赢）。
 *
 * ★ 底色用半透明白：暗色下比页面底（rgb 21,21,23）亮一档，
 *   亮色下沉在白底上——两头都能"看见但不抢眼"。
 *   ✗ 别用 var(--card-bg)：它在 :root 求值、--dsw-* 在 body 上 → 整条静默失效（见 .cm-card 那段）。 */
html[data-agentbubble="on"] .m:not(.me) .b {
  background: rgba(128, 128, 140, .10);
  padding: 10px 12px;
  border-radius: var(--radius-lg);
  border-bottom-left-radius: 6px;   /* 跟原来那个"尖角"语义一致，只是多一圈底 */
}
html[data-agentbubble="on"][data-theme="light"] .m:not(.me) .b {
  background: rgba(0, 0, 0, .035);
}

/* ─────────── 中间过程（思考 / 工具调用 / 结果）───────────
 *
 * ★ 2026-10-12（主人）：这些原来【压根不显示】（core 一直在推，前端丢了）。
 *   现在收进一个折叠块 —— 默认折叠，点开看。
 *
 * 为什么不是 [open]：一轮里可能几十条（4 万条工具消息的库），
 * 全展开会把对话冲没。而折叠标题里带摘要（"file_read ×3"），
 * 不点开也知道它干了什么。
 *
 * 为什么用原生 <details>：默认折叠 + 点击展开 + 键盘可达，
 * 都不用写 JS（这个体量引组件库是杀鸡用牛刀）。
 */
.trace {
  margin: 0 0 14px 2px;
  border-left: 2px solid var(--dsw-alias-border-l2);
  padding-left: 10px;
}
.trace > summary {
  cursor: pointer; list-style: none;
  /* ★ 10-14：工具名【列全】后标题会换行 —— 用 flex-start 让图标对齐【第一行】
     （原来 center 在多行时会把图标飘到两行中间）。见 .tr-title 的换行规则。 */
  display: flex; align-items: flex-start; gap: 6px;
  font-size: 12px; color: var(--dsw-alias-label-caption);
  padding: 3px 0; user-select: none;
}
.trace > summary::-webkit-details-marker { display: none; }
.trace > summary:hover { color: var(--dsw-alias-label-secondary); }
.trace > summary .ic-i { width: 13px; height: 13px; flex: none; margin-top: 1px; }
/* ★ 10-14：标题要能【换行 + 断长串】—— 工具名列全后一行放不下。
   min-width: 0 是 flex item 的收缩下限（默认 auto = min-content，会让它撑破容器）；
   overflow-wrap: anywhere 让长工具名（无空格）也能断。 */
.trace > summary .tr-title { min-width: 0; overflow-wrap: anywhere; }
/* 进行中：一个小呼吸点（它是"还在干活"的唯一信号） */
.trace.live > summary { color: var(--dsw-alias-label-secondary); }
.trace.live > summary .ic-i { animation: tr-pulse 1.4s ease-in-out infinite; }
@keyframes tr-pulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } }
.trace[open] > summary { margin-bottom: 4px; }

/* 展开块【最下面】的"收起"（主人 2026-10-12）——
 * 「展开后如果滑到最下方、想再折叠起来要滑上去，能不能在展开信息的最下方
 *   也加个折叠功能、点击可以收回去」
 * ★ 它不是 summary 的子元素 → 点它【不会】触发原生 details 的展开/折叠，
 *   收起动作由 app.js 显式做（顺便把这一块拉回视野）。
 * ★ 只在展开时有意义；折叠时整块内容都不渲染（details 原生行为），不用管。 */
.tr-foot { display: flex; flex-direction: column; align-items: center; padding: 7px 0 1px; }

/* ★ 2026-10-17（主人）：内容一长，顶上那个「执行中… Ns」就被顶出视野了 ——
 *   底部「收起」【上面】再粘一行同样的状态，带一条分割线。
 *   ★ 只认 `.trace.live.long`（进行中 + 内容超高）：完成后的块不需要 ——
 *     它本来就收起、也没有秒数在跳。
 *   阈值（220px ≈ 12 行）由 app.js 的 TR_LIVE_LONG_PX 判，这里只管长相。
 *   ★ 2026-10-17（主人第二轮）：不要悬浮 —— "below 悬浮有点多余，反倒额外提供了遮挡显得页面有点乱，
 *     固定在那片的下方就好"。所以【不 sticky、不铺底色】：它就在块的屁股上，跟着内容走。 */
.tr-live { display: none; }
.trace.live.long .tr-live {
  display: flex; align-items: center; gap: 6px;
  align-self: stretch;                        /* 分割线要横跨整块宽度 */
  padding: 6px 0 4px; margin-bottom: 4px;
  border-top: 1px solid var(--dsw-alias-border-l2);
  font-size: 12px; color: var(--dsw-alias-label-secondary);
}
/* 小呼吸点 —— 跟 summary 上那个转/呼吸的图标同一个语义（"还在干活"） */
.trace.live.long .tr-live::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; opacity: .6; flex: none;
  animation: tr-pulse 1.4s ease-in-out infinite;
}
/* 2026-10-17 第二轮：原来这里是 position:sticky + 不透明底色（想让它"粘"在视野底部），
 * 但主人说那样多余又挡内容 —— 改回普通流：它就在 .tr-body 之后、"收起"之前。 */
.trace.live.long > .tr-foot {
  padding-bottom: 2px;
}
.tr-close {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 11px; border-radius: 999px;
  font-size: 11.5px; color: var(--dsw-alias-label-caption);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tr-close:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); }
/* 没有 i-chevron-up，把 chevron-right 转上来（sprite 里只有 right） */
.tr-close .ic-i { width: 12px; height: 12px; transform: rotate(-90deg); }

.tr { display: flex; gap: 7px; padding: 3px 0; font-size: 12.5px; }
.tr .tr-k { flex: none; width: 14px; padding-top: 2px; opacity: .5; }
.tr .tr-k .ic-i { width: 12px; height: 12px; }
.tr .tr-v { min-width: 0; flex: 1; color: var(--dsw-alias-label-secondary); line-height: 1.5; }
.tr-pre { white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; }
.tr-call .tr-v b { font-weight: 600; color: var(--dsw-alias-label-primary); }
.tr-call .tr-args { opacity: .65; margin-left: 6px; word-break: break-all; }
.tr-result .tr-v { opacity: .9; }
/* 思考那一块用斜体 + 淡一点 —— 跟"我说的话"分开（10-14：类名从 .tr-think 换成 .tr-clamp） */
.tr-clamp[data-kind="think"] .tr-v { font-style: italic; opacity: .8; }

/* ── 参数 / 结果：一行摘要 + 折叠看全文 ──
 *
 * ★ 2026-10-12（主人两轮反馈）：
 *   ① "参数显示成了 [object Object]" —— 数据里 arguments 是【对象】，
 *      我原来按字符串 JSON.parse → 成了 "[object Object]"。修在 app.js。
 *   ② "file_read 的响应 + exec 的响应太长了、能否默认折叠" —— 这条就是。
 *
 * 结构：<summary> 是【一行摘要】（默认状态就能看明白），
 *       点开是完整内容（参数是格式化 JSON，结果是截断后的原文）。
 */
.tr-more { display: block; min-width: 0; }
.tr-more > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 6px;
  padding: 1px 0; border-radius: 4px;
}
.tr-more > summary::-webkit-details-marker { display: none; }
.tr-more > summary:hover { background: var(--dsw-alias-interactive-bg-hover); }
/* ★ 小三角自己画（原生 marker 在 flex 里位置很怪） */
.tr-more > summary::before {
  content: '▸'; flex: none; width: 9px; opacity: .45; font-size: 10px;
}
.tr-more[open] > summary::before { content: '▾'; }
.tr-more[open] > summary { margin-bottom: 4px; }

.tr-call .tr-more > summary { color: var(--dsw-alias-label-secondary); }
.tr-result .tr-brief { opacity: .75; min-width: 0; flex: 1;
                       overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tr-size { flex: none; opacity: .45; font-size: 11px; }

/* 折叠里的完整内容（限高 + 可滚 —— 别把页面撑爆） */
.tr-more > .tr-pre {
  margin: 2px 0 6px 15px; padding: 7px 9px;
  background: var(--dsw-alias-markdown-code-block-banner);
  border-radius: 6px; font-size: 11.5px; line-height: 1.5;
  /* overflow:auto 是纵向限高（220px）；横向靠 overflow-wrap 断长串，
     别让手机上的横滚条从这儿冒出来。 */
  max-height: 220px; overflow-y: auto; overflow-x: hidden;
  overflow-wrap: anywhere;
  font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  color: var(--dsw-alias-label-secondary);
}

/* ── 段（seg）：一轮过程里的一段（主人 2026-10-13）──
 *
 * 折叠块里不再平铺一行行，而是「段」：
 *   text  段 = 思考 + 说话（合成一行）
 *   tools 段 = 连续的工具调用（摘要 = 工具名 × 次数）
 * ★ 摘要常显、详情点击才展开 —— 一眼看到节奏，要细节点进去。
 *   （结构：.trace > .tr-body > .seg > summary + .seg-more）
 */
/* ★ 10-13 修：这条必须带 `.tr-body >` 作用域。
 *
 *   原因：`.seg` 这个名字【被别人用了】——
 *     设置弹框里的主题切换是 `<div class="seg">`（app.js:2136），
 *     而它的 CSS 原来【漏写了 #modal 前缀】→ 变成一条【全局】`.seg { display: flex }`。
 *     → 同特异性、后写的赢 → 我的段（`<details>`）被撑成 flex：
 *       手机上 summary 和详情【左右并排】（summary 被压得又窄又高、详情撑出横滚条）。
 *
 *   ★ 修了两处：① 这条加作用域 ② 下面 "#modal .seg" 那组补前缀。
 *   ★ 判据：起类名之前先 grep 一遍有没有人用 —— `.seg` 这么通用的名字最容易撞。
 */
.tr-body > .seg { display: block; margin: 1px 0; }
.seg > summary {
  display: flex; align-items: baseline; gap: 6px;
  padding: 2px 5px; border-radius: 5px; cursor: pointer;
  font-size: 12.5px; color: var(--dsw-alias-label-secondary);
  list-style: none;
}
.seg > summary::-webkit-details-marker { display: none; }
.seg > summary:hover { background: var(--dsw-alias-interactive-bg-hover); }
.seg > summary .seg-k { flex: none; width: 14px; text-align: center; opacity: .5; }
.seg > summary .seg-k .ic-i { width: 12px; height: 12px; }
.seg > summary .seg-brief {
  min-width: 0; flex: 1;
  /* ★ 主人 10-13：不裁剪、完整展示 ——
     去掉 nowrap/ellipsis（那会截成一行 + "…"），允许换行。
     长串（URL / 无空格）也要能断，否则窄屏上撑出横向滚动条。 */
  white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
}
.seg[open] > summary { color: var(--dsw-alias-label-primary); }
/* （.seg.no-more 已删 2026-10-14：那个"点开是个空的"的场景随 text 段一起没了 ——
 *   段机制现在只管工具，而工具段必有内容。） */
/* 详情缩进一点 —— 视觉上"挂在"上面那行下面 */
.seg-more { padding-left: 19px; }

/* ─────────── 输入卡片（主人 2026-10-12）───────────
 *
 * 「输入框、发送按钮整理成一个偏卡片的样式，把 /status /usage 的信息统计放进来」
 *
 * 结构（index.html）：
 *   .cm-card
 *     .cm-in     输入 + 发送（上行）
 *     .cm-meta   状态 · 用量（下行，两个都可点开看完整的）
 *
 * 为什么做成卡片：输入区原来是一条"线"（textarea + 一个圆按钮），
 * 而这块地方其实承载两件事 —— 说话、看状态。卡片把这两件事收进一个视觉单元。
 */
#composer {
  flex: none;
  /* ★ 卡片外面那一圈的底 —— 提成变量，好单独调（默认 transparent）。
   *   ★ 它是透明的 = 显示的是 body 底色。所以"把它改透明"在视觉上不会有变化 ——
   *     这块底【本来】就透。要让它看起来不一样，得改页面背景或后面那层。 */
  background: var(--composer-bg);
  /* ★ position: relative —— 给 #tobottom 当定位上下文：
     它 absolute + bottom:100% 浮在【卡片上方】，不占文档流（见下面 #tobottom）。
     ★ padding-top 为 0（主人 10-12 报：那 10px 显示的是页面底色，亮色下 = 一条白边）。 */
  position: relative;
  /* ★ --cm-pad = 卡片到窗口边缘的横向距离。
   *   凡是"要跟卡片/发送按钮同列"的绝对定位元素都用它，别直接用 --pad-x。
   *   理由（主人 10-13 在手机上报）：
   *     --pad-x 是【消息列】的缩进（手机上 min 18px），而 #composer 在 ≤520px 时
   *     padding 是 12px —— 两者不是一回事。混用会让 #tobottom / .addmenu 偏 6px。
   *   → 手机上由 media query 把它覆盖成 12px（跟 #composer 的 padding 对齐）。 */
  --cm-pad: var(--pad-x);
  /* ★ 右边要【只让右边】：留出 --anch-w，跟 #list 的 padding-right 对齐 ——
   *   消息列和输入卡片才等宽（主人 10-13 报）。
   *   ★✗ 别写成简写的左右两半都加（我第一版就是这么错的）：
   *     padding: 0 calc(cm-pad + anch-w) …  → 左边也多缩 26px
   *     → 卡片比消息列窄一个 anch-w，主人一眼看出"差了个锚点列的宽度"。 */
  padding: 0 var(--cm-pad) calc(12px + var(--safe-b));
  /* ★★ 右边只让 --anch-pad，【不让 --sbw】（10-15 改）——
   *    滚动条那 8~15px 由 #list 自己吃进 padding（见上），卡片不再跟着让。
   *    让卡片让 sbw 的话：卡片内容跟消息列对齐，但【卡片自己的右边留白】
   *    会变成 cm-pad + sbw（左 12 / 右 20）—— 主人一眼就看出"右边宽了一点"。
   *   ★ --sbw 仍由 JS 量（syncSbw → documentElement 上）—— 现在只有 #list
   *     和 #anchors 用它，卡片/ #tobottom 都不用了。
   *   ★★ 别再在【后面】写一条不带它们的 padding-right —— 会静默盖掉这条
   *      （L764 那条就是这么坑的，见下面那段注释）。 */
  padding-right: calc(var(--cm-pad) + var(--anch-pad));
}

/* ── 运行中读秒（主人 2026-10-12）──
 *
 * 「消息发送成功后、在发送框这个卡片的上方加一个运行中+读秒之类的机制，
 *   收到最终回复后取消」
 *
 * ★ 它补的是一个真空白：从"发出去"到"收到 final"之间，界面是【完全静止】的
 *   —— 中间过程在折叠块里、状态条也不动。这段最让人怀疑"它到底收到没有"。
 *   读秒 = 一个"它还活着、已经跑了多久"的证据。
 * ★ 数字用 tabular-nums：不然每秒宽度在变，整行会抖。
 */
/* ── 卡片上方那一行：两个子元素现在【都是浮层】──
 * ★ 这一行本身【永远 0 高度】—— 卡片直接贴消息区，上方不留任何空白。
 *   （主人 10-12 报的"白边"就是它曾经占过的那条：那里显示的是页面底色，
 *     而 #composer 是透明的，所以亮色下 = 一条纯白，改背景色也去不掉。）
 */
.cm-top { display: flex; align-items: center; }

/* ★ #runbar（卡片上方的「运行中 3.4s」胶囊）在 2026-10-12 撤掉了 ——
 *   主人："发送消息后就显示那个折叠栏：进行中 xxS，收到别的再按当前逻辑累计进来"。
 *   → 读秒并进了消息流里的 .trace 块（见 app.js 的 paintLive / startClock）。
 *   现在卡片上方只剩 #tobottom 一个浮层，见下面。 */

/* 折叠块的标题：读秒数字用等宽（见 app.js paintLive —— 每 100ms 重画一次）。
   ★ 不加这个，数字宽度一跳，后面的工具名就跟着左右晃。 */
.tr-title { font-variant-numeric: tabular-nums; }

/* ── 回到最新（主人 2026-10-12）──
 *
 * 「聊天框不在底部的时候、发送卡片右侧往上一点提供一个滑到底部按钮，
 *   参考发送按钮样式」
 * 后续：「想要一样大，最好距离右侧的距离跟发送按钮在同一列上」
 * 再后续：「跑到发送框里面去了」→ 移回卡片外面
 * 再后续（★ 最终形态）：「我怎么感觉绝对定位浮着才是正解啊？试试？」
 *
 * ★ 它就是【浮在卡片右上方】的一个浮动按钮 —— 不占文档流，盖在消息区下缘，
 *   像微信 / ChatGPT 的"回到底部"。加阴影让它从内容里浮出来（不然会像"薄薄一层
 *   东西糊在字上"，主人 10-12 报过）。
 * ★ 尺寸/配色/圆角/图标全跟 #send 一样（36×36）。
 * ★ 定位：relative 的祖先是 #composer（见上）。所以——
 *     right:  var(--cm-pad) + 8px   卡片右边缘 8px 处（= 卡片边框 1px + .cm-in 右内边距 7px，
 *                                   跟 #send 同一列）
 *   ★ 用 --cm-pad 【不是】--pad-x（主人 10-13 报：手机上跟发送按钮差 6px ——
 *     因为手机的 --pad-x 是 18px 而 #composer 的 padding 是 12px）。
 *     bottom: 100% + 7px           底边在 #composer 顶边上方 7px —— 即浮在卡片上方一隙
 *   ★ 为什么不用 top/left：bottom 相对 #composer 顶边，而卡片就贴在那儿
 *     （padding-top:0）—— 卡片换行长高时它也不会跑偏。
 */
#tobottom {
  position: absolute;
  /* ★ 要跟 #send 同一列：#send 在卡片右边缘，而卡片右边缘往左让两样
   *   （--anch-pad 锚点条 + --sbw 滚动条 —— 后者 10-15 起【不由卡片】让了，
   *    改由 #list 自己吃进 padding，见 #list 那段）
   *   → 这里也得跟着让，不然它比发送按钮靠右。 */
  right: calc(var(--cm-pad) + var(--anch-pad) + 8px);
  bottom: calc(100% + 7px);
  width: 36px; height: 36px;
  border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-static-deepseek-500); color: #fff;
  display: grid; place-items: center;
  /* ★ 浮层该有的阴影 —— 让它在盖住的文字上面有"浮起来"的层次 */
  box-shadow: 0 2px 10px rgba(0,0,0,.28);
  transition: filter var(--dur) var(--ease);
}
/* ★ display:grid 也会盖掉 hidden 属性自带的 display:none —— 必须补回来 */
#tobottom[hidden] { display: none; }
/* sprite 里没有 chevron-up/down —— 把 chevron-right 转 90° 朝下 */
#tobottom .ic-i { width: 17px; height: 17px; transform: rotate(90deg); }
#tobottom:hover { filter: brightness(1.12); }

.cm-card {
  display: flex; flex-direction: column;
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: var(--dsw-radius-lg, 16px);
  /* ★★ 2026-10-20 主人要的：「输入框区域加个小阴影背景、显得像悬浮起来一样」。
   *
   * ★ 顺便修掉一个【一直没被发现的失效】（实测 getComputedStyle 确认）：
   *   这里原来写的是 `background: var(--card-bg)`，而 --card-bg 在 :root 里定义成
   *   `var(--dsw-specific-input-major)` —— 但 --dsw-* 全家挂在【body】上，
   *   在 :root（html）求值时拿不到 → --card-bg 变 invalid → **整条 background 失效**
   *   → 卡片其实一直是 `rgba(0,0,0,0)`（透明），只是有边框，所以没人看出来。
   *   直接写 --dsw-* 才能真的上色（暗 rgb(44,44,46) / 亮要浅）。
   *
   * ★ 悬浮感靠两样：实底（比页面底亮一档）+ 一圈柔和的投影。 */
  background: var(--dsw-specific-input-major);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .52), 0 2px 8px rgba(0, 0, 0, .34);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  overflow: hidden;
}
/* 亮色：底跟页面同为白，只能靠投影拉开层次（深色投影在亮底上要很淡，否则发脏）。
 * ★ 实测两个底都是 rgb(255,255,255) —— 所以这里只能靠阴影，别指望底色。 */
html[data-theme="light"] .cm-card {
  box-shadow: 0 8px 24px rgba(0, 0, 0, .13), 0 2px 6px rgba(0, 0, 0, .08);
}
/* 拖文件进输入区时：卡片描边亮起来 + 内圈加一圈柔光（2026-10-20） */
.cm-card.dragging {
  border-color: var(--dsw-alias-button-info-fill);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .52), 0 0 0 3px rgba(65, 118, 230, .22);
}
/* 聚焦时整个卡片亮一下（而不是单个 textarea 描边）—— 视觉上更"这是一个输入区" */
.cm-card:focus-within { border-color: var(--dsw-alias-button-info-fill); }

.cm-in { display: flex; align-items: flex-end; gap: 8px; padding: 7px 7px 7px 14px; }

/* 加号（主人 2026-10-13）—— 次要动作：透明底 + 一圈细边，悬停才出头。
 *  ★ 尺寸跟 #send 【严格一致】：都是 36×36（全局 box-sizing:border-box，
 *    所以这 1px 边框不会把它撑成 38 —— 撑大了就跟发送按钮错位了）。
 *  ★ 边框色用 --dsw-alias-border-l2：跟 .cm-card 自己的边框同一个值（一套）。
 *  ★ 图标也改成 17px，跟 #send 一样 —— 原来 19px 会让"＋"显得比"➤"大一号。
 *  ★ 不给主色：两个蓝按钮会互相抢，加号是次要动作。 */
#add {
  flex: none; width: 36px; height: 36px;
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: var(--dsw-radius-sm, 8px);
  background: transparent; color: var(--dsw-alias-label-secondary);
  display: grid; place-items: center;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
#add .ic-i { width: 17px; height: 17px; }
#add:hover, #add.on {
  background: var(--dsw-alias-interactive-bg-hover);
  border-color: var(--dsw-alias-border-l3);
  color: var(--dsw-alias-label-primary);
}
/* ★★ 这里原来有一条【无条件的】#composer 覆盖规则（padding-left/right 都设成 --cm-pad）
 *   —— 10-13 加锚点条时，它变成了【静默杀手】：
 *   同特异性（都是 #composer）下【源码顺序后写的赢】→ 上面那条
 *   `padding-right: calc(--cm-pad + --anch-w)` 变成死代码。
 *   ★ 症状：卡片比消息列【宽一整个锚点列】（主人 10-13 报："还是不行……差了个锚点列的宽度"）。
 *   ★★ 而我上一轮的"验证"是查【字符串在不在发出去的 CSS 里】—— 在。但它一个字不生效。
 *      "存在" ≠ "生效"，同特异性要看【谁在最后】。
 *   ★ 已删（主规则里那条 padding 简写本来就设了 left/right，它纯属多余）。
 *   ⚠️ 以后要再单独覆盖 #composer 的左右内边距：必须带上 --anch-w / --sbw，
 *      或者写到【比主规则更早】的地方 —— 别在它后面写一条"干净"的值。
 */
#in {
  flex: 1; min-width: 0;
  padding: 9px 0; border: 0; background: transparent;
  color: var(--dsw-alias-label-primary);
  resize: none; outline: none; line-height: 1.5;
  min-height: 36px; max-height: 130px;
  /* ★ 字号仍走上面那条 `input, textarea { font-size: 16px }` ——
     16px 是【防 iOS 聚焦时自动放大页面】的底线，别为了好看调小。 */
}
/* ★ 换成图标按钮（主人：「这个发送按钮你写得好随意哈哈哈，就一个字：发…换图标？」）
   —— 那个"发"是 10-10 随手写的。换成 i-send（Lucide 的 send 箭头）。 */
#send {
  flex: none; width: 36px; height: 36px;
  border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-static-deepseek-500); color: #fff;
  display: grid; place-items: center;
  transition: filter var(--dur) var(--ease);
}
#send .ic-i { width: 17px; height: 17px; }
#send:hover { filter: brightness(1.12); }

/* 停止这一轮（主人 2026-10-13）—— 发送后出现、收到 final 消失。
 *
 * ★ 红是【状态色】（--dsw-alias-state-error-primary）不是主色：
 *   它只在"这一轮还在跑"时存在，天然是个临时动作 —— 用状态红提醒"这会打断它"。
 * ★ 尺寸跟 #send #add 一致（36×36 + 同样圆角），三个按钮一条线。
 * ★ 图标 15px（比 17 小一号）：方块是【实心块】，17px 会显得比箭头重。
 * ★ 动画【两段分开】：
 *     入场  stopIn   —— 淡入 + 从右边 6px 滑进来 + 微微放大（像"挤"出来）
 *     退场  stopOut  —— 反向（比入场稍快，消失不拖沓）
 *   入场靠 `:not([hidden])` 触发：display 从 none 变 grid 时动画自动重跑。
 *   退场靠 .off 加动画，播完（160ms）JS 才真正 hidden —— 不然 display:none 会当场剪掉动画。
 * ★ 只做 opacity / transform —— 不碰 width，所以【不会把输入框挤来挤去】。
 *   （发送时输入框本来就空了，但没必要动布局。）
 */
#stop {
  flex: none; width: 36px; height: 36px;
  border: 0; border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-alias-state-error-primary); color: #fff;
  display: grid; place-items: center;
}
#stop[hidden] { display: none; }
#stop .ic-i { width: 15px; height: 15px; }
#stop:hover { filter: brightness(1.12); }
/* 已经点过 /stop —— 变暗 + 不再响应（防连点）。下一个回合 startClock 会重置。 */
#stop.busy { opacity: .5; pointer-events: none; }

#stop:not([hidden]) { animation: stopIn .20s var(--ease); }
#stop.off { animation: stopOut .16s var(--ease) forwards; }

@keyframes stopIn {
  from { opacity: 0; transform: translateX(6px) scale(.88); }
  to   { opacity: 1; transform: none; }
}
@keyframes stopOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateX(6px) scale(.88); }
}

/* ─────────── 附件（主人 2026-10-13）───────────
 *
 * 两个地方用同一套 .attach 单元：
 *   ① 卡片里的【待发预览条】(.cm-atts) —— 64×64，带移除按钮
 *   ② 消息流里的【已发附件】(.m-atts)  —— 108×108，点开看大图
 * 缩略图来源：/api/file?path=…（同源、带 cookie）
 */
.cm-atts {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 10px 12px 0;
  overflow-x: auto;
  scrollbar-width: thin;
}
/* ★ display:flex 会盖掉 hidden 自带的 display:none —— 不加这句，预览条永远显示。
   （今天在 #runbar / #tobottom 上已经栽过。） */
.cm-atts[hidden] { display: none; }

.attach {
  position: relative; flex: none;
  width: 64px; height: 64px;
  border-radius: 10px; overflow: hidden;
  border: 1px solid var(--dsw-alias-border-l1);
  background: var(--dsw-alias-interactive-bg-hover);
}
.attach a { display: block; width: 100%; height: 100%; }
.attach img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 非图片：图标 + 文件名（名字可能很长 → 截 3 行） */
.attach .fi {
  width: 100%; height: 100%; padding: 4px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; text-align: center;
}
.attach .fn {
  font-size: 9.5px; line-height: 1.2; color: var(--dsw-alias-label-secondary);
  word-break: break-all; max-width: 100%; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}
.attach .ic-i { width: 18px; height: 18px; flex: none; color: var(--dsw-alias-label-tertiary); }
/* 移除按钮（只出现在预览条里）—— 浮在右上角 */
.attach .rm {
  position: absolute; top: 3px; right: 3px;
  width: 18px; height: 18px; border-radius: 50%;
  background: rgba(0,0,0,.62); color: #fff;
  display: grid; place-items: center;
}
.attach .rm .ic-i { width: 11px; height: 11px; color: #fff; }
.attach .rm:hover { background: rgba(0,0,0,.82); }

/* ── 上传中（2026-10-20 加进度条）────────────────────────
   ★ 为什么用 XHR：fetch 拿不到上传进度（它只有响应流的下载侧）。
   ★ 底边那条是【真进度】：xhr.upload.onprogress 的 loaded/total。 */
.attach.up-item { border-color: var(--dsw-static-deepseek-500); }
.attach.up-item .fi { gap: 2px; padding-bottom: 8px; }   /* 给底边的进度条让位 */
.attach.up-item .ic-i.spin { animation: att-spin 1s linear infinite; color: var(--dsw-static-deepseek-500); }
@keyframes att-spin { to { transform: rotate(360deg); } }
.attach .pct {
  font-size: 9px; font-variant-numeric: tabular-nums;
  color: var(--dsw-static-deepseek-500);
}
.attach .up {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: rgba(128, 128, 140, .22);
}
.attach .up > i {
  display: block; height: 100%; width: 0;
  background: var(--dsw-static-deepseek-500);
  transition: width .12s linear;
}

/* 消息流里的附件：同一套单元，大一号 + 换行排列 */
.m-atts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.m-atts .attach { width: 108px; height: 108px; }
.m-atts .attach .ic-i { width: 22px; height: 22px; }
.m-atts .attach .fn { font-size: 10.5px; }

/* 加号菜单 —— 绝对定位相对 #composer（★ 不能放 .cm-card 里：它有 overflow:hidden，
   会把菜单裁掉）。位置：卡片上方 7px、左缘跟加号按钮对齐。 */
.addmenu {
  position: absolute;
  /* ★ --cm-pad（不是 --pad-x）—— 跟 #tobottom 同一条理由，手机上才跟加号按钮对齐 */
  left: calc(var(--cm-pad) + 14px);
  bottom: calc(100% + 7px);
  z-index: 4;
  display: flex; flex-direction: column; gap: 2px;
  padding: 4px; min-width: 112px;
  background: var(--dsw-alias-bg-layer-2, var(--dsw-specific-menu));
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 10px;
  box-shadow: 0 6px 20px rgba(0,0,0,.28);
}
.addmenu[hidden] { display: none; }
.addmenu button {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: 7px;
  font-size: 13px; color: var(--dsw-alias-label-primary); text-align: left;
}
.addmenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
.addmenu .ic-i { width: 16px; height: 16px; flex: none; color: var(--dsw-alias-label-secondary); }

/* ── 弹窗 ── */
#mask {
  position: fixed; inset: 0; z-index: 50;
  background: var(--dsw-alias-bg-mask-1);
  display: none; align-items: center; justify-content: center;
  padding: 24px;
}
#mask.open { display: flex; }
/* ★★ 弹窗叠层（2026-10-19 主人拍）：点「详情」是压一层上去，不是替换。
   #munder 就是【底下那一层的副本】—— 主人 10-19 二改："遮挡1层，而不是1层消失"。
   所以它整张画出来（内容由 JS 克隆），再盖一层半透明黑 —— 读起来像后面那层退后去了。
   ★ pointer-events:none —— 它只是"看样子"，点击必须落不到它上面。
   ★ 压在 #modal 之下靠【DOM 顺序】（insertBefore），不靠 z-index：
     两个都是 z-index:auto，谁在后面谁在上。 */
#munder {
  position: absolute;
  border-radius: var(--radius-lg);
  background: var(--dsw-alias-bg-layer-2, var(--dsw-specific-menu));
  /* ★ 不要 border（10-19 主人报"有个白边、比较割裂"）：
     ::after 的 inset:0 只盖到 padding box，盖不住 border ——
     那 1px 的 rgba(255,255,255,.12) 会留在最外圈，像一道发亮的描边。
     它是"退到后面去的副本"，本来就不该有边框。 */
  box-shadow: 0 6px 28px rgba(0,0,0,.35);
  overflow: hidden;
  opacity: 0; pointer-events: none;
  transition: opacity .16s ease;
}
#munder.show { opacity: 1; }
/* 克隆出来的那颗盒子（原来是 #modal，id 被剥掉了）—— 撑满 #munder */
#munder > div {
  width: 100%; height: 100%;
  display: flex; flex-direction: column; overflow: hidden;
}
/* ★ 遮挡层：主人说"可以重一点"。压在副本上，让它明显退到后面去。
   backdrop-filter 是加分项（真机上会虚化），不支持的环境退化成纯压暗。 */
#munder::after {
  content: ''; position: absolute; inset: 0;
  background: rgba(0, 0, 0, .58);
  backdrop-filter: blur(1.5px) saturate(.7);
}
#modal {
  /* ★ 主人说"这个弹框有点小气，太窄了" —— 560 → 880，
     而且高度放到 85vh（表格多的时候少滚一点） */
  /* ★ 再调（主人 10-12）：880 → 1100 —— 表格列一多，880 就得横向滚了。 */
  /* ★ 三调（主人 10-19）：1100 → --modal-w(1650) ——「再宽一半」。
     宽度提到 :root 了，跟 #spanel 共用一处。 */
  /* ★★ 四改（10-19 D 方案）：overflow-y:auto → 三段固定。
     滚动交给 .mbody，头尾不动（原来翻到中间时标题和关闭按钮全滚走）。
     ★ 用 max-height 而不是 height：小弹框（"操作失败"那种）不该撑满 85vh；
       内容多时被 max-height 截住，.mbody 的 flex:1 才拿到剩余高度。 */
  width: min(var(--modal-w, 1650px), 100%); max-height: 85vh;
  display: flex; flex-direction: column; overflow: hidden;
  position: relative;         /* ★ 给兜底的浮动 × 定位 */
  background: var(--dsw-alias-bg-layer-2, var(--dsw-specific-menu));
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: var(--radius-lg); padding: 20px;
  box-shadow: 0 12px 40px rgba(0,0,0,.28);
}
#modal h2 { font-size: 15px; font-weight: 600; margin: 0 0 4px; }
#modal .sub { font-size: 12px; color: var(--dsw-alias-label-tertiary); margin-bottom: 16px; }
/* ★ 2026-10-16：这些"里子"（行 / 分段选择器）现在【两个容器都要用】——
     设置弹窗（.sbody）也画同一套外观。所以选择器【并列】一份。
   ★ 为什么不把 #modal 前缀去掉（变成全局规则）：.seg 这个名字【已经撞过一次】
     （10-13：一条全局 .seg 把消息流里的 <details> 撑成了 flex）。
     保持作用域 + 多写一个选择器，比"省几个字"安全。 */
#modal .row, .sbody .row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 0; border-top: 1px solid var(--dsw-alias-border-l1);
}
#modal .row .k, .sbody .row .k { font-size: 13.5px; }
#modal .row .d, .sbody .row .d { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); margin-top: 2px; }
#modal pre {
  background: var(--dsw-alias-markdown-code-block); padding: 12px;
  border-radius: var(--dsw-radius-sm, 8px); font-size: 12px; overflow-x: auto;
}
/* ★ 10-13 修：加 `#modal` 作用域 —— 它一直就是【弹框里】的分段选择器
 *   （设置弹框的主题切换 <div class="seg">，app.js:2136），
 *   原来漏了前缀 → 变成全局 `.seg { display: flex }`，
 *   把消息流里的折叠段（.trace > .tr-body > .seg，是 <details>）也撑成了 flex：
 *   手机上 summary 和详情左右并排、详情撑出横向滚动条。
 *   ★ 同一天同一个文件里两条 `.seg` 撞车 —— 起名/加规则前先 grep。 */
#modal .seg { display: flex; gap: 4px; background: var(--dsw-specific-selector); padding: 3px; border-radius: 9px; }
#modal .seg button { padding: 5px 11px; border-radius: 7px; font-size: 12.5px; color: var(--dsw-alias-label-secondary); }
#modal .seg button.on { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); }
/* ★ 10-16：设置弹窗里也用同一套分段选择器（见上面 .row 那条的说明）。 */
.sbody .seg { display: flex; gap: 4px; background: var(--dsw-specific-selector); padding: 3px; border-radius: 9px; }
.sbody .seg button { padding: 5px 11px; border-radius: 7px; font-size: 12.5px; color: var(--dsw-alias-label-secondary); }
.sbody .seg button.on { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); }

/* ─────────── 设置弹窗（★ 独立容器 —— 2026-10-16）───────────
 *
 * ★★ 为什么不复用 #modal：设置是「左页签 + 右内容」两栏，而 #modal 是
 *   所有弹窗共用的单列容器。要在公共容器上做两栏，就得挂 :has(.set-wrap)
 *   之类的条件变形 —— 那是【隐形定制】：改 #modal 的人不知道有它在等着。
 *   独立 = 零耦合，代价就是下面这几十行。
 *
 * ★ z-index 40 < #mask 的 50 —— 所以设置开着时，普通弹窗能盖在它上面
 *   （设置里的插件页可能开自己的小弹窗），不必特判谁在上面。
 *
 * ★ 滚动：面板本身不滚（overflow:hidden），滚的是【右区】(.sbody)。
 *   于是左列页签钉住、右区独立滚 —— 两栏布局的常规做法。
 *   ⚠️ 要让 flex 子项能滚，链上每一层都得有 min-height:0（或 min-width:0）——
 *   少一个就是"内容撑破但不滚"。
 */
#smask {
  position: fixed; inset: 0; z-index: 40;
  background: var(--dsw-alias-bg-mask-1);
  display: none; align-items: center; justify-content: center;
  padding: 24px;
}
#smask.open { display: flex; }
#spanel {
  /* ★ 宽度跟 #modal 共用 --modal-w（2026-10-19 提到 :root，一处调两个弹框） */
  width: min(var(--modal-w, 1650px), 100%);
  height: min(760px, 85vh);   /* ★ 用 height 不用 max-height：两栏要撑满 */
  display: flex; flex-direction: column;
  background: var(--dsw-alias-bg-layer-2, var(--dsw-specific-menu));
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 40px rgba(0,0,0,.28);
  overflow: hidden;           /* ★ 圆角要裁住里面（不然子元素是方角） */
}
.shead {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1);
}
.shead b { font-size: 15px; font-weight: 600; }
.swrap { flex: 1; display: flex; min-height: 0; }   /* ★ min-height:0 —— 见上面那条 */
.stabs {
  flex: none; width: 180px; padding: 10px 8px;
  border-right: 1px solid var(--dsw-alias-border-l1);
  overflow-y: auto;
}
.stab {
  padding: 7px 10px; border-radius: 8px; font-size: 13px; cursor: pointer;
  color: var(--dsw-alias-label-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stab:hover { background: var(--dsw-alias-bg-layer-1); }
.stab.on {
  background: var(--dsw-alias-bg-layer-1);
  color: var(--dsw-alias-label-primary); font-weight: 600;
}
.sbody { flex: 1; min-width: 0; padding: 16px 18px; overflow-y: auto; }

/* 窄屏：左右改成上下（页签横过来一行）。★ 重心在 PC（主人 10-16 拍的），
   这里只保证"能用"，不追求好看。 */
@media (max-width: 760px) {
  #smask { padding: 12px; }
  #spanel { height: 92vh; }
  .swrap { flex-direction: column; }
  .stabs {
    width: auto; display: flex; gap: 4px; padding: 8px;
    border-right: none; border-bottom: 1px solid var(--dsw-alias-border-l1);
    overflow-x: auto; overflow-y: hidden;
  }
  .stab { flex: none; }
}

/* ─────────── 手机 / 窄屏 ─────────── */

#scrim { display: none; }

@media (max-width: 1024px) {
  /* ★ 两个抽屉都占屏宽 90%（主人 2026-10-16："感觉可以再宽一点，占页面宽度 90% 这样"）。
   *
   *   左侧：覆盖 --sb-w 就行 —— JS 不碰这个变量。
   *   右侧：★ 不能靠变量，见下面 #rsidebar 那条注释（行内样式会盖过这里）。 */
  :root { --sb-w: 90vw; }
  #sidebar {
    position: fixed; z-index: 40; height: 100%;
    /* ★ 2026-10-23：手机上必须跟 dvh，不能停在 100%
     *
     *   桌面：它是 #app 的 flex 子项 → 高度 = #app 的 100dvh（见上面 111-112 行）
     *   手机：这里变成 position:fixed → 【脱离 #app 的 flex 流】
     *         height:100% 在 fixed 下相对视口解析，等于 100vh ——
     *         而移动浏览器的 100vh 是【大视口】（含地址栏 / 底部工具栏），
     *         比眼前看得见的那块高出一截
     *         → 侧栏底部（版本号 + 设置/重启/关闭那排）落到浏览器 UI 底下，
     *           而侧栏自己是 fixed、不出滚动条（只有里面的 .nav 滚），
     *           所以用户既看不到也滚不出来 = 「被版本号那块遮挡」。
     *
     *   实测（视口 429 / 模拟可视 369）：压到可视高后 footer 底 = 369 正好贴底，
     *   .nav 自动从 302 → 241 收缩，内容靠内部滚动 —— 那才是该有的样子。
     *
     *   ★ 这跟上面 #app 那条是同一条判据：凡"占满屏高"的都要 dvh，
     *     而 fixed 元素不会继承父元素的 dvh，得自己写一遍。 */
    transform: translateX(-100%); width: var(--sb-w) !important;
    transition: transform var(--dur) var(--ease);
  }
  #app.sb-open #sidebar { transform: none; }
  /* ★ dvh 覆盖（2026-10-23）—— 跟上面 #sidebar{height:100%} 配对。
   *   写成独立规则而不是嵌在 #sidebar 块里：两者同一特异性、后写的赢，
   *   语义上也更清楚（"有 dvh 就用它"）。 */
  @supports (height: 100dvh) {
    #sidebar { height: 100dvh; }
  }
  /* ★ 手势拖动期间禁掉 transition（2026-10-16）——
   *   不禁的话每帧都在追动画，元素跟不上手指、看着像"拖影"。
   *   JS 在 touchmove 里加 .swiping、松手时摘掉（见 app.js「抽屉手势」那节）。 */
  #sidebar.swiping, #rsidebar.swiping { transition: none; }
  #app.collapsed #sidebar { width: var(--sb-w) !important; }
  #scrim {
    display: block; position: fixed; inset: 0; z-index: 39;
    background: var(--dsw-alias-bg-mask-1); opacity: 0; pointer-events: none;
    transition: opacity var(--dur) var(--ease);
  }
  #app.sb-open #scrim { opacity: 1; pointer-events: auto; }
  /* ── 右侧栏在窄屏改成【覆盖式抽屉】（2026-10-15）──
     宽屏那套是"推"（#main 变窄，见前面的 #rsidebar）—— 窄屏不能推：
     主区本来就没多少地方，再让出 340px 就压没了。
     这里跟左侧栏一个做法：fixed + translateX 滑进来，共用同一个 scrim。
     ★ width 要 !important —— 主规则是 width:0，而
       `#app.rs-open #rsidebar` 特异性更高，不强制的话合上时它会是 0 宽。 */
  #rsidebar {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 40;
    /* ★ 90vw 写死、不读 --rsb-w（主人 2026-10-16）。
     *   为什么不能用变量：rsbRender() 会把 --rsb-w 写成 <html> 的【行内】样式，
     *   行内盖过 :root → 上面那个 --sb-w 那种覆盖在这儿不管用。
     *   ★ 而它画出来的值（窄屏 240px）来自 rsbMax() 里那条"减去左侧栏宽"——
     *     那是【宽屏"推"式布局】的约束（工作区 + 侧栏不能超窗宽）；
     *     窄屏是【覆盖式抽屉】、根本不推东西，所以那条约束在这儿不成立，却还在生效。
     *     ↑ 这才是"手机端感觉有点窄"的真正原因（不是我一开始以为的 86vw）。
     *   → 直接写死，窄屏不读那个变量；宽屏那条主规则一个字没动。 */
    width: 90vw !important;
    transform: translateX(100%);
    transition: transform var(--dur) var(--ease);
    padding-top: var(--safe-t);
  }
  #app.rs-open #rsidebar { transform: none; }
  #app.rs-open #scrim { opacity: 1; pointer-events: auto; }
  /* 拖动接缝只留给宽屏（2026-10-15）—— 窄屏的工作区是【浮在聊天上面】的抽屉，
     "拖接缝调比例"那个动作在这儿没有意义：拖宽了也不会推，只是盖得更多。
     （要调宽度的话，抽屉已经给了 max-width:86vw 的天花板。） */
  .rs-grip { display: none; }
  #menu-btn { display: inline-flex !important; }
}
@media (min-width: 1025px) { #menu-btn { display: none; } }

/* ★★ 2026-10-21 主人："手机版顶栏左侧有两个按钮、功能都是打开侧边栏，优化下"
 *
 * 事实核对（改之前）：
 *   #menu-btn      title="侧栏"        onclick=toggleSidebar()
 *   #collapse-btn  title="收起侧栏"     onclick=toggleSidebar()   ← 同一个函数
 *   而 CSS 只给了 #menu-btn 手机显示规则（上面那两条），#collapse-btn 一条都没有
 *   → 它俩在所有尺寸下【都同时在场】，窄屏就是两个一模一样的按钮。
 *
 * ★ 留哪个：窄屏留 #menu-btn（汉堡 —— 手机上"打开抽屉"的通用符号），
 *   宽屏留 #collapse-btn（panel-left —— 那儿是"收起一栏"，不是抽屉）。
 *   ★ 两个图标不是同一个，这是有意的：动作语义不同（开抽屉 vs 收起栏）。
 *   ★ 判据跟上面那条对齐，都用 1024/1025 这个分界（同一个断点，别引入新的）。
 */
@media (max-width: 1024px) { #collapse-btn { display: none; } }

@media (max-width: 520px) {
  /* ★ 锚点条【手机上也显示 · 完全浮着、一点不占】（主人 2026-10-15）。
   *   沿革（四次）：10-13 藏起来（`--anch-w:0` + `display:none`，主人说"没想好手机怎么做"）
   *   → 10-15 ①：显示 + 占 26px。主人："占了一点宽度，不太好"
   *   → 10-15 ②：占 2px（想避开文字）。主人："还是把输入框/聊天框右边往左压了一点，左右不对称"
   *   → ★ 现在：**`--anch-pad: 0`** —— 一点不占。消息列与输入卡片左右【都是 12px】，对称。
   *
   *   ★★ 遮挡文字不管了（主人原话："你不用考虑遮挡文字这个问题，就浮起来试试"）。
   *      `#anchors` 本来就是 absolute + 浮在消息上层 —— 只有那 16×13 的短横线拦点击，
   *      别处照样能点到文字。（上一版留 2px 就是这个"遮挡"顾虑，主人说不用管。）
   *
   *   ★★ 两个数现在【各管各的】，不再互相依赖：
   *      `--anch-pad: 0`  → 只管"占多少位" → 0 = 不占，三处留白一起归位（#list / #composer / #tobottom）
   *      `--anch-w: 16px` → 只管"短横线离右缘多远 + 点击区多大" → ★ 已经完全不吃布局了
   *   ⚠️ 别只写 `--anch-w: 0` —— 那会把条也一起弄没。
   *   ★ 为什么不把 --anch-w 再缩小：条本身是【透明的】，遮挡只由那条 9px 的线决定；
   *     缩窄只会让点击区更小（它现在 16×13，手机上本来就不大），视觉上几乎没变。
   *
   *   ⚠️ **必须写 `0px` 不能写 `0`** —— 这一条我踩过（10-15，被主人一眼看到"右边顶到边上了"）：
   *      `--anch-pad` 是被塞进 `calc()` 里的（`calc(12px + var(--anch-pad))`），
   *      而 **`calc()` 里裸的 `0` 是非法的**（<number> 不能跟 <length> 相加）→
   *      整条声明判定"计算时无效" → `padding-right` 退回初始值 **0** → 右边真顶到边。
   *      （headless Edge 实测：`calc(12px + 0)` = 0px，`calc(12px + 0px)` = 12px。
   *        失效的还有 #composer 的 padding-right 和 #tobottom 的 right（后者直接变 auto）。）
   *   下面这个 padding 简写会把基规则的 padding-right 一并覆盖（我们就是要覆盖它：
   *   窄屏左边距是 12px 不是 --pad-x）—— 仍用 --anch-pad 写，免得以后它不为 0 时漏掉。 */
  #main { --anch-pad: 0px; --anch-w: 16px; }
  /* 条窄了，锚点按钮跟着窄 —— 20px 会被 #anchors 的 overflow-x 裁掉两边。 */
  .anch { width: 16px; }
  #list { padding: 14px 12px; padding-right: max(0px, 12px + var(--anch-pad) - var(--sbw, 0px)); }
  .m > div { max-width: 88%; }
  .m-atts .attach { width: 92px; height: 92px; }   /* 窄屏缩略图小一号 */
  /* ★ --cm-pad 和 padding 用【同一个】12px：写两份的话，以后改一个忘一个，
   *   又会出现"按钮跟发送框不同列"（主人 10-13 报的那个）。 */
  #composer {
    --cm-pad: 12px;
    padding: 10px var(--cm-pad);
    padding-bottom: calc(10px + var(--safe-b));
    /* ★ 上面那个 padding 简写会把 padding-right 重置掉 —— 这里补回【跟基规则同一个式子】，
     *   免得手机上又出现"卡片跟消息列不等宽"。
     *   ★ 不带 --sbw（10-15 起）—— 滚动条由 #list 自己吃进 padding，卡片不让。 */
    padding-right: calc(var(--cm-pad) + var(--anch-pad));
  }
  #modal { padding: 16px; }
}



/* ─────────── 卡片下半行：状态 · 用量（主人 2026-10-12）───────────
 *
 * 这两条原来在【右上角】（状态条）。主人要把它们收进输入卡片。
 * 两行都可点开看完整的（/status /usage）—— 所以它们是 button，不是 span。
 */
.cm-meta {
  display: flex; align-items: center; gap: 2px;
  /* ★ 左内边距 6px（主人 2026-10-13：「PC 白色距离左边太远了」→ 收到 6px）。
   *
   *   沿革（三次）：7px（跟输入框文字左对齐）→ 51px（.cm-in 里多了 36px 的加号，
   *   文字起点变 58px，要 51+7 才追得上）→ 6px（主人看着太远，收回来）。
   *
   *   ⚠️ 现在它【不再跟输入框文字左对齐】—— 那是主人要的，不是 bug。
   *      对到哪儿了：状态文字起点 = 6 + .cm-stat 自己的 7 = 13px，
   *      而 ＋ 按钮左缘在 14px（.cm-in 的 padding-left）→ 差 1px。
   *      要跟 ＋ 严格对齐，把这个数改成 7 就行（一个数）。
   *   ★ 左右对称：`3px 6px 4px 6px`，跟 #usage 顶右那侧一样是 6px。
   *   ★ 位置跟【亮暗主题无关】—— 全库只有一条 [data-theme] 规则（管消息气泡背景），
   *     所以这一处改完，白色和黑色两边一起变。
   */
  padding: 3px 6px 4px 6px;
  border-top: 1px solid var(--dsw-alias-border-l1);
  min-height: 26px;
}
/* ★ 2026-10-13 收：这里原来有个 `@media (max-width:520px){ .cm-meta{padding-left:7px} }`
 *   —— 它是为"桌面 51px 在窄屏上太挤"打的补丁（主人当天在手机上报过 /status 没顶格）。
 *   现在桌面也收到 6px，两边同值 → 那条 media 查询成了死代码，删掉（少一处要同步的数）。
 */
.cm-stat {
  display: flex; align-items: center; gap: 1px;
  min-width: 0; flex: 0 1 auto;
  padding: 4px 7px; border-radius: 6px;
  font-size: 11.5px; color: var(--dsw-alias-label-caption);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.cm-stat:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); }
.cm-stat i { font-style: normal; opacity: .35; margin: 0 2px; }
.cm-stat .warn { color: var(--dsw-alias-state-warn-label); }
/* ─────────── 状态行那两个【槽位】（2026-10-21）───────────
 *
 * .cm-meta 里不再有写死的按钮，而是两个空容器：
 *   左边 composer.meta.left · 右边 composer.meta.right
 *
 * ★★ 「靠右」是【位置的性质】：.cm-meta-r 有 margin-left:auto，
 *	不是"某个按钮挂了 margin-left:auto"。
 *	原来那条 `#usage { margin-left:auto }` 就是"绑在某一个 id 上"——
 *	换成别的东西进来就得再补一条，而补漏了不会报错（只是不靠右了）。
 *
 * ★ 容器本身是 flex：里面可能有多项（槽位容量 1 + 轮换，见 app.js 的 SLOTS）。
 *	min-width:0 是给将来放长文字的项留的 —— 没有它，flex 子项不肯缩、
 *	撑出去也不报错（这一个月栽过两次，都在别的面板上）。
 */
.cm-meta-l,
.cm-meta-r {
  display: flex; align-items: center; gap: 2px;
  min-width: 0;
}
/* 用量靠右（它跟左边的状态不是一类东西：一个是"我脑子里多满"，一个是"花了多少"） */
.cm-meta-r { margin-left: auto; }

/* 槽位里的一项 —— 宿主给的是 <button class="cm-stat">（见 app.js 的 mountMetaSlot）。
 * ★ 轮换时隐藏的那几项用 [hidden]，它们不占位。
 * ★ 淡入是给"轮换切上来"那一下的：直接换内容会闪，而 150ms 的 opacity
 *	足够遮住，又不至于让人等。
 * ★ 用 animation 而不是 transition：hidden 的切换不给 transition 机会
 *	（display:none ↔ block 之间没有可过渡的中间态）。 */
.cm-stat:not([hidden]) { animation: cm-fadein .15s ease; }
@keyframes cm-fadein { from { opacity: 0 } to { opacity: 1 } }
/* ★★ 必须显式写这一条（10-21 实测踩到）：上面 `.cm-stat { display: flex }` 是
 *	【作者样式】，会压过浏览器默认的 `[hidden] { display: none }`（UA 样式表最弱）
 *	→ `btn.hidden = true` 设了属性、元素照样显示（当时两个指标同时亮着、opacity 都是 1，
 *	  轮换等于没做）。判据：凡是给元素写了 display 的地方，`[hidden]` 都要补回来。 */
.cm-stat[hidden] { display: none; }
.conn { font-size: 12px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; }


/* ─────────── 弹窗：头/内容/尾 三段固定（2026-10-19 主人拍 D 方案）───────────
 *
 * 为什么要重构：原来滚动在 #modal 自己身上（overflow-y:auto），
 *   往下翻 400px 后【标题栏 top=-379、表头 top=-340、底部关闭一起滚走】
 *   → 翻到第 8 个插件时，屏幕上既看不到自己在看什么，也找不到关闭按钮。
 *   11 个弹框共用这一个壳，所以改一处、全部受益。
 *
 * ★ 结构：.mhead（固定）× / .mbody（唯一滚动）/ .mfoot（可选，固定）
 */

.mhead {
  /* ★ 头部要能放右上角的 × */
  display: flex; align-items: flex-start; gap: 12px;
  flex: none;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--dsw-alias-border-l1);
  margin-bottom: 0;          /* 原来是 14px —— 现在由 .mbody 的 padding 提供 */
}
.mhead > :first-child { flex: 1; min-width: 0; }   /* 标题块占满，× 靠右 */
.mhead h2 { font-size: 16px; font-weight: 600; margin: 0 0 3px; }
.mhead .sub { font-size: 12px; color: var(--dsw-alias-label-tertiary); word-break: break-all; }
/* 头部常驻关闭 —— 有了它，底栏那条纯「关闭」就冗余了。
   ★★ 类名不能叫 `mclose` —— 那是 KaTeX 输出里【数学右闭合符】的类
      （⟨m₁…⟩ 的 ⟩、{user} 的 }、(P) 的 )），KaTeX 的 DOM 里就带着它。
      我的规则会命中它：display:flex + 28×28 + 圆角 → 每个右括号被撑成方块。
      实测过（10-23）：改名前 `.katex .mclose` 的 computed width=27.99px。
      改名后 KaTeX 输出的 35 个类名与我方 536 个类名的交集 = {katex}（那个是故意的、有作用域）。 */
.mx-close {
  flex: none; width: 28px; height: 28px; padding: 0; border: none; border-radius: 7px;
  background: transparent; color: var(--dsw-alias-label-tertiary); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .12s, color .12s;
}
.mx-close:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
.mx-close .ico { width: 15px; height: 15px; }
/* 兜底位置：调用方没给 .mhead 时，× 浮在弹框右上角（不占布局） */
.mx-close.floating { position: absolute; top: 14px; right: 14px; z-index: 3; }

/* 内容区 —— 唯一会滚的那一层 */
.mbody { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 2px 4px 0; }

/* ★ 表头吸顶。
   ★★ 坑有两层（实测三次才对）：
   ① .tw 是横向滚动容器（style.css:1681 overflow-x:auto + overflow-y:hidden）
      → 它【自己就是滚动盒】→ 里面的 sticky 只相对它，而它纵向不滚 → 永远粘不住。
      只把 overflow-y 改成 clip 没用：x 轴还在 auto，它照样是滚动盒。
   ② border-collapse:collapse 不是原因（单独改过，delta 不变）。
   ③ 解法：让 .tw 自己承担纵向滚动。
      ★ 但 height:100% 不行 —— .mbody 的高度是 flex 算出来的（height 属性还是 auto），
        百分比高度会回退成 auto（实测：.tw 拿到 1201px = 内容高度，没被限制）。
      ★ 正解：.mbody 变 flex column，.tw 用 flex:1 + min-height:0 拿到确定高度。
        .mbody 自己已有确定高度（父 flex + min-height:0），所以这条路走得通。
      :has 让【非表格页不受影响】。 */
.mbody:has(> .tw) { overflow: hidden; display: flex; flex-direction: column; }
.mbody:has(> .tw) > .tw { flex: 1; min-height: 0; overflow: auto; }
.mbody .dt thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--dsw-alias-markdown-code-block-banner);   /* ★ 必须不透明，否则行透上来 */
  /* ★ 补住下边缘的 1px 缝：border-collapse 下 th 的底边框归属有歧义，
     下面那行的文字上沿会从缝里露出来（截图里看到的「半截字」就是这么来的）。
     top 也从 -1px 改成 0 —— 负值会让表头整体上移，反而多露 1px。 */
  box-shadow: 0 1px 0 var(--dsw-alias-markdown-code-block-banner);
}

/* 底栏【可选】—— 只有调用方真的给了 .mfoot 才存在（如确认框的 取消/确认） */
.mfoot { display: flex; justify-content: flex-end; flex: none;
  margin-top: 14px;
  padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1);
  gap: 8px; }   /* ★ 2026-10-14：取消 + 确认两个按钮。单个按钮时 gap 无害。 */

/* ── 二次确认：影响说明（2026-10-14，侧栏的重启 / 退出）──
 * 弹框的主题：不是"你确定吗？"，是【说清点了会发生什么】。
 * 所以一条一句、每句都是"会发生的事实"，最后那条不可逆的要显眼。 */
.pwrlead { font-size: 13.5px; color: var(--dsw-alias-label-primary); margin-bottom: 10px; }
/* ★ 2026-10-22：guard 关着时那句警示（"这个按钮不会重启，只会停"）。
 *   用 error 色而不是 warn 色 —— 它说的是「点下去就起不来了」，不是"注意一下"。
 *   ★ 这一条是【事实纠正】，不是危险提示：按钮本来叫"重启"，而它实际会停。 */
.pwrlead.warn { color: var(--dsw-alias-state-error-primary); font-weight: 500; }
/* 补充说明（为什么判不准 / 这一项要重启才生效）—— 比正文小一档、灰一档 */
.pwrhint {
  font-size: 12px; color: var(--dsw-alias-label-tertiary);
  margin: -6px 0 10px 0; line-height: 1.5;
}
/* ★ 2026-10-22：侧栏那个「重启」按钮，guard.enable=false 时给一点琥珀色。
 *   ★ 用 warn 色不是 error 色 —— 它常驻侧栏，红着会像"系统出故障了"；
 *     而实际的意思是"这个按钮名不副实"，属于【提醒】级别。
 *   ★ 用 #btn-restart 限定：`.item` 是全站通用类（插件/配置/工具…都在用），
 *     直接写 .item.no-guard 会波及别处。 */
#btn-restart.no-guard { color: var(--dsw-alias-state-warn-label); }
#btn-restart.no-guard .ic { color: var(--dsw-alias-state-warn-label); }

/* toast —— 角落里一句话，说完自己消失（2026-10-22，配合重启回来的提示）
 *
 * ★ 位置在【顶栏下方】而不是顶栏里：右上角是「工作区」按钮，压上去就挡住它了。
 *   top 用 52px（#topbar 的高度）+ safe-t + 12px 间距。
 * ★ pointer-events: none —— 它不该拦任何点击。这一条是判据：
 *   一条"不用人管"的提示，如果自己能吃掉一次点击，那就违背了它的定位。
 *   （将来真要有"撤销"这种可点的 toast，得单独给它一个类把这条打开。）
 * ★ 不加 display:none 隐藏 —— 用 opacity + transform 做淡入，元素一直在 DOM 里，
 *   连着来第二条时只改文字，不重建节点（重建会把过渡动画掐掉）。 */
.toast {
  position: fixed;
  /* 右缘对齐顶栏「已连接」的右缘：12px 边距 + 34px 工作区按钮 + 6px gap = 52px */
  right: 52px;
  top: calc(52px + var(--safe-t, 0px) + 12px);
  z-index: 90;                          /* > #mask(50) / 看图(60) / 菜单(70) */
  display: flex; align-items: center; gap: 7px;
  max-width: min(360px, calc(100vw - 32px));
  padding: 9px 14px; border-radius: 9px;
  font-size: 13px; line-height: 1.4;
  background: var(--dsw-alias-bg-layer-2);
  color: var(--dsw-alias-label-primary);
  border: 1px solid var(--dsw-alias-border-l2);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .26);
  opacity: 0; transform: translateY(-8px);
  transition: opacity .2s ease, transform .25s ease;
  pointer-events: none;
}
.toast.on { opacity: 1; transform: none; }
.toast .ic { width: 14px; height: 14px; flex: none; }
.toast.ok .ic { color: var(--dsw-alias-state-success-primary); }
/* 工作区打开时整体左移，让开它占的那一栏（--rsb-w 随拖拽变化）。
   ★ toast 是 BODY 的直接子元素、排在 #app 之后，所以 ~ 选得到。
   ★ 手机（≤1024）上工作区是【覆盖式抽屉】（fixed + translateX），不是挤窄主区，
     挪多少都躲不开，所以那边保持原位 —— 见文末窄屏段。 */
#app.rs-open ~ .toast { right: calc(52px + var(--rsb-w)); }
@media (max-width: 1024px) {
  /* 手机上顶栏更挤，回到贴边 16px；抽屉开着时不挪（挪了也没地方放） */
  .toast, #app.rs-open ~ .toast { right: 16px; }
}
.pwrl { list-style: none; margin: 0; padding: 0; }
.pwrl li {
  display: flex; gap: 7px; padding: 5px 0;
  font-size: 13px; line-height: 1.55; color: var(--dsw-alias-label-secondary);
}
.pwrl li::before { content: "·"; flex: none; color: var(--dsw-alias-label-caption); }
.pwrl li.hot { color: var(--dsw-alias-state-error-primary); }
.pwrl li.hot::before { color: var(--dsw-alias-state-error-primary); }

.sec { font-size: 13px; font-weight: 600; margin: 16px 0 7px;
  color: var(--dsw-alias-label-secondary); }
.sec:first-child { margin-top: 0; }

/* 键值表：名字在左、值在右（比一串 JSON 好读太多） */
.kv {
  display: grid; grid-template-columns: minmax(120px, max-content) 1fr;
  gap: 1px 16px; font-size: 13px;
}
.kvk { color: var(--dsw-alias-label-tertiary); padding: 4px 0; }
.kvv { color: var(--dsw-alias-label-primary); padding: 4px 0; word-break: break-all; }

/* ── 配置面板（2026-10-13，只读版）──
 * ★ 类名都带 cfg 前缀：这文件里通用名撞过车（`.seg` 同时是"折叠段"和"弹框主题切换"，
 *   害主人报了两轮"手机上展开变左右布局"）—— 新类名一律带自己的前缀。 */
.cfgbar { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.cfgsrch {
  flex: 1; min-width: 0; padding: 7px 10px;
  border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px;
  background: var(--dsw-alias-bg-layer-1, transparent);
  color: var(--dsw-alias-label-primary); font-size: 13px; outline: none;
}
.cfgsrch:focus { border-color: var(--dsw-static-deepseek-500); }
.cfgsum { font-size: 12px; color: var(--dsw-alias-label-secondary); white-space: nowrap; }
.cfgsum b { color: var(--dsw-alias-label-primary); }

/* 分组标题（agent / gateway / plugins …） */
.gsec {
  display: flex; align-items: baseline; gap: 8px;
  margin: 16px 0 6px; font-size: 12px; font-weight: 600;
  color: var(--dsw-alias-label-secondary);
}
.gsec:first-child { margin-top: 4px; }
.gsec .n { font-weight: 400; color: var(--dsw-alias-label-tertiary); }
/* ★ 组头右边那句指路（2026-10-15）：plugins 组的启用/停用收口到插件面板。
 *   用普通字重 + 更淡的颜色 —— 它是说明，不是标题的一部分，
 *   太显眼会跟组名抢。margin-left:auto 把它推到行尾，跟「N 项」拉开。 */
.gsec .cfgnote {
  margin-left: auto; font-weight: 400; font-size: 12px;
  color: var(--dsw-alias-label-tertiary);
}

/* ── 配置表（2026-10-15 五列版 · 10-19 调整列宽）──
 *   配置项 | 默认值 | yaml | db 值 | 生效方式
 * ★ 老的 .srcb（来源徽章）和 .cfgdots 随重设计一起删了 —— JS 里已零引用。
 *
 * ★★ 固定列宽（主人 2026-10-15）："配置项列长一点、默认值/yaml/db 等宽、
 *    生效方式保持现状"。
 *    靠 table-layout: fixed —— 列宽由【表头】定，不随每行的内容跳。
 *    （不固定的话每行按自己内容算，同一张表在不同分组下长得都不一样。）
 *
 * ★★ 2026-10-19 主人：「第 1 列太宽了」。
 *    病根：原先 2/3/4 写死 150px、第 1 列不写宽度 = 【吃掉剩下的全部】。
 *    1650 弹框里那等于 746px —— key 只有 ~200px 宽，剩下 500px 全是空白。
 *    → 改成【按比例分】，第 1 列 42% 够放下最长的 key
 *      （plugins.adapter-web.password = 28 字符 ≈ 200px），
 *      多出来的宽度不再全塞给它。
 */
.dt.cfgt { table-layout: fixed; }
.dt.cfgt th:nth-child(1) { width: 42%; }
.dt.cfgt th:nth-child(2),
.dt.cfgt th:nth-child(3) { width: 16%; }     /* 默认值 / yaml —— 等宽，好横向对照 */
.dt.cfgt th:nth-child(4) { width: 16%; }     /* db 值：要放输入框 + 保存 */
.dt.cfgt th:nth-child(5) { width: 10%; }     /* 生效方式：两个字 */

/* ★ 长的不换行：截断 + 悬停看全（主人 10-15）。
 *   值那几格走 JS 的 trunc()（自带 title=全文），这儿只把它的 max-width 放开 ——
 *   原来 .dt td .trunc 限 19em（~237px），列宽已经定了就不需要那个限。 */
.dt.cfgt td { overflow: hidden; }
.dt.cfgt .trunc { max-width: 100%; }
.dt.cfgt .tid {                              /* 配置项名（key）同样截断 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ★★ 说明也收敛成【一行】（2026-10-19）——
 *   原来它没有 nowrap，第 1 列一宽（旧版 746px）它就换行，
 *   同一张表里有的行 1 行、有的 3 行，疏密不齐。
 *   全文照旧在 DOM 里，悬停看（.tdesc 由 JS 的 trunc 生成，自带 title）。 */
.dt.cfgt .tdesc {
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ★ 窄屏（≤1024）另一套比例（2026-10-19）——
 *   宽屏那套在窄屏下会把 db 格压到 ~107px（输入框只剩 50 出头，放不下字）。
 *   窄屏改为【把第 1 列让出来的宽度分给后四列】：
 *     674 内容区下 → 216 / 135 / 135 / 135 / 54
 *   最长的 key（plugins.adapter-web.password ≈ 200px）仍能完整显示。 */
@media (max-width: 1024px) {
  .dt.cfgt th:nth-child(1) { width: 32%; }
  .dt.cfgt th:nth-child(2),
  .dt.cfgt th:nth-child(3),
  .dt.cfgt th:nth-child(4) { width: 20%; }
  .dt.cfgt th:nth-child(5) { width: 8%; }
}
/* db 格：列宽已定，输入框跟着列走（不再用固定 220px）*/
.dt.cfgt .cfgin { max-width: 100%; }
.dt.cfgt .cfgedit { flex-wrap: wrap; }       /* ★ 兜底：窄到放不下时按钮掉下一行，别溢出 */

/* ★★ 手机档（≤760）：定宽 + 横滚（2026-10-23 主人）。
 *
 * 问题：上面那套百分比只是【重新分配】宽度，表格仍然缩到容器宽 ——
 *   366px 容器下 db 格只剩 64px → 输入框 40px（看得到 4 个字符）→
 *   保存按钮被 flex-wrap 挤到第二行 → 那一行高 75，别的行 58。
 *   （主人原话：「db 值的输入框和保存按钮太窄还导致换行了」。）
 *
 * 解法：给表格 min-width，让它比容器宽 → .tw 的 overflow-x 终于有事可滚。
 *   列宽改成 px（不再按比例），因为「哪几列必须完整」是固定的：
 *     配置项名  auto   最长 plugins.adapter-web.password（28 字符 ≈ 192px）
 *     默认值/96 · yaml/96   这俩是等宽短值
 *     db 值  148     输入框 + 保存按钮要同一行
 *     生效方式 64     两个字
 *   620 是量出来的下限：再小，最长的 key 会被截断（而 key 截断了就不知道在改啥）。
 *
 * ★ 代价（知情的选择）：手机上要看全一行得左右滚。
 *   比"挤到换行、每行 75px 高、一屏只看得到 1 行"强。
 * ★ 为什么不用容器查询：那要给 .mbody 加 container-type，而 .mbody 同时是
 *   sticky 表头的滚动容器 —— contain 会不会破坏 sticky 我没验过，不值得为
 *   这件事冒风险。所以它看【视口】：PC 上把右栏拖得很窄【不会】触发这一档
 *   （现有架构限制，知悉；PC 上「配置」是整块弹窗，本来就够宽）。
 */
@media (max-width: 760px) {
  .dt.cfgt { min-width: 620px; }
  .dt.cfgt th:nth-child(1) { width: auto; }
  .dt.cfgt th:nth-child(2),
  .dt.cfgt th:nth-child(3) { width: 96px; }
  .dt.cfgt th:nth-child(4) { width: 148px; }
  .dt.cfgt th:nth-child(5) { width: 64px; }
  /* 定宽表里不需要"掉下一行"这个兜底了 —— 现在宽度够，硬让它 wrap 反而错位 */
  .dt.cfgt .cfgedit { flex-wrap: nowrap; }
}

/* ★ 哪一层【在生效】，那一格加亮（代替老的"来源"徽章）。
 *   默认色 + 半粗就够：别的格子是 tertiary 那种灰，一对比就分得出来。 */
.cfgwin { color: var(--dsw-alias-label-primary); font-weight: 600; }

/* ★ db 那格在生效时也高亮 —— 但里头是输入框，不能像另两格那样改字色，
 *   所以改【边框】：一眼看出"这层有值，而且是它在生效"。 */
.cfgedit.cfgwin .cfgin { border-color: var(--dsw-alias-label-secondary); }

/* ★★ 「哪一层有值」= 颜色（2026-10-19 主人）。
 *
 *   yaml 格有值 → 蓝（跟发送按钮/下载角标同一个 --dsw-static-deepseek-500）
 *   db   格有值 → 橙边（跟"被覆盖"那行同族 #e0a33a）
 *
 * ★ 跟上面 .cfgwin（在生效 = 加粗）是【两个信号】，可以同时成立：
 *     middle_size  yaml 0.1（蓝）被 db 0.3（橙）盖住 → 两个色都在，正是覆盖链。
 *   ★ 为什么要分开：只看加粗不知道"改哪层有用"，只看颜色不知道"现在是谁在生效"。
 *
 * ★ 顺序要紧：这两条必须在 .cfgwin 之后 —— 同特异性靠后者覆盖，
 *   否则 yaml 有值又在生效时，颜色会被 .cfgwin 的 primary 抢走。
 */
.dt.cfgt .cfgy { color: var(--dsw-static-deepseek-500); }
.dt.cfgt .cfgy .trunc { color: inherit; }
.dt.cfgt .cfgedit.cfgdb .cfgin {
  border-color: #e0a33a;
  box-shadow: 0 0 0 1px rgba(224, 163, 58, .20);
}

/* 不能改的那格（凭证 / 数组）—— 说清为什么 + 该去哪改 */
.cfgno { font-size: 12px; color: var(--dsw-alias-label-tertiary); }

/* 生效方式：它是【信息】不是按钮（说清实时还是重启） */
.cfghow { font-size: 12px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; }

/* db 值那格：输入框 + 保存 */
.cfgedit { display: flex; gap: 6px; align-items: center; }
.cfgin {
  flex: 1; min-width: 0;                       /* min-width:0 —— flex 子项默认不肯窄过内容 */
  max-width: 220px;                            /* ★ 别拉满整列（主人 10-15："现在的输入框好长啊"）*/
  padding: 3px 7px; border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 6px; font-size: 12.5px; font-family: inherit;
  background: var(--dsw-alias-bg-layer-1, transparent);
  color: var(--dsw-alias-label-primary); outline: none;
}
.cfgin:focus { border-color: var(--dsw-static-deepseek-500); }

/* ★★ 2026-10-22：配置项有 `options` 时画【下拉框】（后端 371fd1d 打通的枚举）。
 *
 *    .cfgin 那套（padding / border / 圆角 / 字号）直接继承，这里只补 select 特有的三样：
 *      ① 去掉原生外观 + 自绘箭头（原生箭头在暗色主题下是系统灰，还占宽）
 *      ② option 的底色 —— <option> 能设的样式有限（原生控件），
 *         但 background / color / font 是生效的；不给的话暗色下会掉回白底
 *      ③ cursor:pointer（下拉是可以点的，输入框不用这个提示）
 *
 *    ★ 箭头用两道 linear-gradient 拼（不是 data URI）—— data URI 里的引号
 *      在模板字符串和 CSS 两处都要转义，是自找麻烦。
 *    ★ 颜色全走变量：亮暗两套自动跟着变，不用各写一遍。
 */
.cfgin-sel {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: 20px;                          /* 给箭头留位 */
  cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-tertiary, #8a8f96) 50%),
    linear-gradient(135deg, var(--dsw-alias-label-tertiary, #8a8f96) 50%, transparent 50%);
  background-position: calc(100% - 11px) 51%, calc(100% - 7px) 51%;
  background-size: 4.5px 4.5px, 4.5px 4.5px;
  background-repeat: no-repeat;
}
.cfgin-sel option {
  background: var(--dsw-alias-bg-layer-1, #2a2a2c);
  color: var(--dsw-alias-label-primary, #e8eaed);
  font-family: inherit; font-size: 12.5px;
  padding: 6px 8px;                              /* Windows 上只有左右生效，无害 */
}
html[data-theme="light"] .cfgin-sel option { background: #fff; color: #0f1115; }
.cfgmsg { font-size: 11px; margin-top: 2px; color: var(--dsw-alias-label-tertiary); }
.cfgmsg.err { color: var(--dsw-static-red-400); }
.cfgmsg.ok { color: var(--dsw-alias-state-success-primary, #4caf50); }

/* 数据表 */
.tw { overflow-x: auto; border: 1px solid var(--dsw-alias-border-l1);
  border-radius: var(--dsw-radius-sm, 8px); }
.dt { border-collapse: collapse; width: 100%; font-size: 13px; }

/* ─────────── 长文本截断（主人 2026-10-12）───────────
 *
 * 「现在涉及长文本的比较丑，比如 prompt，能不能做成默认就一行、
 *   指上去用 prop 显示完整内容捏」
 *
 * 做法：全文照放 DOM，由 CSS 截成一行；悬停用原生 title 提示。
 * ★ 为什么不先截字符串：截了之后 hover 也没全文了。
 * ★ 为什么不用 JS 做 tooltip：原生 title 零代码、零维护，
 *   而且各平台行为一致（移动端长按也能看）。
 */
.trunc {
  display: inline-block; max-width: 24em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom;
}
/* 表格里更紧一点（一列太宽会把其他列挤扁） */
.dt td .trunc { max-width: 19em; }

/* 任务列的主次两行：id 是主角，desc 是它的说明 */
.tid { font-weight: 500; }
.sti { width: 14px; height: 14px; vertical-align: -2px; margin-right: 4px; opacity: .9; }
.sti.ok  { color: var(--dsw-alias-state-success-primary, #22c55e); }
.sti.warn{ color: var(--dsw-alias-state-warn-primary, #f59e0b); }
.sti.bad { color: var(--dsw-alias-state-error-primary, #ec1313); }
.sti.dim { opacity: .45; }
/* muted —— "这个不用点了"的次要说明（如 已打开 / 已暂停）。跟 .sti.dim 一个色。 */
.muted { opacity: .45; }

/* ★ 未显示的界面扩展提示（app.js · unsupportedNotice）——
   比通用 .muted 亮一点：它是【要被人看见的提醒】，不是禁用态。 */
.item.muted { opacity: .75; }
/* "启动中"的箭头转起来 —— 静态图标看不出"正在进行"（10-12） */
.sti.spin { animation: stspin 1.2s linear infinite; }
@keyframes stspin { to { transform: rotate(360deg); } }
.stt { font-size: 12.5px; }

/* 用量的合计行：一行、放最下面、标粗（主人 10-12） */
/* 表格的合计行（tfoot）—— 主人 10-12：「total 做成表格的一行、放最下面标粗」 */
.dt tfoot td {
  font-weight: 600;
  color: var(--dsw-alias-label-primary);
  border-top: 2px solid var(--dsw-alias-border-l2);
  background: var(--dsw-alias-bg-layer-1, transparent);
}

.tdesc { font-size: 11.5px; color: var(--dsw-alias-label-caption); margin-top: 2px; }
/* 相对时间（"+18h"）用小字跟在绝对时间后面 —— 一个给"什么时候"，一个给"还多久" */
.rel { font-size: 11.5px; color: var(--dsw-alias-label-caption); margin-left: 3px; }
/* 操作列里两个按钮之间留点空 */
.dt td .tbtn + .tbtn { margin-left: 6px; }

/* ─────────── 表格里的操作按钮（面板专属渲染用）───────────
   ★ 它是"面板才知道的交互"的落点：cron 的停用/启用、
     skill 的删除、sub 的派活 —— 都会长这样。 */
.tbtn {
  padding: 2px 10px; border-radius: 6px; font-size: 12px; line-height: 1.7;
  border: 1px solid var(--dsw-alias-border-l2);
  color: var(--dsw-alias-label-secondary); background: transparent;
  white-space: nowrap; cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tbtn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
/* 主操作（比如"启用"—— 那是把它打开，是个正向动作） */
.tbtn.on { color: var(--dsw-static-deepseek-500); border-color: currentColor; }
.tbtn:active { transform: translateY(1px); }

/* ★ 禁用态（配置面板的「保存」：内容没改就不给点）。
 *   除了变淡，还要【去掉 hover 和 active】—— 一个"看着能点、点下去没反应"的按钮，
 *   正是这个面板最该避免的东西（它本来就是被"假按钮"坑出来的）。 */
.tbtn:disabled { opacity: .42; cursor: default; }
.tbtn:disabled:hover { background: transparent; color: var(--dsw-alias-label-secondary); }
.tbtn:disabled:active { transform: none; }
.dt th, .dt td { padding: 8px 12px; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--dsw-alias-border-l1); }
.dt th { background: var(--dsw-alias-markdown-code-block-banner); font-weight: 600;
  font-size: 12px; color: var(--dsw-alias-label-secondary); white-space: nowrap; }
.dt tr:last-child td { border-bottom: 0; }
.dt td .d { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); margin-top: 2px; }
.dt td.r { text-align: right; white-space: nowrap; }

.empty { padding: 20px; text-align: center; font-size: 13px;
  color: var(--dsw-alias-label-tertiary); }

/* ─────────── 三段式列表（row / list / listGroup）───────────
   ★ 2026-10-19 主人拍板（C 方案）：插件 / MCP / 技能 / 子agent / 工具
     五个面板的表格 → 这一套。规则住在 sdk/uiext/components.js 的 row()。

   ★ 为什么不是表格：表格的列宽由【内容】决定，列一多就挤，长文本只能截断，
     而"说明"是最该给一整行的东西（它才是"这条是干嘛的"）。
     行是每行自己排版：名字 + 标签一行、说明一行、右侧状态和动作。

   ★ 三段 = 锚点(li-a) · 状态(li-st) · 动作(li-act)。
     状态和动作【并排常显】—— 不做 hover 才出现：那样格子里平时是空的，
     宽屏上会留出一条"空走廊"（2026-10-19 样机实测出来的）。 */
.lr { display: flex; flex-direction: column; gap: 1px; }

.li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 16px;
  padding: 13px 16px; border-radius: 10px;
  border: 1px solid transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.li:hover {
  background: var(--dsw-alias-interactive-bg-hover);
  border-color: var(--dsw-alias-border-l1);
}
/* 整行可点（工具 / 命令）：光标变手指 + hover 时名字变蓝，让人知道"这行能进" */
.li-click { cursor: pointer; }
.li-click:hover .li-n { color: var(--dsw-static-deepseek-500); }
/* 搜索命中的那几段（回答"我搜 file 怎么会搜到 memory_add"） */
.li-n .hl, .li-d .hl {
  font-weight: 600; color: var(--dsw-static-deepseek-500);
  background: color-mix(in srgb, var(--dsw-static-deepseek-500) 14%, transparent);
  border-radius: 3px; padding: 0 1px;
}
.li-a { min-width: 0; }
.li-t { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.li-n {
  font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 名字后面的小灰字：内置 / 外部 / 76 工具 —— 分类和量级，不该占一列 */
.li-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; }
/* 带色调的标签（工具面板的风险等级 —— 一眼看出哪些会动东西）
   ★ 只标、不拦（跟 model.ToolKind 那条一致） */
.li-tag.tag-read { color: var(--dsw-alias-label-caption); }
.li-tag.tag-write { color: #d99a2b; }
.li-tag.tag-exec { color: #d9612b; }
/* 「● 未读」—— 事件面板里还没告知过的那些 */
.li-tag.tag-new { color: #d9612b; }
/* 第二行说明 —— 单行省略，全文挂 title。这是这一行里信息量最大的东西 */
.li-d {
  margin-top: 3px; font-size: 12px; line-height: 1.5;
  color: var(--dsw-alias-label-tertiary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.li-end { display: flex; align-items: center; gap: 14px; }
.li-st { display: flex; align-items: center; gap: 5px; }
.li-st .stt { font-size: 11.5px; }
.li-act { display: flex; gap: 6px; }
.li-act .tbtn { font-size: 11.5px; padding: 4px 11px; }

/* 分组（listGroup）：组头 = 名字 + 数量，跟配置文件页左栏同一套语言 */
.lg + .lg { margin-top: 14px; }
.lg-h {
  display: flex; align-items: baseline; gap: 7px;
  padding: 2px 6px 7px; font-size: 12px;
  color: var(--dsw-alias-label-secondary);
}
.lg-h b { font-weight: 600; }
.lg-n {
  font-size: 11px; color: var(--dsw-alias-label-tertiary);
  background: var(--dsw-alias-markdown-code-block);
  padding: 1px 6px; border-radius: 20px;
}

/* 手机上把说明收掉、按钮缩一圈 —— 一行只留"名字 + 状态 + 一个动作" */
@media (max-width: 1024px) {
  .li { padding: 10px 12px; gap: 10px; }
  .li-d { display: none; }
  .li-end { gap: 8px; }
  .li-act .tbtn { padding: 3px 8px; }
}

/* 长文本（比如 /skill 现在只给 text） */
.md-pre {
  margin: 0; padding: 12px 14px; border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-alias-markdown-code-block);
  /* ★ 2026-10-23：原来是 var(--dsw-font-family, monospace) —— 名字【存在】但值是【正文字体】，
     所以 fallback 永不触发、代码块一直不是等宽，而且不报错。真正的等宽是 --ds-font-family-code。 */
  font-family: var(--ds-font-family-code, monospace); font-size: 12.5px;
  line-height: 1.65; overflow-x: auto; white-space: pre-wrap; word-break: break-word;
  color: var(--dsw-alias-label-primary);
}

/* ★ .pill（状态小标签）已于 2026-10-12 删除 —— 它只服务 MCP 面板，
   而 MCP 现在跟插件一样走 stateCell（图标 + 文字）；JS 侧 STATE_CN/STATE_CLS/
   statePill 也一起删了。留着的死 CSS 会让人以为"还有另一条路"。 */

.warn { color: var(--dsw-static-red-400); }

/* 深嵌套折叠 */
.deep { margin: 8px 0; }
.deep > summary {
  cursor: pointer; font-size: 13px; padding: 6px 0;
  color: var(--dsw-alias-label-secondary);
}
.deep > summary:hover { color: var(--dsw-alias-label-primary); }

/* ─────────── 气泡里的 Markdown ─────────── */
/* （渲染器在 markdown.js —— 它先把整段转义再做标记替换，
    所以这里只需管"长什么样"。） */

.b.md > *:first-child { margin-top: 0; }
.b.md > *:last-child  { margin-bottom: 0; }
.b.md p { margin: 0 0 8px; }
.b.md .md-h {
  font-size: 14.5px; font-weight: 600; margin: 12px 0 6px;
  padding-bottom: 3px; border-bottom: 1px solid var(--dsw-alias-border-l1);
}
.b.md h1.md-h { font-size: 16px; }
.b.md h2.md-h { font-size: 15px; }

.b.md ul, .b.md ol { margin: 0 0 8px; padding-left: 20px; }
.b.md li { margin: 2px 0; }

.b.md code {
  font-family: var(--ds-font-family-code, monospace);
  font-size: 12.5px; padding: 1.5px 5px; border-radius: 5px;
  background: var(--dsw-alias-markdown-inline-code);
}
.b.md .md-code {
  margin: 8px 0; padding: 10px 12px; border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-alias-markdown-code-block);
  font-family: var(--ds-font-family-code, monospace);
  font-size: 12.5px; line-height: 1.5; overflow-x: auto; white-space: pre;
  position: relative;
}
/* ── 代码块「复制」按钮（markdown.js 生成 .md-codewrap 包一层）──────────
 *  ★ 布局：代码区在上面、工具条在下面，两者拼成一个整体。
 *    为什么不浮在右上角：那要给 pre 留 74px 右内边距（挤掉横向空间），
 *    而且长行代码会从按钮底下穿过去。
 *  ★ 圆角：外框 .md-codewrap 一个 radius + overflow:hidden 裁出来，
 *    两块自己都不写 radius —— 拼缝处才不会出现直角或双圆角。
 *  ★ padding 要盖过 `.b.md .md-code`（3 个 class）→ 这里写 4 个。
 */
.md-codewrap {
  margin: 8px 0;
  border-radius: var(--dsw-radius-sm, 8px);
  overflow: hidden;                    /* 裁出圆角 + 挡住工具条的方角 */
  background: var(--dsw-alias-markdown-code-block, #f6f7f9);
}
.b.md .md-codewrap > .md-code,
.tr-v.md .md-codewrap > .md-code {
  margin: 0;                           /* 边距交给 wrap */
  border-radius: 0;                    /* 圆角交给 wrap */
  background: transparent;             /* 底色交给 wrap */
}

.md-codefoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  padding: 3px 6px 3px 12px;
  background: var(--dsw-alias-markdown-code-block-banner, rgba(127, 127, 127, .09));
}
.md-codefoot > .md-lang {
  font-size: 10.5px;
  color: var(--dsw-alias-label-caption, #898e94);
  text-transform: uppercase;
  letter-spacing: .03em;
}

.md-codefoot > .md-copy {
  font-family: inherit; font-size: 11px; line-height: 1;
  padding: 3px 8px; border-radius: 5px; cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  color: var(--dsw-alias-label-secondary, #61666b);
  transition: background .12s, color .12s, border-color .12s;
}
.md-codefoot > .md-copy:hover {
  background: var(--dsw-alias-bg-layer-1, #fff);
  border-color: var(--dsw-alias-border-l2, #e3e5e8);
}
.md-codefoot > .md-copy.ok {
  color: var(--dsw-alias-state-success-label, #16a35c);
  border-color: currentColor;
  background: transparent;
}
.md-codefoot > .md-copy.err {
  color: var(--dsw-alias-state-error-label, #d92b2b);
  border-color: currentColor;
  background: transparent;
}

/* 旧的右上角语言标记：lang 已经挪进工具条，这条不再生成 —— 留着说明来历，
 * 别的 .md-code 里若还有 .md-lang（旧渲染的存量 DOM）仍然能看。 */
.b.md .md-code .md-lang {
  position: absolute; top: 6px; right: 10px; font-size: 10.5px;
  color: var(--dsw-alias-label-caption); text-transform: uppercase;
}
.b.md blockquote {
  margin: 8px 0; padding: 4px 0 4px 11px;
  border-left: 3px solid var(--dsw-alias-border-l3);
  color: var(--dsw-alias-label-secondary);
}
.b.md hr { border: 0; border-top: 1px solid var(--dsw-alias-border-l2); margin: 12px 0; }
.b.md a { color: var(--dsw-alias-link); text-decoration: none; }
.b.md a:hover { text-decoration: underline; }
.b.md .md-table {
  border-collapse: collapse; margin: 8px 0; font-size: 12.5px;
  display: block; overflow-x: auto; max-width: 100%;
}
.b.md .md-table th, .b.md .md-table td {
  border: 1px solid var(--dsw-alias-border-l2); padding: 5px 9px; text-align: left;
}
.b.md .md-table th { background: var(--dsw-alias-markdown-code-block-banner); font-weight: 600; }

/* ── 聊天区里的代码要"看得出是代码"（主人 2026-10-13）──
 *
 * 问题：设计 token 给的代码底跟背景【几乎同色】——
 *     亮：页面 #ffffff · 代码块 rgb(249,250,251) · 行内 rgb(250,250,250)  → Δ 只有 5~6/255
 *     暗：页面 rgb(21,21,23) · 代码块 rgb(27,27,28)                      → Δ 只有 6
 *   我自己的气泡也糊（暗色下行内代码 Δ=3）。→ 代码看着就是正文。
 *
 * 解法：叠一层【半透明】而不是换一个固定色值 ——
 *   同一个值叠在"白页面""我的浅蓝气泡(- #edf3fe)""深灰气泡(- #2c2c2e)"上
 *   都显出同样的层次，而且气泡底以后再改，这里不用跟着动。
 *   （同族先例：附件芯片的 --dsw-alias-interactive-bg-hover = rgba(38,49,72,.06)）
 *
 * 范围：只改 #list（聊天区）。弹框里的 .md-pre 和设计系统的 token 本身都不动 ——
 *   改 token 是改别人设计系统（不该），而且弹框底色本来就不一样。
 *   ★ 折叠块（.tr-v.md）里的也一起改了：同一件事，别留一半。
 *
 * 回退：删掉这一段，就回到 token 的默认底。 */
#list .b.md code,
#list .b.md .md-code,
#list .tr-v.md code,
#list .tr-v.md .md-code { background: rgba(0, 0, 0, .07); }

html[data-theme="dark"] #list .b.md code,
html[data-theme="dark"] #list .b.md .md-code,
html[data-theme="dark"] #list .tr-v.md code,
html[data-theme="dark"] #list .tr-v.md .md-code { background: rgba(255, 255, 255, .09); }

/* ★ 2026-10-23：同一个问题在【侧栏】也有 —— 上面那条当时只写了 #list。
 *
 * 侧栏的底是 --dsw-specific-sidebar-fill，而代码块的底是
 * --dsw-alias-markdown-code-block，★ 这两个 token 是【同一个值】
 *   （实测 4 套皮肤 × 亮暗 = 8 组，全部相同：snow 亮 #f8fafb、暗 #121920…）
 * 于是工作区详情里的 diff / 源码 / json 原文都"看着就是正文"，一条边界都没有。
 *
 * 为什么今天才暴露：10-13 写上面那段时，侧栏还只能看图，没人在那儿看代码。
 * 10-23 加了「把 diff 推到工作区看」（.m-chg-push），这块就成了主战场。
 *
 * ★ 叠加值跟聊天区【完全一样】，不另写一套 —— 同一个问题用同一个答案。
 *   范围也收得很窄：只到 .ws-detail 里的 code / .md-code，
 *   JSON 树、PDF、tex 那些渲染器各有各的样式，不受影响。 */
#rsidebar .ws-detail .b.md code,
#rsidebar .ws-detail .b.md .md-code { background: rgba(0, 0, 0, .07); }

html[data-theme="dark"] #rsidebar .ws-detail .b.md code,
html[data-theme="dark"] #rsidebar .ws-detail .b.md .md-code { background: rgba(255, 255, 255, .09); }

/* ─────────── 滚动条 ───────────
 *
 * ★ 2026-10-12（主人："所有滚动条好像是默认样式，很丑"）
 *
 * 两层写（渐进增强）：
 *   ① 标准属性 scrollbar-width / scrollbar-color —— 新 Chrome / Edge / Firefox
 *   ② ::-webkit-scrollbar —— 旧 Chrome / Safari / Android WebView
 *
 * ★ 为什么两个都写而不是删一个：规范里【标准属性优先】——
 *   设了 scrollbar-color 之后，Chrome 会忽略 ::-webkit-scrollbar（不是冲突，是设计）。
 *   于是：新浏览器走 ①，旧浏览器走 ②，各拿各的。
 * ⚠️ iOS Safari 两个都不支持（系统限制：滚动条只在滑动时淡入）—— 改不了，也不该改。
 *
 * 手法：thumb 用 border + background-clip 收窄 2px —— 滚动条整体更细、不挡路；
 *       track 透明（不要那条灰底）。
 */
/* ★★ 2026-10-12 实测修正：vendor 的 --dsw-* 变量定义在【body】上
 *   （选择器是 `html[data-theme="dark"] body`），不是 :root。
 *   → 原来写在 `html {}` 里的 scrollbar-color 读不到变量 = 那条声明【无效】。
 *   → scrollbar-width / scrollbar-color 都是【继承属性】，挂在 body 上，
 *     所有后代（#list / .nav / .tr-pre / #modal）自动继承。
 *
 * 为什么用 @supports 隔离（实测踩过）：
 *   规范规定 —— 一旦 scrollbar-color/scrollbar-width 不是 auto，
 *   Chrome 会【忽略 ::-webkit-scrollbar】。而下面那段精细的自定义
 *   （收窄 2px + 圆角）比系统细条更好看。
 *   所以：Firefox 走标准属性，Chrome/Safari/Android 走 ::-webkit-scrollbar。
 */
@supports not selector(::-webkit-scrollbar) {
  body { scrollbar-width: thin; }
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb {
  background: var(--dsw-alias-scrollbar-bg-l1);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--dsw-alias-scrollbar-hover-l1);
  background-clip: padding-box;
}
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }

/* ── ★ 折叠块里的 markdown 也要防溢出（主人 2026-10-13 报的横滚条）──
 *
 * 根因：markdown 的规则【全都是 `.b.md`】—— 那是限定在消息气泡 `.b` 里的。
 *   而折叠块里我说的话是 `.tr-v.md`，【不在 `.b` 里】→ 那些规则一条都不生效：
 *   代码块没有 max-width、表格没有横向容器、手机上也不换行 →
 *   ★ 手机上就冒横向滚动条（长 content 尤其明显）。
 *
 * 这里给折叠块一份【防溢出最小集】—— 只做"不断破布局"，
 * 不动字号/间距（折叠块有自己的一套，比气泡小一号）。
 */
.tr-v.md { min-width: 0; }
.tr-v.md .md-code,
.tr-v.md pre {
  max-width: 100%;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.tr-v.md code { overflow-wrap: anywhere; word-break: break-word; }
/* 表格：宽表不撑破容器 —— 给它自己的横向滚动（而不是让整块溢出） */
.tr-v.md .md-table { display: block; max-width: 100%; overflow-x: auto; }

/* ★ 手机上让【代码块】也换行 —— 横滚条在手机上格外碍事。
   （代价：代码不再保持缩进对齐；但手机上"能读"比"对齐"重要。） */
@media (max-width: 520px) {
  .b.md .md-code { white-space: pre-wrap; overflow-wrap: anywhere; }
  .b.md .md-table { font-size: 12px; }
  /* ★ 折叠块在窄屏上更娇气：19px 的段详情缩进 + 7px 的图标间距一起挤，
     内容可用宽度就剩不下多少了 —— 手机上都收一点。 */
  .seg-more { padding-left: 9px; }
  .tr { gap: 5px; }
}

/* ── 注入折叠（记忆全文之类，2026-10-13）──
   ★ 它每轮都在、一条 16 KB —— 折叠成一行，点开才看。
   判据：它确实发生过（所以不隐藏），但它不是"谁说的话"（所以不占正屏）。 */
.inject {
  margin: 8px 0; border: 1px dashed var(--dsw-alias-border-l3);
  border-radius: 10px; background: var(--dsw-alias-bg-layer-1);
  font-size: 12.5px; opacity: .82;
}
.inject > summary {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; cursor: pointer; list-style: none;
  color: var(--dsw-alias-label-tertiary);
}
.inject > summary::-webkit-details-marker { display: none; }
.inject .ic-i { width: 13px; height: 13px; flex: none; }
.inject .inj-t { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inject .inj-s { flex: none; font-variant-numeric: tabular-nums; }
.inject .inj-b {
  margin: 0; padding: 8px 10px; max-height: 240px; overflow: auto;
  border-top: 1px dashed var(--dsw-alias-border-l3);
  white-space: pre-wrap; word-break: break-word;
  font-family: ui-monospace, monospace; font-size: 11.5px; line-height: 1.5;
}

/* ── 大段文字块：思考 / 内容（主人 2026-10-14）──
 *
 * 一个块 = 一整段文字（.tr-v 里是全文）：折叠态【4 行】，点它 → 不限高；再点 → 回 4 行。
 *
 * ★ 为什么是一个整块，而不是原来的"摘要行 + 折叠详情"：
 *     ① 摘要行只有一个，却同时装着"思考"和"正文"——
 *        于是有正文时摘要显示正文、点开却是思考（表里不一）
 *     ② 摘要取思考前 60 字、详情又是思考全文 → 前 60 字读两遍（重复）
 *     ③ 流式块和正式块长得不一样 → 中间闪一下（跳变）
 *   现在：思考一块、内容一块，各自是各自的内容；流式和正式是【同一个元素】。
 *
 * ★ 4 行怎么来的：.tr{font-size:12.5px} × .tr-v{line-height:1.5} = 18.75px/行
 *   6em = 12.5×6 = 75px = 4 行（3 行 56px 一句话就滚了，5 行 94px 偏高）。
 *   ★ 用 em 不用 px：字号改了它自己跟着走（写死 px 的话，哪天改 12.5 就成了错的）。
 *
 * ★ overflow 用 hidden 不用 auto：限高的目的是"别糊满屏"，展开靠【点】不靠滚 ——
 *   不该因为限了高就冒一条滚动条出来。
 *   ★ hidden 照样能用 JS 设 scrollTop（流式要跟着尾巴走），见 app.js 的 addDelta。
 *
 * ★ 另一半在 app.js（CSS 管不了）：流式时滚到尾巴、落定时滚回开头
 *   （摘要态看头，流式态看尾）。
 *
 * ★★ 两种块【长得不一样】（主人 2026-10-14 晚：「思考摘要/全文改成单行、
 *    content 保持 4 行字体加粗」）：
 *      think → 摘要态【1 行】· 淡 + 斜体   （它是过程，扫一眼就够）
 *      say   → 摘要态【4 行】· 加粗        （那是"说出来的话"，要比思考显眼）
 *    ★ 点开都是不限高，两边的开合行为一致。
 */
.tr-clamp { cursor: pointer; }
.tr-clamp .tr-v { max-height: 6em; overflow: hidden; }
.tr-clamp.open .tr-v { max-height: none; }

/* ★ 用 `:not(.open)` 兜住"折叠态"，而不是靠【书写顺序】压过上面那条 `.open` ——
 *   两条特异性一样（都是 0,3,0），谁在后面谁赢；
 *   10-13 正是栽在"同特异性的规则被后一条盖掉、CSS 静默失效"上
 *   （我 curl 到字符串在、就报"生效了"）。这样写就不依赖"谁在后面"。
 * ★ 1.5em = 1 行：12.5 × 1.5 = 18.75px —— 第二行正好从 18.75px 起、被裁掉，不露半行。 */
.tr-clamp:not(.open)[data-kind="think"] .tr-v { max-height: 1.5em; }
.tr-clamp[data-kind="say"] .tr-v { font-weight: 600; }

/* 还在写的那一段：末尾一个闪烁光标 —— 一眼看出"在写"，而不是"卡住了"。
 * （落定之后这个标记就摘掉，见 app.js 的 settleClamp。） */
.tr-delta .tr-v::after {
  content: '▍';
  margin-left: 1px;
  animation: deltaBlink 1s steps(1, end) infinite;
}
@keyframes deltaBlink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* ─────────────── 配置面板：数组项平铺（2026-10-15）───────────────
 *
 * 数组项（provider / mcp.servers / lsp.servers）整个不能被覆盖，
 * 但它的【元素字段】能按名字覆盖。
 *
 * ★ 10-15 改：原来整个数组挤在一个格子里（元素名当标题、字段往里缩），
 *   跟别的行长得完全不一样 —— 主人说"显示效果不统一"。
 *   现在【每个字段就是表格里的一行】，四列跟谁都一样；
 *   只用 ↳ + 缩进标一下"这是上面那张数组的子项"。
 *
 * ★ 缩进为什么写在名称格里、而不是给 <tr> 加 class：
 *   那些行是 table() 造的，而 table() 是【所有面板共用】的 ——
 *   为一处改它的签名不划算（共用件上多一个参数，以后谁都可能踩）。
 */

/* 数组子项的名称格：缩进 + ↳ 标记 */
.cfgsub { position: relative; padding-left: 15px; }
.cfgm {
  position: absolute;
  left: 0;
  top: 0;
  color: var(--dsw-alias-label-tertiary);
}

/* ── 「命令」面板（web/uiext/help.js，2026-10-16 第二轮）────────────────────────
 *
 * 主人：「现在的平铺效果不太好，做成 table 样式、点击能查看命令详情」。
 *
 * ★ 需要新增样式的只有两件（其余全靠 `.dt` / `.tbtn` / `.empty` 那套现成的）：
 *	① 行【可点】要看得出来：光标变手型 + 悬停有底色
 *	② 那一列「命令 / 操作名」用等宽字体 —— 它们是【要看字符的】（/help、-set），
 *	   不是散文。等宽之后一列排下来才好对（错误 spelling 一眼看出）。
 *
 * ★ 类名一律加 `cmd` 前缀：这是【一个面板的】样式，不去占像 `.name` `.hint` 这种通用名
 *   （占了之后别人不敢用，而它其实只服务这一处）。
 */
.dt.cmdlist td:first-child { white-space: nowrap; }
.dt.cmdlist tbody tr { cursor: pointer; }
/* ★ 底色挂在 td 上不是 tr 上：.dt th 自己有 background，td 没有 ——
 *   挂在 tr 上会被（将来某个）td 背景盖掉，挂 td 是稳的。 */
.dt.cmdlist tbody tr:hover td { background: var(--dsw-alias-interactive-bg-hover); }

.cmdn {
  font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-weight: 500;
}
.cmda {
  font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12px; color: var(--dsw-alias-label-secondary);
}
.cmdz { color: var(--dsw-alias-label-caption); }   /* 那个「—」和来源名：次要信息 */

.cmdhint { font-size: 11.5px; color: var(--dsw-alias-label-caption); margin-top: 8px; }

/* ─────────── 输入框命令提示（主人 2026-10-16）───────────
 *
 * 「输入 / 时弹个提示，输入 /h 就过滤 /h 开头的」—— 数据来自 /help --json。
 * 逻辑在 app.js（cmdTip*），这里只管它长什么样。
 *
 * ★ 它浮在【输入卡片上方】，不是卡片里 —— .cm-card 有 overflow:hidden，放里面会被裁掉。
 *   挂的是 #composer（relative），跟 #tobottom 是同一个定位思路（见那段注释）。
 * ★ 用 --cm-pad 而不是 --pad-x：跟卡片自己的左右内边距对齐（手机上是 12 不是 18）。
 */
.cmdtip {
  position: absolute;
  left: var(--cm-pad, 12px); right: var(--cm-pad, 12px);
  bottom: calc(100% + 7px);        /* 底边在卡片顶边上方 7px —— 跟 #tobottom 对称 */
  z-index: 6;
  max-height: 38vh;                /* 命令多了就滚，别把它顶到屏幕外 */
  overflow-y: auto;
  padding: 4px;
  /* ★★ 这里【故意不写 var(--card-bg)】（2026-10-16 实测）
   *
   *   --card-bg 在 :root 里定义成 `var(--dsw-specific-input-major)`，
   *   但 --dsw-* 全家都定义在【body】上（vendor/design-platform.css，
   *   选择器是 `html[data-theme=dark] body` —— style.css 1617 行那条注释早记过）。
   *   ★ CSS 自定义属性在【声明它的元素上】就要求值 → html 上没有 --dsw-* →
   *     --card-bg 整条变 invalid → 所有读它的地方 background 全部【静默失效】。
   *   （实测：getComputedStyle(html).getPropertyValue('--card-bg') === ''，
   *     而 .cm-card 的 backgroundColor = rgba(0,0,0,0) —— 输入卡片一直是透明的。）
   *
   *   所以这里直接读 body 上的那个 token。
   *   ★ 别改回 --card-bg —— 除非先把 :root 那条挪到 body。
   */
  background: var(--dsw-specific-input-major, #2a2a2c);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
}
/* ★ 亮色下浮层跟页面【同为纯白】（--dsw-specific-input-major = bluish-00）——
 *   不能靠色差，只能靠边框 + 阴影把它"抬起来"。
 *   （暗色下有天然色差：浮层 bluish-850 比页面底 bluish-950 亮一档，不用管。）
 */
html[data-theme="light"] .cmdtip {
  border-color: rgba(0, 0, 0, .14);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .18);
}

/* ★ is-on 是【键盘/鼠标的当前项】—— 用一层淡底，不抢主色（它是"选中"不是"按钮"） */
.cmdtip-i { display: flex; align-items: baseline; gap: 8px; padding: 6px 8px; border-radius: 7px; cursor: pointer; }
.cmdtip-i.is-on { background: rgba(127, 127, 127, .14); }
.cmdtip-i b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; }
.cmdtip-i span { font-size: 12px; color: var(--dsw-alias-label-caption); }
/* 说明靠右、超长省略 —— 它是"补充"，命令名才是主角 */
.cmdtip-i em {
  margin-left: auto; font-style: normal; font-size: 12px;
  color: var(--dsw-alias-label-caption);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 58%;
}
.cmdd { font-size: 15px; margin: 8px 0 2px; }
.cmddesc { color: var(--dsw-alias-label-secondary); margin-top: 4px; line-height: 1.5; }
.cmdsec {
  margin-top: 14px; font-size: 12px; font-weight: 600;
  color: var(--dsw-alias-label-tertiary);
}

/* ── 「事件」面板（plugin-tips，2026-10-16）────────────────────────────
 *
 * ★ 颜色一律用 rgba 绝对值，不引 --dsw-* 变量 ——
 *   那些变量定义在【body】上；在 :root（html）上求值会静默失效
 *   （2026-10-16 刚踩过：--card-bg 一整条为空，浮层透明得看不清）。
 *   这儿的元素都在 body 里、其实继承得到；但少一个依赖少一个坑。
 */
.tipbar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.tipbar .cmdhint { margin-left: auto; }
.tipf.on { font-weight: 700; background: rgba(128, 128, 128, .18); }
.tiptime { white-space: nowrap; }
.tipdot { color: #e5484d; font-size: 10px; margin-right: 4px; }
.tipk {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  font-size: 11px; border: 1px solid rgba(128, 128, 128, .35); opacity: .75;
}
.tipk-p {
  color: #e5484d; border-color: rgba(229, 72, 77, .5);
  background: rgba(229, 72, 77, .12); opacity: 1;
}
.tipnote { margin-top: 8px; }
/* ── 「工具」面板（web/uiext/tools.js，2026-10-16）────────────────────────────
 *
 * 主人：「我至少需要工具名称、工具描述、来源……风险等级也列一下」
 *      + 「顺便注册个侧边栏 UI 组件，位置在【查询】工具、命令…这里」
 *
 * ★ 它跟 plugin-order 那三个面板（hooks/slots）是姊妹，但【住的地方不同】：
 *	那三个是 buildin 插件（自带样式）；
 *	这个是 adapter-web【自己】的（跟 sub/skill/plugin/mcp/help 一样）——
 *	因为它读的 /tool 是 core/toolset 的命令，按主人 10-15 定的那条：
 *	  「core 包提供的命令/功能，由 adapter-web 提供 UI」。
 *
 * ★ 只加了四样（其余靠 .dt 那套现成的）：行样式、风险标签、搜索框、底部那行说明。
 *   类名一律 `tl-` 前缀 —— 一个面板的样式，不占通用名。
 *
 * ★★ 颜色一律用 --dsw-alias-*：它们定义在【body】上（见本文件 1825 行那条长注释），
 *   别用 --card-bg（它在 :root 求值 → 整条静默失效）。
 */
.tl-tools { min-width: 0; }
.tl-search {
  width: 100%; box-sizing: border-box; margin-bottom: 8px;
  padding: 5px 9px; font: inherit; font-size: 12px;
  color: var(--dsw-alias-label-primary);
  background: var(--dsw-specific-input-major);
  border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px;
}
.tl-search:focus { outline: none; border-color: var(--dsw-alias-brand-text, #6aa9ff); }
.tl-head { font-size: 11px; color: var(--dsw-alias-label-caption); margin: 2px 0 6px; }
.tl-grp {
  font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary);
  margin: 10px 0 4px; padding-bottom: 3px;
  border-bottom: 1px solid var(--dsw-alias-border-l1);
}
.tl-grp:first-of-type { margin-top: 4px; }
.tl-cnt { float: right; font-weight: 400; opacity: .7; }
.tl-row {
  display: flex; align-items: baseline; gap: 7px;
  padding: 4px 6px; border-radius: 5px; cursor: pointer;
}
.tl-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
.tl-name {
  font-family: var(--dsw-font-mono, ui-monospace, monospace);
  font-size: 12px; flex: 0 0 auto;
  color: var(--dsw-alias-label-primary);
}
.tl-desc {
  font-size: 11.5px; color: var(--dsw-alias-label-caption);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* 风险标签：[读] 灰 / [写] 橙 / [执行] 红 —— ★ 只标记、不拦截（见 model.ToolKind） */
.tl-k {
  flex: 0 0 auto; font-size: 10px; line-height: 1.5;
  padding: 0 5px; border-radius: 8px; border: 1px solid currentColor; opacity: .85;
}
.tl-kr { color: var(--dsw-alias-label-caption); }
.tl-kw { color: #d99a2b; }
.tl-ke { color: #d9612b; }
.tl-empty { font-size: 12px; color: var(--dsw-alias-label-caption); padding: 14px 0; text-align: center; }
.tl-foot {
  font-size: 11px; color: var(--dsw-alias-label-caption);
  margin-top: 12px; padding-top: 8px;
  border-top: 1px solid var(--dsw-alias-border-l1); line-height: 1.6;
}

/* 详情（「工具」点一行之后的弹窗内容）*/
.tl-dt-name {
  font-family: var(--dsw-font-mono, ui-monospace, monospace);
  font-size: 14px; display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
}
.tl-dt-owner {
  font-size: 11px; font-weight: 400; font-family: inherit;
  color: var(--dsw-alias-label-caption);
}
.tl-dt-desc {
  font-size: 12.5px; line-height: 1.7; white-space: pre-wrap; margin-bottom: 12px;
}
.tl-dt-h { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary); margin: 10px 0 5px; }
.tl-dt-none { font-size: 12px; color: var(--dsw-alias-label-caption); }
.tl-dt-hint { font-size: 11px; color: var(--dsw-alias-label-caption); margin-top: 5px; }
.tl-pt { font-size: 12px; }
.tl-pt td:first-child { color: #d9612b; width: 12px; }   /* 那个 ★（必填）*/

/* ── 技能详情（点列表一行之后压上来的那一层）2026-10-19 ──────
   ★ 为什么在宿主 style.css 而不是插件自己的：
     跟 .tl-* 同一个理由（10-16 定的）—— skill 是【宿主的扩展】
     （/skill 是 core 包提供的命令，界面归 adapter-web），
     所以它的样式就该跟宿主住一起。
   ★ 前缀 sk- 跟 tl-（工具）分开 —— 同一套视觉语言，但归属不同。 */

.sk-meta {
  font-size: 11.5px; color: var(--dsw-alias-label-caption);
  margin-bottom: 10px; font-family: var(--dsw-font-mono, ui-monospace, monospace);
}
.sk-desc {
  font-size: 12.5px; line-height: 1.75; white-space: pre-wrap;
  background: var(--soft-bg);
  border-radius: 8px; padding: 9px 11px; margin-bottom: 12px;
}
.sk-h {
  font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary);
  margin: 12px 0 5px;
}
/* 正文：保留原始换行和缩进（SKILL.md 里常带命令行示例，渲染 md 会把缩进吃掉） */
.sk-body {
  max-height: 46vh; overflow: auto;
  background: var(--soft-bg);
  border-radius: 8px; padding: 9px 11px; margin: 0;
  font-size: 12px; line-height: 1.6;
}
.sk-file { max-height: 56vh; overflow: auto; margin: 0; font-size: 12px; }
/* 子文件清单：一行一个按钮（整行可点，跟列表里那些行一个手感） */
.sk-files { display: flex; flex-direction: column; gap: 2px; }
.sk-file-btn {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  width: 100%; text-align: left; cursor: pointer;
  background: none; border: 0; border-radius: 7px;
  padding: 7px 9px; font: inherit; font-size: 12px;
  color: var(--dsw-alias-label-primary);
}
.sk-file-btn:hover { background: var(--soft-bg); }
.sk-fi { font-family: var(--dsw-font-mono, ui-monospace, monospace); }
.sk-fs { font-size: 11px; color: var(--dsw-alias-label-caption); flex-shrink: 0; }

/* ── 记忆（两层：分类 → 条目）2026-10-19 ──────────────────
   ★ 由 plugin-memory 自己的 ui/memory.js 用；前缀 mem- 在它那边声明过，
     这里只是把"条目正文"的样式补上（分类列表用宿主现成的 .li-* 那套）。 */

.mem-item {
  font-size: 12.5px; line-height: 1.7; white-space: pre-wrap;
  padding: 9px 11px; border-radius: 8px;
  background: var(--soft-bg);
  margin-bottom: 6px;
}
.mem-item-cat {
  font-size: 10.5px; color: var(--dsw-alias-label-caption);
  margin-bottom: 4px; display: block;
}
/* 列表里默认截 3 行，点开看全（★ 用 clamp 不用截字符串 —— 那指数是原始量） */
.mem-clamp {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mem-clamp.open { display: block; -webkit-line-clamp: unset; }

/* ── 灵魂日记（三段：正式 / 草稿 / 归档）2026-10-19 ────────── */

.sn-sec { margin-bottom: 14px; }
.sn-sec-h {
  font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary);
  margin-bottom: 6px; display: flex; align-items: center; gap: 6px;
}
.sn-sec-n {
  font-size: 10.5px; font-weight: 400; color: var(--dsw-alias-label-caption);
}
.sn-day-btn {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  width: 100%; text-align: left; cursor: pointer;
  background: none; border: 0; border-radius: 7px;
  padding: 7px 9px; font: inherit; font-size: 12.5px;
  color: var(--dsw-alias-label-primary);
}
.sn-day-btn:hover { background: var(--soft-bg); }
.sn-day { font-family: var(--dsw-font-mono, ui-monospace, monospace); }
.sn-size { font-size: 11px; color: var(--dsw-alias-label-caption); flex-shrink: 0; }
.sn-body {
  max-height: 58vh; overflow: auto; margin: 0;
  font-size: 12.5px; line-height: 1.75; white-space: pre-wrap;
}

/* ─────────── 插件市场（2026-10-20）───────────
 *
 * 它是一个【覆盖层】，不是弹窗（.modal 那套）——
 * 市场是"逛"，需要大块地方；关掉就回到聊天，不打断会话。
 *
 * ★ 颜色一律直接读 --dsw-*，不经过 :root 中转 ——
 *   --dsw-* 挂在 body 上，在 :root 里包一层会静默断链（10-16 那个 --card-bg 的坑）。
 */
.mk-mask {
  position: fixed; inset: 0; z-index: 60;
  display: none;
  padding: 24px;
  background: rgba(0, 0, 0, .58);
  backdrop-filter: blur(2px);
}
.mk-mask.open { display: flex; align-items: center; justify-content: center; }

.mk-panel {
  display: flex; flex-direction: column;
  width: min(1180px, 100%);
  height: min(86vh, 100%);
  background: var(--dsw-alias-bg-base);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: var(--dsw-radius-lg, 16px);
  box-shadow: 0 24px 64px rgba(0, 0, 0, .5);
  overflow: hidden;
}

/* ── 头：标题 / 搜索 / 视图切换 / 关 ── */
.mk-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--dsw-alias-border-l2);
  flex: none;
}
.mk-title { font-size: 15px; font-weight: 600; flex: none; }

/* 详情态：搜索框和视图切换都收起来，头里只剩标题 + 关闭。
   ★ 必须让标题顶上去当那个"撑开器" —— 平时是 .mk-q（flex:1）在撑，
     它一 hidden，剩下的全是 flex:none，会一起挤到左边。 */
.mk-panel.detail .mk-title { flex: 1 1 auto; min-width: 0; }
.mk-panel.detail .mk-viewsw { display: none; }
.mk-q {
  flex: 1 1 auto; min-width: 0;
  padding: 7px 12px;
  font-size: 13px;
  color: var(--dsw-alias-label-primary);
  background: var(--dsw-specific-input-major);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 9px;
  outline: none;
}
.mk-q:focus { border-color: var(--dsw-static-deepseek-500); }

.mk-viewsw {
  display: flex; gap: 2px; padding: 2px; flex: none;
  background: var(--dsw-specific-input-major);
  border-radius: 9px;
}
.mk-vbtn {
  display: grid; place-items: center;
  width: 28px; height: 26px; border-radius: 7px;
  color: var(--dsw-alias-label-tertiary);
}
.mk-vbtn svg { width: 15px; height: 15px; fill: currentColor; stroke: none; }
.mk-vbtn:hover { color: var(--dsw-alias-label-primary); }
.mk-vbtn.on { background: var(--dsw-static-deepseek-500); color: #fff; }

.mk-close {
  flex: none; width: 28px; height: 28px; border-radius: 8px;
  font-size: 18px; line-height: 1;
  color: var(--dsw-alias-label-tertiary);
}
.mk-close:hover { background: var(--dsw-specific-input-major); color: var(--dsw-alias-label-primary); }

/* ── 内容区（唯一滚动的地方）── */
.mk-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 16px; }
.mk-notice {
  margin-bottom: 14px; padding: 11px 14px;
  font-size: 12.5px; line-height: 1.7;
  color: var(--dsw-alias-label-primary);
  background: rgba(224, 163, 58, .1);
  border: 1px solid rgba(224, 163, 58, .35);
  border-radius: 10px;
}
.mk-notice code, .mk-hint code {
  font-family: var(--dsw-font-mono, ui-monospace, monospace);
  font-size: 12px; padding: 1px 5px; border-radius: 4px;
  background: rgba(128, 128, 128, .18);
}
.mk-empty {
  padding: 48px 0; text-align: center;
  font-size: 13px; color: var(--dsw-alias-label-tertiary);
}

/* ── 卡片视图 ── */
.mk-grid {
  display: grid;
  /* ★ minmax(0,1fr) 不能写 1fr —— 1fr 的下限是 min-content，
     描述文字会把格子撑破、整页横向滚动（snow-market 上栽过一次，10-20）。 */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: 12px;
}
.mk-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px;
  background: var(--dsw-specific-input-major);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 12px;
  min-width: 0;
}
.mk-card.is-in { border-color: rgba(65, 118, 230, .45); }

.mk-top { display: flex; align-items: center; gap: 11px; min-width: 0; }
.mk-ico {
  flex: none; width: 40px; height: 40px; border-radius: 10px;
  display: grid; place-items: center;
  font-size: 17px; font-weight: 600; color: #fff;
  background: var(--dsw-static-deepseek-500);
}
/* 按名字哈希分 6 档 —— 同一个作者的插件常同前缀，颜色会靠近 */
.mk-ico[data-h="1"] { background: #4a9d7c; }
.mk-ico[data-h="2"] { background: #a4703c; }
.mk-ico[data-h="3"] { background: #8b5fbf; }
.mk-ico[data-h="4"] { background: #b8555f; }
.mk-ico[data-h="5"] { background: #3f8fa8; }

.mk-namewrap { min-width: 0; flex: 1 1 auto; }
.mk-name {
  display: flex; align-items: center; gap: 5px;
  font-size: 13.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mk-v {
  flex: none; font-size: 10px; line-height: 1;
  padding: 2px 4px; border-radius: 4px;
  color: var(--dsw-static-deepseek-500);
  background: rgba(65, 118, 230, .16);
}
.mk-sub, .mk-meta {
  margin-top: 2px; font-size: 11.5px;
  color: var(--dsw-alias-label-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mk-desc {
  margin: 0; font-size: 12.5px; line-height: 1.6;
  color: var(--dsw-alias-label-secondary);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mk-foot {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;            /* ★ 标签多了要能折，别把右边顶开 */
  margin-top: auto;
}
.mk-tags { display: flex; gap: 5px; flex-wrap: wrap; min-width: 0; }
.mk-b {
  font-size: 10.5px; padding: 1px 6px; border-radius: 4px;
  color: var(--dsw-alias-label-tertiary);
  background: rgba(128, 128, 128, .16);
}
.mk-acts {
  display: flex; align-items: center; gap: 8px;
  margin-left: auto; flex: none;
  flex-wrap: wrap;
}
.mk-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
.mk-tag.on { color: #5fb98a; }
/* 市场有更新的版本 —— 跟「被 db 覆盖」那族用同一个橙色，语义都是"这里有件事要你处理" */
.mk-tag.up { color: #e0a33a; }

/* 版本选择器（只有多版本时出现）
 *
 * ★ 2026-10-20 换掉原生 <select>：它的弹层在 Windows 上是【系统菜单】画的，
 *   option 的 background/color 即使拿到计算值也不会体现在弹层里，
 *   所以暗色面板上弹出来永远是一块系统白/系统蓝 —— 跟面板是两种东西。
 *
 * ★ 自绘之后换来三样：配色跟面板同源、当前项能高亮、hover 有反馈。
 *   代价：键盘导航、点外收起这些都要自己写（在 bindActions 里）。 */
.mk-vwrap { position: relative; display: inline-flex; flex: none; }
.mk-vsel {
  display: inline-flex; align-items: center; gap: 7px;
  height: 28px; padding: 0 10px;
  font-size: 12.5px; font-family: inherit;
  color: var(--dsw-alias-label-primary);
  background: var(--dsw-specific-input-major, rgba(128, 128, 128, .12));
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 8px; cursor: pointer;
}
.mk-vsel:hover { border-color: var(--dsw-alias-border-l1); }
.mk-vsel:focus-visible { outline: 2px solid var(--dsw-static-deepseek-500); outline-offset: 1px; }
.mk-vtxt { font-variant-numeric: tabular-nums; }
.mk-vchev {
  width: 12px; height: 12px; flex: none;
  color: var(--dsw-alias-label-tertiary);
  transition: transform .15s ease;
}
.mk-vwrap.open .mk-vchev { transform: rotate(180deg); }
.mk-vwrap.open .mk-vsel { border-color: var(--dsw-static-deepseek-500); }

/* 弹层：贴着按钮向下展开，跟卡片同一个底色 + 一层投影（表示它是浮起来的） */
.mk-vmenu {
  position: absolute; z-index: 5; top: calc(100% + 5px); right: 0;
  min-width: 100%; padding: 4px;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--dsw-specific-input-major, #2c2c2e);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .38), 0 2px 6px rgba(0, 0, 0, .24);
}
.mk-vmenu[hidden] { display: none; }
/* 下方没空间时向上展开（JS 里量滚动容器底边后加 .up） */
.mk-vwrap.up .mk-vmenu { top: auto; bottom: calc(100% + 5px); }
.mk-vwrap.up .mk-vchev { transform: rotate(180deg); }
.mk-vopt {
  padding: 6px 10px; text-align: left;
  font-size: 12.5px; font-family: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--dsw-alias-label-secondary);
  background: transparent; border: 0; border-radius: 7px;
  cursor: pointer; white-space: nowrap;
}
.mk-vopt:hover { background: rgba(128, 128, 128, .14); color: var(--dsw-alias-label-primary); }
.mk-vopt.on { color: var(--dsw-static-deepseek-500); font-weight: 600; }
.mk-vopt.on::after { content: ' ✓'; font-weight: 400; }

.mk-btn {
  padding: 5px 13px; font-size: 12.5px;
  color: var(--dsw-alias-label-primary);
  background: transparent;
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 8px;
}
.mk-btn:hover { background: rgba(128, 128, 128, .14); }
.mk-btn.primary {
  color: #fff;
  background: var(--dsw-static-deepseek-500);
  border-color: transparent;
}
.mk-btn.primary:hover { filter: brightness(1.08); }
.mk-btn[disabled] { opacity: .5; pointer-events: none; }

/* 危险动作用红色 —— 卸载不可逆（虽然进 trash，但人得看见警告） */
.mk-btn.danger { color: #e5695f; border-color: rgba(229, 105, 95, .45); }
.mk-btn.danger:hover { background: rgba(229, 105, 95, .14); border-color: rgba(229, 105, 95, .7); }

/* 卸载确认层 —— ★ 叠在覆盖层【里面】而不是用 ui.modal：
   市场的 .mk-mask 是 z-index 60，#mask 只有 50，弹框会被盖住 */
.mk-confirm {
  position: fixed; inset: 0; z-index: 61;
  display: flex; align-items: center; justify-content: center;
  padding: 16px; background: rgba(0, 0, 0, .55);
}
.mk-cf-card {
  width: min(440px, 100%);
  padding: 20px 22px;
  background: var(--dsw-specific-input-major);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 14px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .45);
}
.mk-cf-card h3 { margin: 0 0 10px; font-size: 15px; font-weight: 600; }
.mk-cf-card p { margin: 0 0 18px; font-size: 13px; line-height: 1.15rem; color: var(--dsw-alias-label-secondary); }
.mk-cf-card code {
  font-family: ui-monospace, Consolas, monospace; font-size: 12px;
  padding: 1px 5px; border-radius: 4px; background: rgba(128, 128, 128, .16);
}
.mk-cf-acts { display: flex; justify-content: flex-end; gap: 8px; }

/* ── 列表视图 ── */
.mk-rows { display: flex; flex-direction: column; gap: 8px; }
.mk-row {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto auto;
  align-items: center; gap: 12px;
  padding: 10px 12px;
  background: var(--dsw-specific-input-major);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 10px;
}
.mk-row.is-in { border-color: rgba(65, 118, 230, .45); }

/* ── 安装/更新的进度条（2026-10-20）──────────────────────
   ★ 数据不是前端估的：服务端 /api/market/progress 报【真字节数】
     （下载读了多少 / 总共多长），前端每 250ms 问一次。
   ★ total=0（服务端没给 Content-Length）时走"不确定"态：
     一道 30% 宽的条来回滑动，而不是假装知道百分比。 */
.mk-prog {
  display: flex; flex-direction: column; gap: 5px;
  grid-column: 1 / -1;          /* 列表行是 grid：占满第二行 */
  margin-top: 2px;
}
.mk-prog-bar {
  height: 4px; border-radius: 2px; overflow: hidden;
  background: rgba(128, 128, 140, .22);
}
.mk-prog-fill {
  display: block; height: 100%; width: 0;
  background: var(--dsw-static-deepseek-500, #4176e6);
  transition: width .18s linear;
}
.mk-prog-lab {
  font-size: 11px; color: var(--dsw-alias-label-tertiary);
  font-variant-numeric: tabular-nums;
}
/* 不确定态：条子来回滑 */
.mk-prog.indet .mk-prog-fill {
  animation: mk-indet 1.1s ease-in-out infinite;
  transition: none;
}
@keyframes mk-indet {
  0%   { margin-left: 0; }
  50%  { margin-left: 70%; }
  100% { margin-left: 0; }
}
.mk-row .mk-ico { width: 36px; height: 36px; font-size: 15px; border-radius: 9px; }
.mk-desc2 {
  margin-top: 2px; font-size: 12px; color: var(--dsw-alias-label-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mk-row .mk-meta { margin: 0; flex: none; }

.mk-pre {
  margin: 0; padding: 10px 12px;
  font-size: 12px; line-height: 1.6; white-space: pre-wrap;
  background: rgba(128, 128, 128, .12); border-radius: 8px;
  max-height: 40vh; overflow: auto;
}
.mk-hint { margin: 10px 0 0; font-size: 12.5px; color: var(--dsw-alias-label-tertiary); }

/* ── 点卡片 / 整行看详情 ──
   ★ 只给能点的加 pointer，不能点的保持箭头 —— 否则处处是手，等于什么都没说。 */
.mk-card.mk-open,
.mk-row.mk-open { cursor: pointer; }
.mk-card.mk-open:hover { border-color: rgba(65, 118, 230, .45); }
.mk-row.mk-open:hover { background: rgba(128, 128, 128, .07); }
.mk-card.mk-open:focus-visible,
.mk-row.mk-open:focus-visible {
  outline: 2px solid var(--dsw-static-deepseek-500);
  outline-offset: 2px;
}

/* ── 详情（覆盖层内容区里换一屏，不是新开一层浮层）── */
.mk-detail { padding: 2px 2px 8px; }

.mk-dhead { margin-bottom: 14px; }
.mk-back {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 11px 5px 8px;
  font-size: 13px; font-family: inherit;
  color: var(--dsw-alias-label-secondary);
  background: rgba(128, 128, 128, .08);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 8px; cursor: pointer;
}
.mk-back svg { width: 14px; height: 14px; }
.mk-back:hover {
  color: var(--dsw-alias-label-primary);
  border-color: var(--dsw-alias-border-l3);
}

/* ★ flex-wrap：详情页顶部最多 5 个元素（已装/市场 两个标签 + 换版本 + 停用 + 卸载），
 *   面板一窄就超出右边界，而且 .mk-namewrap 会被压成 0 —— 插件名直接看不见。
 *   ★ 两层都要允许换行：.mk-dtop 不换行的话，acts 自己 wrap 也没用（它整块被挤出去）。 */
.mk-dtop { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.mk-dtop .mk-ico { width: 52px; height: 52px; font-size: 21px; border-radius: 13px; }
.mk-dname {
  margin: 0 0 4px; font-size: 18px; font-weight: 600; line-height: 1.3;
  color: var(--dsw-alias-label-primary);
}
/* ★ flex-wrap：详情页顶部最多 5 个元素（已装/市场 两个标签 + 换版本 + 停用 + 卸载），
 *   面板一窄就超出右边界，而且 .mk-namewrap 会被压成 0 —— 插件名直接看不见。
 *   允许换行之后：宽的时候仍是一行（justify-content 保持靠右），窄的时候往下折。 */
.mk-dacts {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  flex: none; flex-wrap: wrap; justify-content: flex-end; max-width: 100%;
}
.mk-ddesc {
  margin: 14px 0 0; font-size: 13.5px; line-height: 1.65;
  color: var(--dsw-alias-label-secondary);
}

.mk-dstate {
  margin-top: 2px; padding: 9px 12px;
  font-size: 12.5px; border-radius: 9px;
  color: var(--dsw-alias-label-secondary);
  background: rgba(128, 128, 128, .08);
  border: 1px solid transparent;
}
.mk-dstate b { color: var(--dsw-alias-label-primary); font-weight: 600; }
.mk-dstate .mk-up { color: var(--dsw-static-amber-500); font-weight: 500; }
/* 三种状态各自一个色 —— 一进详情页，"要不要动手"这件事应该零思考
 * ★ 用低透明度底色 + 彩色文字，不铺满整块：这是【状态】，不是警告横幅 */
.mk-dstate.on   { color: rgb(108, 190, 145); background: rgba(76, 175, 125, .10); border-color: rgba(76, 175, 125, .28); }
.mk-dstate.off  { color: var(--dsw-alias-label-secondary); background: rgba(128, 128, 128, .10); border-color: rgba(128, 128, 128, .22); }
.mk-dstate.none { color: rgb(122, 167, 224); background: rgba(65, 118, 230, .10); border-color: rgba(65, 118, 230, .26); }

/* ★ 上面那三组颜色是【给暗色页调的】—— 亮色页上 rgb(122,167,224) 放在
 *   10% 蓝底上，对比度只有 2.3:1（WCAG AA 要 4.5）。
 *   所以亮色另给一套更深的值，而不是指望同一组颜色两边都行。
 *   （这跟 snow-market 那边"硬编码亮色放到暗色页上"是同一类错，方向相反） */
html[data-theme="light"] .mk-dstate.on   { color: rgb(30, 115, 75); }
html[data-theme="light"] .mk-dstate.none { color: rgb(40, 85, 175); }

/* ── 详情分区：把「判断依据」和「文档」分开 ──
 * ★ 改版前的问题：状态 / requires / 元信息 / README 全在同一根竖线上，
 *   读的人分不清哪块是要他动手的、哪块只是说明。
 *   分区之后，每条竖线上只有一类东西。 */
.mk-sec {
  margin-top: 18px; padding: 14px 16px 12px;
  background: rgba(128, 128, 128, .05);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 12px;
}
.mk-sec-t {
  margin-bottom: 10px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .08em;
  color: var(--dsw-alias-label-tertiary);
}
/* 分区里的 requires 不用再自己撑外面那圈 —— 它已经在框里了 */
.mk-sec .mk-req { margin: 12px 0 0; }
.mk-sec .mk-dmeta { margin-top: 14px; }

/* ── 使用说明（README）折叠 ──
 * ★ 用原生 <details>：自带展开/收起、键盘可达、屏幕阅读器能读状态。
 *   summary 上的 ::marker 默认是个三角形，跟自绘的箭头会重复，去掉。 */
.mk-fold {
  margin-top: 18px;
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 12px;
  background: rgba(128, 128, 128, .04);
  overflow: hidden;
}
.mk-fold > summary {
  display: flex; align-items: center; gap: 9px;
  padding: 12px 16px;
  font-size: 13px; cursor: pointer;
  list-style: none;
  user-select: none;
}
.mk-fold > summary::-webkit-details-marker { display: none; }
.mk-fold > summary::marker { content: ''; }
.mk-fold > summary:hover { background: rgba(128, 128, 128, .07); }
.mk-fold > summary:focus-visible {
  outline: 2px solid var(--dsw-static-deepseek-500);
  outline-offset: -2px;
}
.mk-fold-t { font-weight: 600; color: var(--dsw-alias-label-primary); }
.mk-fold-m { font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }
.mk-fold-c {
  width: 12px; height: 12px; flex: none;
  color: var(--dsw-alias-label-tertiary);
  transition: transform .15s ease;
}
.mk-fold[open] .mk-fold-c { transform: rotate(90deg); }
.mk-fold-b {
  padding: 4px 16px 16px;
  border-top: 1px solid var(--dsw-alias-border-l2);
}

.mk-dtags { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; }

.mk-dmeta {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 7px 16px; margin: 16px 0 0;
  font-size: 12.5px;
}
.mk-dmeta dt { color: var(--dsw-alias-label-tertiary); }
.mk-dmeta dd {
  margin: 0; color: var(--dsw-alias-label-secondary);
  overflow: hidden; text-overflow: ellipsis;
}
.mk-dmeta code {
  padding: 1px 5px; border-radius: 5px;
  background: rgba(128, 128, 128, .14);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}
.mk-dmeta a { color: var(--dsw-static-deepseek-500); text-decoration: none; }
.mk-dmeta a:hover { text-decoration: underline; }

/* ── 「装之前要准备」（requires）──
 * ★ 它必须在【下载之前】看见：缺哪个 key、要不要连某个网 ——
 *   装完再发现就晚了。所以排在动作按钮下面、README 上面。
 * ★ 配色跟 snow 的语义色走，不硬编码亮色（暗色页面上会是一块刺眼的白）。 */
.mk-req {
  margin: 18px 0 4px; padding: 12px 14px;
  background: rgba(224, 163, 58, .07);
  border: 1px solid rgba(224, 163, 58, .26);
  border-radius: 10px;
}
.mk-req-t { font-size: 12.5px; font-weight: 600; color: rgb(224, 163, 58); margin-bottom: 8px; }
.mk-req ul { margin: 0; padding: 0; list-style: none; }
.mk-req li { font-size: 12.5px; padding: 4px 0; line-height: 1.7; color: var(--dsw-alias-label-secondary); }
.mk-req code {
  font-family: var(--dsw-font-mono, ui-monospace, monospace);
  font-size: 12px; padding: 1px 5px; border-radius: 4px;
  background: var(--dsw-alias-bg-l2, rgba(128,128,128,.14));
  color: var(--dsw-alias-label-primary);
}
.mk-rtag {
  display: inline-block; min-width: 38px; text-align: center;
  font-size: 10.5px; padding: 1px 6px; border-radius: 4px; margin-right: 8px;
  background: rgba(128, 128, 128, .16); color: var(--dsw-alias-label-secondary);
}
.mk-rtag.must { background: rgba(224, 122, 95, .18); color: rgb(230, 145, 120); }
.mk-rtag.opt  { background: rgba(65, 118, 230, .16); color: rgb(122, 167, 224); }
.mk-rtag.net  { background: rgba(224, 163, 58, .16); color: rgb(224, 163, 58); }
.mk-rtag.cmd  { background: rgba(76, 175, 125, .16); color: rgb(108, 190, 145); }
/* 同 .mk-dstate：上面那组是暗色的值，亮色下要换更深的一档才够对比 */
html[data-theme="light"] .mk-rtag.must { color: rgb(150, 60, 35); }
html[data-theme="light"] .mk-rtag.opt  { color: rgb(40, 85, 175); }
html[data-theme="light"] .mk-rtag.net  { color: rgb(150, 100, 15); }
html[data-theme="light"] .mk-rtag.cmd  { color: rgb(30, 115, 75); }
.mk-rtag-url { display: inline-block; margin-left: 0; }
.mk-rtag-url a { color: var(--dsw-static-deepseek-500); text-decoration: none; }
.mk-rtag-url a:hover { text-decoration: underline; }

/* 没带 README 时的兜底块（带 README 的走 .mk-fold） */
.mk-readme { margin-top: 18px; padding: 12px 16px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; background: rgba(128, 128, 128, .04); }
.mk-readme > h3 {
  margin: 0 0 8px; font-size: 12.5px; font-weight: 600;
  color: var(--dsw-alias-label-secondary);
}
.mk-muted { margin: 0; font-size: 12.5px; color: var(--dsw-alias-label-tertiary); }

/* ── 详情里的 Markdown ──
   ★ 只作用在 .mk-detail 里，别跟聊天区那套 .b.md 打架。 */
.mk-detail .md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-secondary); }
.mk-detail .md > *:first-child { margin-top: 0; }
.mk-detail .md > *:last-child { margin-bottom: 0; }
.mk-detail .md h1, .mk-detail .md h2, .mk-detail .md h3,
.mk-detail .md h4, .mk-detail .md h5, .mk-detail .md h6 {
  margin: 20px 0 8px; font-weight: 600; line-height: 1.35;
  color: var(--dsw-alias-label-primary);
}
.mk-detail .md h1 { font-size: 17px; }
.mk-detail .md h2 { font-size: 15.5px; }
.mk-detail .md h3 { font-size: 14px; }
.mk-detail .md h4, .mk-detail .md h5, .mk-detail .md h6 { font-size: 13px; }
.mk-detail .md p { margin: 10px 0; }
.mk-detail .md ul, .mk-detail .md ol { margin: 10px 0; padding-left: 22px; }
.mk-detail .md li { margin: 4px 0; }
.mk-detail .md code {
  padding: 1.5px 5px; border-radius: 5px;
  background: rgba(128, 128, 128, .16);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
  color: var(--dsw-alias-label-primary);
}
.mk-detail .md pre {
  margin: 12px 0; padding: 12px 14px;
  background: rgba(128, 128, 128, .1);
  border: 1px solid var(--dsw-alias-border-l2);
  border-radius: 9px;
  overflow: auto; max-height: 46vh;
}
.mk-detail .md pre code {
  padding: 0; background: none; font-size: 12.5px; line-height: 1.6;
  color: var(--dsw-alias-label-secondary);
}
.mk-detail .md blockquote {
  margin: 12px 0; padding: 8px 14px;
  border-left: 3px solid rgba(65, 118, 230, .5);
  background: rgba(65, 118, 230, .07);
  border-radius: 0 8px 8px 0;
  color: var(--dsw-alias-label-secondary);
}
.mk-detail .md blockquote > *:first-child { margin-top: 0; }
.mk-detail .md blockquote > *:last-child { margin-bottom: 0; }
.mk-detail .md table { margin: 12px 0; border-collapse: collapse; font-size: 12.5px; width: 100%; }
.mk-detail .md th, .mk-detail .md td {
  padding: 7px 10px; border: 1px solid var(--dsw-alias-border-l2); text-align: left;
}
.mk-detail .md th {
  background: rgba(128, 128, 128, .09); font-weight: 600;
  color: var(--dsw-alias-label-primary);
}
.mk-detail .md a { color: var(--dsw-static-deepseek-500); text-decoration: none; }
.mk-detail .md a:hover { text-decoration: underline; }
.mk-detail .md img { max-width: 100%; border-radius: 8px; }
.mk-detail .md hr {
  margin: 18px 0; border: 0; border-top: 1px solid var(--dsw-alias-border-l2);
}
.mk-detail .md strong { font-weight: 600; color: var(--dsw-alias-label-primary); }

/* ── 手机 ── */
@media (max-width: 620px) {
  /* ★ 不铺满：四周留 12px，保留圆角 —— 手机上也看得出"这是一层浮起的面板"。
     完全铺满（padding:0 + 圆角 0）会像切了一个新页面，关掉的入口不好找。 */
  .mk-mask { padding: 12px; }
  .mk-panel { width: 100%; height: 100%; border-radius: 14px; }
  .mk-head { flex-wrap: wrap; gap: 8px; }
  .mk-q { order: 3; flex-basis: 100%; }
  .mk-grid { grid-template-columns: minmax(0, 1fr); }
  /* ★ 详情页顶部：图标+名字一行，动作区换到第二行占满整宽。
     不这么分的话，5 个按钮挤在一行 —— 名字被压成 0 宽、右端顶出面板（实测溢出 65px）。 */
  .mk-dtop { flex-wrap: wrap; }
  .mk-dacts { width: 100%; margin-left: 0; justify-content: flex-start; }
  .mk-row { grid-template-columns: 32px minmax(0, 1fr) auto; }
  .mk-row .mk-ico { width: 32px; height: 32px; font-size: 14px; }
  .mk-row .mk-meta { display: none; }
}

/* ────────────────────────────────────────────────────────────────
 * 基础组件 · barList（横向条形图）
 *
 * ★ 在 sdk/uiext/components.js 里实现，宿主和插件共用。
 *   这里只放样式 —— 因为组件只吐 class，颜色全走 CSS 变量。
 *
 * ★★ 为什么一个颜色值都不写死：
 *   两套主题（暗/亮）下 --dsw-* 的值不一样，写死 rgb(...) 就会在另一套主题下
 *   变成刺眼的一块。market 那边踩过这个坑（#fffbeb 放在暗色页上，三块白斑）。
 *
 * ★ 布局用 flex，不用 grid：
 *   条本身要 flex:1 吃掉剩余宽度，而列宽（label/数值/次数/百分比）是固定的 ——
 *   flex 的「固定 + 弹性」比 grid 的 minmax 更直白。
 * ──────────────────────────────────────────────────────────────── */
.blist { display: flex; flex-direction: column; gap: 2px; }

.bl-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  font-size: 12.5px;
  line-height: 1.4;
}

.bl-lab {
  flex: none;
  width: 48px;
  color: var(--dsw-alias-label-secondary);
  font-variant-numeric: tabular-nums;   /* ★ 数字等宽，扫一列不会跳 */
}

.bl-track {
  flex: 1 1 auto;
  min-width: 40px;                       /* ★ 窄屏时别把条压成一条线 */
  height: 15px;
  background: var(--dsw-alias-interactive-bg-hover);
  border-radius: 3px;
  overflow: hidden;
}

.bl-fill {
  display: block;
  height: 100%;
  background: var(--dsw-alias-brand-primary-new-color, #4176e6);
  border-radius: 3px;
  transition: width .35s cubic-bezier(.4, 0, .2, 1);
}

.bl-val {
  flex: none;
  width: 58px;
  text-align: right;
  color: var(--dsw-alias-label-primary);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.bl-sub {
  flex: none;
  width: 62px;
  text-align: right;
  color: var(--dsw-alias-label-secondary);
  font-variant-numeric: tabular-nums;
}

.bl-right {
  flex: none;
  width: 48px;
  text-align: right;
  color: var(--dsw-alias-state-success-primary, #4caf50);
  font-variant-numeric: tabular-nums;
}

/* muted —— 「当天未完」这类：条虚化，数值降一档。
   ★ 不用隐藏，也不用虚线边框 —— 那些看着像"缺数据"，而它其实有数据、只是不全。 */
.bl-row.muted .bl-fill {
  background: var(--dsw-alias-label-secondary);
  opacity: .4;
}
.bl-row.muted .bl-val { color: var(--dsw-alias-label-secondary); }

/* 窄屏：让出「次数」那一列 —— 手机上 4 列会挤，而百分比比次数更该留 */
@media (max-width: 620px) {
  .bl-sub { display: none; }
  .bl-lab { width: 42px; }
}

/* ────────────────────────────────────────────────────────────────
 * 用量分析面板（usage.js 的 modal 内容）
 *
 * ★ 只在这里定义 ua-* —— barList 的样式在下面「基础组件」那节，
 *   两处分开放：一个是"这批数据长什么样"，一个是"条怎么画"。
 * ──────────────────────────────────────────────────────────────── */
.ua { min-width: 0; }

.ua-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.ua-lab { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); flex: none; }
.ua-tabs { display: flex; gap: 4px; margin-left: auto; }

.ua-tab {
  padding: 3px 10px;
  font: inherit;
  font-size: 12px;
  color: var(--dsw-alias-label-secondary);
  background: transparent;
  border: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, .09));
  border-radius: 6px;
  cursor: pointer;
}
.ua-tab:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
.ua-tab.on {
  color: var(--dsw-alias-brand-text, #6aa9ff);
  border-color: var(--dsw-alias-brand-text, #6aa9ff);
  background: rgba(65, 118, 230, .12);
}

/* ── 四个 KPI ── */
.ua-kpis {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 14px;
}
.ua-k {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--dsw-alias-interactive-bg-hover);
  min-width: 0;
}
.ua-kl { font-size: 11px; color: var(--dsw-alias-label-secondary); }
.ua-kv {
  font-size: 17px;
  font-weight: 600;
  color: var(--dsw-alias-label-primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ua-kv.up   { color: var(--dsw-alias-state-error-primary, #e05252); }   /* 涨了 = 花更多 */
.ua-kv.down { color: var(--dsw-alias-state-success-primary, #4caf50); }
.ua-kv.dim  { color: var(--dsw-alias-label-secondary); }
.ua-ks { font-size: 10.5px; color: var(--dsw-alias-label-tertiary, #8a8f96); }

/* ── 一节（标题 + 内容）── */
.ua-sec { margin-bottom: 14px; }
.ua-h {
  font-size: 12px;
  color: var(--dsw-alias-label-secondary);
  margin-bottom: 6px;
}
.ua-hs { color: var(--dsw-alias-label-tertiary, #8a8f96); font-size: 11px; }

.ua-tbl { font-size: 12px; }

/* ── 缓存掉点（2026-10-21）─────────────────────────────────────
 *
 * 逐次调用一根柱。颜色语义跟上面四段那套【不共用】：
 *   这里 蓝/橙/红 = 正常 / 中等 / 掉得狠（严重程度），
 *   上面 蓝/橙/绿/紫 = 命中/未命中/输出/思维链（成分）。
 * ★ 两个都用橙，但含义不同 —— 因为它们出现在两块不同的图里，
 *   各自有图例；不共用变量是为了将来改一边不会悄悄改坏另一边。
 */
.ua-hsub {
  color: var(--dsw-alias-label-tertiary, #8a8f96);
  font-size: 11px; margin-left: 10px; font-weight: 400;
}

.ua-ckpi { display: flex; gap: 22px; flex-wrap: wrap; margin: 10px 0 12px; font-size: 11px; }
.ua-ckpi span { color: var(--dsw-alias-label-tertiary, #8a8f96); display: block; }
.ua-ckpi b {
  color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.ua-ckpi b.o { color: var(--ua-c-mid, #4176e6); }
.ua-ckpi b.r { color: var(--ua-c-bad, #e05252); }

.ua-cchart {
  position: relative; height: 120px;
  border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, .10));
}
.ua-cchart .bars { position: absolute; inset: 0; }
/* ★★ 2026-10-21 第三版：改成【绝对定位】—— 这是让"逐次不聚合"和"不溢出"同时成立的关键。
 *
 *   前两版都是 flex 流式排布（`flex: 1 1 0; min-width: 1px`）：
 *     · 柱数一多，每根被压到 min-width → 总宽 = N 像素 → 容器装不下 → 整页横向滚动
 *     · 不溢出就只能减少柱数（聚合）→ 尖峰被平均掉，"降了几次"看不出来
 *
 *   绝对定位的元素【不参与布局流】，所以 N 多大都不撑破容器。
 *   x 由时间比例决定（status.js 里算 left%），宽度固定 2px。
 */
.ua-cchart i {
  position: absolute; bottom: 0; width: 2px; margin-left: -1px;
  display: block; border-radius: 1px 1px 0 0;
  background: var(--ua-c-ok, #4ec97f); opacity: .85;
  transition: opacity .12s;
}
/* ★ 调用频率块（2026-10-21 合进主图）：从【顶部】往下挂的灰色块。
 *
 *   为什么在顶部不在底部：底部那截高度已经被"未命中量"占了。
 *   频率如果也从底部起，两块柱子会叠在一起 —— 分不清"这根高是因为调用多，
 *   还是因为掉得多"。一上一下，两个维度物理分离。
 *
 *   颜色是中性灰，不带语义：它只说"这里在动"，不说"好不好"。 */
.ua-cchart i.freq {
  top: 0; bottom: auto; height: auto;
  background: var(--ua-freq, rgba(140,150,165,.22));
  border-radius: 0 0 1px 1px;
  opacity: 1;
}
html[data-theme="light"] .ua-cchart i.freq { background: rgba(100,110,130,.16); }
/* 正常调用：不画高度（height 由 JS 不给，用这里的固定小点）—— 只表示"这一刻有调用" */
.ua-cchart i.n { height: 3px; opacity: .55; }
/* ★ 三档配色（2026-10-21 主人定）：低=绿 · 中=蓝 · 高=红
 *   opacity 也跟着调：绿那档最淡（它是"正常"，不该抢眼），红那档最实。
 *   失效的柱子加宽到 3px —— 从一片 2px 小点里跳出来，"降了几次"才数得清。 */
.ua-cchart i.s { background: var(--ua-c-mid, #4176e6); opacity: 1; width: 3px; margin-left: -1.5px; }
.ua-cchart i.x { background: var(--ua-c-bad, #e05252); opacity: 1; width: 4px; margin-left: -2px; }
.ua-cchart i.z { background: var(--ua-other, #8a8f96); opacity: .4; height: 3px; }
.ua-cchart i:hover { opacity: 1; z-index: 2; }

.ua-caxis { position: relative; height: 18px; margin-top: 4px; }
/* 整点分隔线（替代原来的"色带分区"：横轴现在是真实时间，整点直接在对的位置上） */
.ua-czones { position: absolute; inset: 0; pointer-events: none; }
.ua-czones i.gl { position: absolute; top: 0; bottom: 0; width: 1px; display: block;
  background: rgba(255, 255, 255, .07); }
html[data-theme="light"] .ua-czones i.gl { background: rgba(38, 49, 72, .09); }
.ua-caxis span {
  position: absolute; transform: translateX(-50%); font-size: 10px; white-space: nowrap;
  line-height: 18px; color: var(--dsw-alias-label-tertiary, #8a8f96);
}

.ua-cleg {
  display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px;
  font-size: 11px; color: var(--dsw-alias-label-tertiary, #8a8f96);
}
.ua-cleg i {
  display: inline-block; width: 9px; height: 9px; border-radius: 2px;
  margin-right: 5px; vertical-align: -1px;
}
/* ★ 类名 .b/.o/.r 是旧的（当时按颜色取的名：blue/orange/red），
 *   2026-10-21 三档改成 低绿/中蓝/高红 之后【名字已经不等于颜色】。
 *   没改名是因为它同时出现在 status.js 里，一起改容易漏一处 ——
 *   但注释必须留着，否则下一个人看到 `.b` 是绿的会以为是 bug。 */
.ua-cleg i.b { background: var(--ua-c-ok, #4ec97f); opacity: .85; }
.ua-cleg i.o { background: var(--ua-c-mid, #4176e6); }
.ua-cleg i.r { background: var(--ua-c-bad, #e05252); }
.ua-cleg i.z { background: var(--ua-other); opacity: .35; }

/* ★ 表格默认 5 行高、内部滚动（主人 2026-10-21 定）——
 *   10 行全铺开会把面板拉得很长，而那 10 行里真正要看的通常就前两三行。
 *   表头 sticky 是因为滚动时列名必须还在（不然滚两下就不知道哪列是哪列了）。
 */
.ua-ctab {
  margin-top: 12px; max-height: 168px; overflow-y: auto;
  /* ★★ overflow-x 必须显式写 hidden（2026-10-21 主人报「一点点横向滚动条」）。
   *   只写 overflow-y: auto 时，按 CSS 规范另一个轴会从 visible 被算成 auto
   *   （实测 computed overflow-x = "auto"）—— 于是这个块【自带横向滚动能力】。
   *   表格宽度本来就是 100%、右对齐到边，任何一次子像素取整/滚动条占位
   *   都会把这个滚动条翻出来。
   *   ★ 判据：凡是设了 overflow-y，另一个轴也要显式给值 —— "我只想滚纵向"
   *     这个意图，CSS 不会替你说。 */
  overflow-x: hidden;
  border: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, .10));
  border-radius: 8px;
}
.ua-ctbl { width: 100%; border-collapse: collapse; font-size: 12px; }
.ua-ctbl th,
.ua-ctbl td {
  text-align: left; padding: 5px 9px;
  border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, .07));
}
.ua-ctbl th {
  position: sticky; top: 0; z-index: 1;
  background: var(--dsw-alias-bg-layer-2, var(--dsw-specific-menu, rgb(35, 35, 36)));
  color: var(--dsw-alias-label-tertiary, #8a8f96);
  font-weight: 500; font-size: 11px;
}
/* ★★ 表头的数字列必须跟数据行一样【右对齐】（2026-10-21 主人报「表格有点错位」）。
 *   原来 `th, td { text-align: left }` 统一左对齐，而 `td.num { text-align: right }`
 *   只作用于数据格 —— 于是表头「未命中 tokens」贴着左，下面 133,854 贴着右，
 *   列名和数值错开一整列宽。这是**对齐规则写在了两处、只改了一处**的典型。
 *   加两给 `th` 一条：凡是数字列（和 td.num 同位置）就右对齐。
 *   ★ 用 :not(:first-child) 而不是 nth-child(n+2)：列数会变（现在是 5 列），
 *     写死数字下次加列就又错了。判据：**第一列是文字（时刻），其余一律数字**。 */
.ua-ctbl th:not(:first-child) { text-align: right; }
.ua-ctbl tbody tr:last-child td { border-bottom: 0; }
.ua-ctbl td.num { text-align: right; font-variant-numeric: tabular-nums; }
.ua-ctbl td.s { color: var(--ua-miss); font-weight: 600; }
.ua-ctbl td.x { color: var(--dsw-alias-state-error-primary, #e05252); font-weight: 600; }
.ua-ctbl td.muted { color: var(--dsw-alias-label-tertiary, #8a8f96); }

.ua-cnote {
  margin-top: 10px; font-size: 11px; line-height: 1.7;
  color: var(--dsw-alias-label-tertiary, #8a8f96);
}

/* ── 用量面板 · 四段堆叠条（2026-10-21）───────────────────────────
 *
 * ★ 颜色定义在 .ua / .ws 上（而不是全局 :root）：这套色只服务这两块，
 *   放到全局会跟别处的语义撞（比如绿色的含义到处都是）。
 * ★ 亮暗各一套：暗色用亮一点的、亮色用深一点的 —— 同一组值硬套两边，
 *   总有一边对比度不够（市场那边 10-20 踩过：硬编码亮色放暗色页上，三块白斑）。
 * ★★ 2026-10-21：「缓存掉点」整块从 .ua（/usage 面板）搬到了 .ws（/status 面板），
 *   但变量只定义在 .ua 上 —— 于是 .ua-cchart 里四档柱子【全部透明】，
 *   只有带 fallback 的红色那档显示出来（主人报「只显示了红色」）。
 *   同族：10-20 的 --card-bg 断链（定义在 :root、用在 body 后代）。
 *   ★ 判据：**变量定义在哪，和它在哪被用，是两件事** —— 搬 DOM 结构时
 *     必须连带检查它依赖的变量还在不在作用域里。
 */
.ua, .ws {
  --ua-hit:   #4176e6;
  --ua-miss:  #e8c84a;
  --ua-out:   #5ab49b;
  --ua-reas:  #8b7de0;
  --ua-other: #8a8f96;

  /* ★ 2026-10-21 主人定：缓存掉点的三档改成 —— 低=绿 · 中=蓝（原来低用的那个） · 高=红（保持）
   *   单独开三个变量，不复用 --ua-hit / --ua-miss：
   *   那几个在 /usage 面板里表达的是「输入命中 / 输入未命中」，语义不同。
   *   ★ 判据：**同一个颜色在两个面板里可以长得一样，但名字不能借** ——
   *     借了之后改一边就会连带改坏另一边（今天 --ua-miss 变黄时 /usage 也一起变了）。 */
  --ua-c-ok:  #4ec97f;
  --ua-c-mid: #4176e6;
  --ua-c-bad: #e05252;

  /* 调用频率块（与失效柱同图，从顶部往下挂的那个灰色块）。
   * ★ 2026-10-21 补定义 —— 之前只在用点上写了 fallback，从没定义过，
   *   亮色下也就一直是暗色那个值（css_vars.py 扫出来的）。 */
  --ua-freq: rgba(140,150,165,.22);
}
html[data-theme="light"] .ua,
html[data-theme="light"] .ws {
  --ua-hit:   #2b5fc7;
  --ua-miss:  #a37a00;      /* 亮底上纯黄只有 1.8:1，压成暗金 ≈ 4.6:1 */
  --ua-out:   #2c8168;
  --ua-reas:  #6355bd;
  --ua-other: #6b7280;

  /* 亮底上三个都要压深，否则对比度不够（图形元素 AA 要求 3:1） */
  --ua-c-ok:  #16a35c;
  --ua-c-mid: #2b5fc7;
  --ua-c-bad: #d92b2b;

  --ua-freq: rgba(100,110,130,.16);
}

/* 条的外层：只负责"占位 + 裁切"，宽度由内联 style 给 */
.bl-bar {
  display: flex;
  height: 100%;
  overflow: hidden;
  border-radius: 3px;
  transition: width .35s cubic-bezier(.4, 0, .2, 1);
}

/* 段：宽度按各段数值占比。
 * ★ min-width 是给"太小的段"保底 —— 未命中常年只有 1%，不给至少 1px 会彻底消失，
 *   而它"存在"这件事比"它多宽"更重要（宽度看不出来时右边还有数字）。 */
.bl-seg {
  display: block;
  height: 100%;
  flex: 0 0 auto;
  min-width: 1.5px;
}
.bl-seg.hit   { background: var(--ua-hit,   #4176e6); }
.bl-seg.miss  { background: var(--ua-miss,  #e8913a); }
.bl-seg.out   { background: var(--ua-out,   #5ab49b); }
.bl-seg.reas  { background: var(--ua-reas,  #8b7de0); }
.bl-seg.other { background: var(--ua-other, #8a8f96); }

/* 多列（barList 的 cols）—— 左右各占一半（主人 2026-10-21 定）
 *
 * ★★ 为什么是"两个等分容器"而不是"track 吃剩余 + 列按内容宽"：
 *   表头那行 track 里【没有内容】，数据行 track 里【有条】——
 *   两者算出来的 flex-basis 不同 → 实测表头 track 839px、数据行 149px，
 *   右边的列就整体错位（10-15 左移 150px，10-18 左移 353px，只有 100% 那天碰巧对上）。
 *   改成两个 `flex: 1 1 0` 的容器后，表头和数据行的结构【完全一致】，对齐是天然的。
 */
.bl-row.with-cols { gap: 18px; }
.bl-l { flex: 1 1 0; min-width: 0; display: flex; align-items: center; gap: 8px; }
.bl-r { flex: 1 1 0; min-width: 0; display: flex; align-items: center; }
.bl-row.with-cols .bl-c {
  flex: 1 1 0;
  min-width: 0;
  text-align: right;
  color: var(--dsw-alias-label-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 第一列（命中）留一点左内边距，跟左边那条条隔开 */
.bl-row.with-cols .bl-r .bl-c:first-child { padding-left: 6px; }
.bl-head { padding-bottom: 4px; margin-bottom: 2px; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.08)); }
.bl-head .bl-c { color: var(--dsw-alias-label-tertiary, #8a8f96); font-size: 11px; }

/* 图例 */
.ua-lg {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 11px;
  color: var(--dsw-alias-label-tertiary, #8a8f96);
  margin-bottom: 8px;
}
.ua-lg span.sw {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 2px;
  margin-right: 4px;
  vertical-align: -1px;
}
.ua-lg span.sw.hit  { background: var(--ua-hit); }
.ua-lg span.sw.miss { background: var(--ua-miss); }
.ua-lg span.sw.out  { background: var(--ua-out); }
.ua-lg span.sw.reas { background: var(--ua-reas); }
.ua-lgv { margin-left: auto; opacity: .8; }

.ua-sw { color: var(--dsw-alias-label-primary); }

/* ── 今日来源：一整条堆叠 + 下面一行图例（2026-10-21 D4 稿）───────
 *
 * ★ 为什么是"一整条"不是"每行一根"：
 *   这一段回答的是「今天这份钱怎么分的」，一整条切成几段，谁占大头一眼就出来。
 *   每行一根条要拿眼去比长度，还占五倍高度（而且那正是下面【每日消耗】的形态，
 *   两段长得一样会让人以为是同一件事）。
 */
.ua-split {
  display: flex;
  height: 22px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--dsw-alias-interactive-bg-hover);
}
.ua-sg { display: block; height: 100%; flex: 0 0 auto; min-width: 2px; }
.ua-sg.hit   { background: var(--ua-hit); }
.ua-sg.miss  { background: var(--ua-miss); }
.ua-sg.out   { background: var(--ua-out); }
.ua-sg.reas  { background: var(--ua-reas); }
.ua-sg.other { background: var(--ua-other); }

.ua-lg2 {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-top: 8px;
  font-size: 12px;
  color: var(--dsw-alias-label-secondary);
}
.ua-lg2 span { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
.ua-lg2 .dot {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 2px;
}
.ua-lg2 .dot.hit   { background: var(--ua-hit); }
.ua-lg2 .dot.miss  { background: var(--ua-miss); }
.ua-lg2 .dot.out   { background: var(--ua-out); }
.ua-lg2 .dot.reas  { background: var(--ua-reas); }
.ua-lg2 .dot.other { background: var(--ua-other); }
.ua-lg2 b { color: var(--dsw-alias-label-primary); font-weight: 600; font-variant-numeric: tabular-nums; }
.ua-lg2 em { font-style: normal; color: var(--dsw-alias-label-tertiary, #8a8f96); font-variant-numeric: tabular-nums; }

/* 页脚：分类合计表，一条细线隔开 */
.ua-foot {
  padding-top: 10px;
  margin-top: 4px;
  border-top: 1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.08));
  font-size: 11px;
  color: var(--dsw-alias-label-tertiary, #8a8f96);
}
/* ★ 六列，跟上面「每日消耗」的 barList 同构（1 个日期列 + 6 个数据列）——
   第一列给个和日期列差不多的宽度，右边六列等分，视觉上就和上面的表对齐了。 */
.ua-sum {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
.ua-sum th,
.ua-sum td { text-align: right; font-weight: 400; padding: 2px 0; }
.ua-sum th:first-child {
  text-align: left;
  width: 42px;
}
.ua-sum thead th { color: var(--dsw-alias-label-tertiary, #8a8f96); }
.ua-sum tbody th { color: var(--dsw-alias-label-secondary, #a8adb4); }
.ua-sum tbody td {
  color: var(--dsw-alias-label-primary, #e6e8ea);
  font-variant-numeric: tabular-nums;
}
/* 表格里第 1 个数据格（命中）离日期列远一点 —— 跟上面条形图的起点对齐 */
.ua-sum thead th:nth-child(2),
.ua-sum td:first-child { padding-left: 10px; }

/* 标题栏右侧的日期副标题 */
.ua-sub {
  margin-left: 10px;
  font-size: 11.5px;
  color: var(--dsw-alias-label-tertiary, #8a8f96);
}



.ua-note {
  font-size: 11px;
  line-height: 1.7;
  color: var(--dsw-alias-label-secondary);
}

/* 窄屏：KPI 折成两行两列 */
@media (max-width: 620px) {
  .ua-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ua-kv { font-size: 15px; }
}

/* ─────────── 状态面板：环形 / 堆叠 / 预算条（2026-10-21）───────────
 *
 * ★ 为什么环和条都自己画，不用 sdk 的 barList：
 *   ① 环不是条（barList 表达的是"长度"）
 *   ② 堆叠条要 4 段着色 + 图例，barList 的 segs 是给"每行一根"用的
 *   → 形态不同就不硬塞，等第三个使用者出现再提。
 *
 * ★ 颜色定义在 .ws 上（不是 :root）：这套色只服务这一个面板，
 *   放全局会跟别处语义撞（绿色在 snow 里到处有用）。
 *   亮暗两套值 —— market 那边 10-20 踩过「硬编码亮色放暗色页上」。
 */

/* 槽位里那个小环 —— 替掉「上下文」三个字（主人 2026-10-21 提的） */
.st-ring {
  position: relative; display: inline-block;
  width: 15px; height: 15px; flex: none;
}
.st-ring svg { display: block; width: 15px; height: 15px; transform: rotate(-90deg); }
.st-ring b {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 7.5px; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.st-ring .tr { stroke: currentColor; opacity: .18; }
/* ★ 三档配色（主人 2026-10-21 定）：<50 蓝 · 50~80 橙 · ≥80 红。
 *   ★ 为什么默认（不加类）就是蓝：蓝色是"正常"，它该是常态，不该靠一个类来表示。
 *     只有异常才需要类名 —— 这样漏写类时显示的是蓝，不是黑/灰。
 *   ★ 环的 stroke 原来走 currentColor（继承 .cm-stat 的灰）—— 那样永远灰，
 *     换色必须显式给，所以这里写死三档、不再用 currentColor。 */
.st-ring .fg { stroke: var(--dsw-alias-brand-primary-new-color, #4176e6); }
.st-ring b   { color: var(--dsw-alias-brand-primary-new-color, #4176e6); }
.st-ring.lv2 .fg { stroke: var(--dsw-alias-state-warn-label); }
.st-ring.lv2 b   { color: var(--dsw-alias-state-warn-label); }
.st-ring.lv3 .fg { stroke: var(--dsw-alias-state-error-primary, #e05252); }
.st-ring.lv3 b   { color: var(--dsw-alias-state-error-primary, #e05252); }
/* ★ 亮色下把蓝压深一档：`--dsw-alias-brand-primary-new-color` 在亮暗【同一个值】
 *   （实测两套都是 rgb(65,118,230)），它没跟随主题。而同一个蓝放在白底上，
 *   环里 7.5px 的数字对比度只有约 4.0:1 —— 低于文字要求的 4.5。
 *   橙和红不用管：它们走 --dsw-alias-state-*，那两个变量自带两套值。
 *   ★★ `:not(.lv2):not(.lv3)` 不能省（实测踩到）：`html[data-theme="light"] .st-ring .fg`
 *   的特异性 (0,3,1) 高于 `.st-ring.lv2 .fg` 的 (0,3,0) —— 不加排除，
 *   亮色下三个档全变成蓝，橙和红的那两档直接失效（而且不报错）。 */
html[data-theme="light"] .st-ring:not(.lv2):not(.lv3) .fg { stroke: #2b5fc7; }
html[data-theme="light"] .st-ring:not(.lv2):not(.lv3) b   { color: #2b5fc7; }

/* 槽位三段之间的分隔 —— 原来是个 "/"，太扎眼（主人 2026-10-21 说不好看） */
.cm-stat .sep { opacity: .28; margin: 0 3px; font-style: normal; }

.ws { --ws-sys: rgb(65,118,230); --ws-tool: rgb(232,145,58);
      --ws-mid: rgb(90,180,155); --ws-buf: rgb(155,120,220);
      --ws-ok: rgb(90,180,155); --ws-warn: rgb(232,145,58); --ws-bad: rgb(220,95,95);
      --ws-track: rgba(255,255,255,.07); }
html[data-theme="light"] .ws {
      --ws-sys: rgb(45,95,200); --ws-tool: rgb(190,110,25);
      --ws-mid: rgb(35,135,105); --ws-buf: rgb(115,80,180);
      --ws-ok: rgb(30,125,85); --ws-warn: rgb(180,105,20); --ws-bad: rgb(190,60,60);
      --ws-track: rgba(38,49,72,.08); }

/* 环 + 合计 */
.ws-top { display: flex; align-items: center; gap: 18px; margin: 14px 0 0; }
.ws-ring { position: relative; width: 78px; height: 78px; flex: none; }
.ws-ring svg { display: block; width: 78px; height: 78px; transform: rotate(-90deg); }
.ws-ring .tr { stroke: var(--ws-track); }
.ws-ring .fg { stroke: var(--ws-sys); stroke-linecap: round; transition: stroke-dasharray .3s; }
.ws-ring .c {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.ws-ring .n { font-size: 18px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }
.ws-ring .u { font-size: 9.5px; opacity: .55; margin-top: 3px; }
.ws-big { font-size: 15.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ws-big em { font-style: normal; font-size: 11.5px; font-weight: 400; opacity: .55; margin-left: 5px; }
.ws-note { font-size: 10.5px; opacity: .5; margin-top: 4px; }

/* 「缓存掉点」那块的天数切换（2026-10-21 从 /usage 搬来时加的）
   ★ 跟 .ua-tabs 一个样式语言，但小一号 —— 它在 /status 面板里是个次级控件，
     不该跟上面「上下文」那些主内容抢视觉重量。 */
.ws-days { display: inline-flex; gap: 4px; margin-left: 10px; vertical-align: middle; }
.ws-dtab {
  font: inherit; font-size: 10.5px; line-height: 1;
  padding: 4px 9px; border-radius: 7px; cursor: pointer;
  color: var(--dsw-alias-label-secondary);
  background: transparent; border: 1px solid transparent;
}
.ws-dtab:hover { background: var(--dsw-alias-interactive-bg-hover); }
.ws-dtab.on {
  /* ★ 用 .ua-tab.on 那一套（brand 色 + 12% 底）——
     原先我写的是 --dsw-alias-interactive-bg-select，那个变量【根本不存在】，
     整条声明会静默失效（跟 10-20 那个 --card-bg 断链同一个坑）。 */
  color: var(--dsw-alias-brand-text, #6aa9ff);
  background: rgba(65, 118, 230, .12);
  border-color: var(--dsw-alias-brand-text, #6aa9ff);
}

/* 四段堆叠 + 图例 */
.ws-stack {
  display: flex; height: 11px; border-radius: 5px; overflow: hidden;
  margin: 13px 0 10px; background: var(--ws-track);
}
.ws-stack i { display: block; height: 100%; }
.ws-lg { display: flex; flex-wrap: wrap; gap: 5px 16px; font-size: 11.5px; }
.ws-lg span { display: flex; align-items: center; gap: 5px; }
.ws-lg .dot { width: 7px; height: 7px; border-radius: 2px; flex: none; }
.ws-lg b { font-weight: 600; font-variant-numeric: tabular-nums; }
.ws-lg em { font-style: normal; opacity: .55; font-size: 10.5px; }
.ws-lg .w { opacity: .45; font-size: 10.5px; }

/* 预算条：标签 / 轨道 / 百分比 / 用量 ——— 四段对齐 */
.ws-bud { display: flex; align-items: center; gap: 9px; font-size: 11.5px; margin-bottom: 8px; }
.ws-bud .lb { width: 52px; flex: none; opacity: .8; }
.ws-bud .tk { flex: 1; height: 7px; background: var(--ws-track); border-radius: 4px; overflow: hidden; min-width: 0; }
.ws-bud .tk i { display: block; height: 100%; border-radius: 4px; transition: width .3s; }
.ws-bud .pv { width: 46px; flex: none; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.ws-bud .pj { width: 128px; flex: none; text-align: right; font-size: 10.5px; opacity: .55;
              font-variant-numeric: tabular-nums; }
.ws-bud .hint { width: 66px; flex: none; text-align: right; font-size: 10.5px; opacity: .6; }

.ws-warn {
  background: color-mix(in srgb, var(--ws-warn) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--ws-warn) 30%, transparent);
  border-radius: 8px; padding: 8px 11px; font-size: 11.5px;
  color: var(--ws-warn); margin-top: 10px;
}

/* ─────────── 窄屏：/usage 和 /status 面板（2026-10-21）───────────
 *
 * ★ 为什么集中在这里、挂在 620 断点：
 *   这两个面板都是 10-21 加的，内容宽度是按桌面算的（六列表格 ~740、逐次柱图 ~736）。
 *   手机上（面板约 420）**数学上放不下**，整页会被撑出横向滚动。
 *
 * ★ 两条不同的解法：
 *   ① 表格 → 横向滚动 + 首列 sticky（冻结"日期/时刻"，右滑看数字列）
 *      比"把六列折成两行"稳妥：折行要重排 DOM，而且列数一变就得重写；
 *      横滚是 CSS，列数怎么加都不用动。
 *   ② 柱状图 → JS 里按小时聚合（见 status.js 的 narrow 参数）。
 *      CSS 做不到"把 736 个元素合并成 7 个"。
 */
@media (max-width: 620px) {
  /* 面板本身：边距收紧一点，把宽度让给内容 */
  #modal { padding: 12px; }
  #modal h2 { font-size: 14px; }

  /* ① 明细表横滚 + 首列冻结 */
  .ua-ctab, .bl-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .ua-ctbl, .bl-row.with-cols { min-width: 560px; }   /* 少于这个宽度就横滑，别硬挤 */

  .ua-ctbl th:first-child, .ua-ctbl td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--dsw-alias-bg-layer-2, #232324);   /* ★ 必须不透明，否则数字从底下透上来 */
    box-shadow: 1px 0 0 var(--dsw-alias-border-l1);
  }
  .ua-ctbl thead th:first-child { z-index: 2; }         /* 表头要压住数据行的首列 */

  /* ② KPI 六项：两列（原来三列太挤） */
  .ua-ckpi { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* ③ 图例折行 */
  .ua-cleg { flex-wrap: wrap; gap: 4px 12px; }
  .ua-cleg > span { font-size: 11px; }

  /* ④ 说明文字小一号 */
  .ua-cnote { font-size: 11px; line-height: 1.6; }

  /* ★★ ⑤ 滑窗预算那两行：改成三行堆叠（2026-10-21 主人报"手机上顶开了"）
   *
   *   原结构是【一行五个 flex 子项】，其中四个是固定宽：
   *     .lb 52 · .pv 46 · .pj 128 · .hint 66 = 292px 固定 + gap 36 = 328px
   *   只有 .tk（进度条）能收缩 —— 面板内容宽掉到 358 时进度条只剩 30px，
   *   再窄就直接撑破容器。桌面上 672 宽刚好够，所以一直没暴露。
   *
   *   ★ 改法是【换行 + order】，不是缩字号：数字是这个面板的核心信息，不该被缩。
   *     第一行：buffer ................ 255.0%   （label 靠左，百分比靠右）
   *     第二行：▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  （进度条占满整宽）
   *     第三行：26.7K / 10.5K ... 快满了
   */
  .ws-bud { flex-wrap: wrap; gap: 3px 8px; }
  .ws-bud .lb   { order: 1; }
  .ws-bud .pv   { order: 2; margin-left: auto; }   /* 顶到右边，跟 label 分居两端 */
  .ws-bud .tk   { order: 3; flex: 0 0 100%; }      /* 进度条独占一行 */
  .ws-bud .pj   { order: 4; flex: 1 1 auto; }
  .ws-bud .hint { order: 5; flex: 0 0 auto; }
}

/* provider 元素的 role 输入（2026-10-22）
   ★ 跟 vendor 下拉框并排，所以要窄一点 —— 太长会把名字挤没。 */
.ce-role {
  flex: 0 0 auto;
  width: 9em;
  padding: 2px 6px;
  border: 1px solid var(--line, #d8dbe3);
  border-radius: 6px;
  background: var(--input-bg, #fff);
  color: inherit;
  font: inherit;
  font-size: 12px;
}
.ce-role::placeholder { color: var(--muted, #8a8f9c); }
.ce-role:focus { outline: 2px solid var(--accent, #6aa9ff); outline-offset: 1px; }

/* ═══════════════ LaTeX「伪 PDF」预览（2026-10-23）═══════════════
 *
 * 主人 15:21：「tex 文件纯前端渲染成类 PDF 可行吗？」
 *      15:31：「3、先实现 C 的功能」（C = 对照读：看 PDF 想找源码）
 *
 * ★ 这不是 PDF，是把 LaTeX 子集摆成 A4 的样子。参数全按 article 10pt 换算：
 *     纸 794px = 210mm@96dpi · 边距 96px ≈ 25mm · 正文 604px ≈ 160mm
 *     字号 13.33px = 10pt · 行距 16px = \baselineskip 12pt · 首行缩进 2em
 *   → 「远看像论文」成立；断行位置 / 分页 / 浮动体位置和真 LaTeX 一定不同。
 *
 * ★★ 纸永远是【白底黑字】（跟真 PDF 一样），不跟随暗色主题 ——
 *   跟随主题会让"这份文档长什么样"这个判断失真。
 */
.texv {
  margin-top: 10px;
  border: 1px solid var(--dsw-alias-border-l1);
  border-radius: 8px;
  overflow: hidden;
  background: var(--dsw-alias-bg-layer-1);
}
/* 工具条直接复用 .jt-bar / .jt-sw / .jt-tag —— 跟 JSON 展示器一个观感 */

.texv-body { display: flex; align-items: stretch; min-height: 0; }

/* ── 视图切换：三个视图 = 两个 pane 的组合 ──
 * ★ 用 data-view 而不是 hidden：hidden 会被作者写的 display 压过（踩过）。 */
.texv-pane { min-width: 0; max-height: 72vh; overflow: auto; }
.texv[data-view="pdf"] .texv-pane-pdf { flex: 1 1 auto; }
.texv[data-view="pdf"] .texv-pane-src { display: none; }
.texv[data-view="raw"] .texv-pane-src { flex: 1 1 auto; }
.texv[data-view="raw"] .texv-pane-pdf { display: none; }
.texv[data-view="split"] .texv-pane-src {
  /* ★ 宽度用 CSS 变量，拖动分隔条时只改变量（见 app.js texBindDivider）。
     缺省 42%，左右各留一点以免窄到看不见。 */
  flex: 0 0 calc(var(--texv-split, 42) * 1%);
  border-right: 0;
}
.texv[data-view="split"] .texv-pane-pdf {
  flex: 1 1 auto;
}

/* ── 对照视图的分隔条 ──
 * 视觉只 5px，但用 ::before 把可点区域左右各扩 3px（总共 11px），
 * 鼠标不用对得很准。★ 不加这个，5px 的条在触控板上几乎抓不住。 */
.texv-div { display: none; }
.texv[data-view="split"] .texv-div {
  display: block;
  position: relative;
  flex: 0 0 5px;
  cursor: col-resize;
  background: var(--dsw-alias-border-l1);
  touch-action: none;            /* 触屏拖动时别让浏览器滚页面 */
}
.texv-div::before {
  content: '';
  position: absolute;
  inset: 0 -3px;
}
.texv-div:hover, .texv-div.on { background: var(--dsw-alias-interactive-bg-hover); }

/* ── 源码 pane ── */
.texv-src {
  font-family: var(--ds-font-family-code);
  font-size: 11.5px;
  line-height: 1.6;
  padding: 4px 0 8px;
  overflow-x: auto;
}
.texv-l {
  display: flex;
  white-space: pre;
  cursor: pointer;
  border-radius: 0;
}
.texv-l:hover { background: rgba(127, 127, 127, .08); }
/* 行号 sticky：横向滚的时候行号留在左边 */
.texv-no {
  position: sticky;
  left: 0;
  flex: none;
  width: 3.4em;
  padding-right: .7em;
  text-align: right;
  color: var(--dsw-alias-label-caption);
  background: var(--dsw-alias-bg-layer-1);
  user-select: none;
}
.texv-l:hover .texv-no { background: var(--dsw-alias-bg-layer-2); }
.texv-c { flex: 1 1 auto; }
/* ★ 跳到哪一行 / 哪一块：两个 .on 是配对的，同一次点击一起亮 */
.texv-l.on { background: rgba(90, 160, 255, .18); }
.texv-l.on .texv-no { background: rgba(90, 160, 255, .28); color: var(--dsw-alias-label-primary); }

/* ── 排版 pane ── */
.texv-pane-pdf {
  background: #e9e9ec;
  padding: 12px;
  display: flex;
  justify-content: flex-start;
  /* ★ 必须 flex-start 覆盖默认的 stretch —— 否则纸被拉成容器高（72vh），
     内容 2000px 溢出但纸只有 300px 高，看起来"只有半页"。2026-10-23 踩到。 */
  align-items: flex-start;
}
.tex-blk.on { outline: 2px solid #3b82f6; outline-offset: 3px; }
.tex-blk { cursor: pointer; }

/* ── A4 纸 ── */
.tex-paper {
  flex: 0 0 auto;               /* 同上：不参与伸缩 */
  width: 794px;                 /* 210mm @96dpi */
  box-sizing: border-box;
  padding: 96px;                /* 25mm */
  margin: 0 auto;
  background: #fff;
  color: #111;
  font-family: "KaTeX_Main", "SimSun", "Songti SC", "Noto Serif CJK SC", serif;
  font-size: 13.33px;           /* 10pt */
  line-height: 16px;            /* \baselineskip = 12pt */
  text-align: justify;
  transform-origin: top left;
  -webkit-font-smoothing: antialiased;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .18);
}

/* 标题 */
.tex-paper .tex-title { font-size: 17.3px; font-weight: 700; text-align: center; margin: 0 0 10px; line-height: 1.3; }
.tex-paper .tex-author { text-align: center; font-size: 14.7px; margin: 8px 0 4px; }
.tex-paper .tex-date { text-align: center; font-size: 14.7px; margin: 4px 0 22px; }

/* 摘要 */
.tex-paper .tex-abstract { margin: 0 32px 18px; font-size: 12px; }
.tex-paper .tex-abs-title { text-align: center; font-weight: 700; margin-bottom: 3px; }

/* 章节 */
.tex-paper .tex-h2 { font-size: 14.7px; font-weight: 700; margin: 17px 0 7px; line-height: 1.3; }
.tex-paper .tex-h3 { font-size: 13.33px; font-weight: 700; margin: 12px 0 5px; }

/* 段落：LaTeX 段间不空行，靠首行缩进区分 */
.tex-paper p { margin: 0; text-indent: 2em; text-align: justify; }
.tex-paper .tex-abstract p { text-indent: 0; }

/* 列表 */
.tex-paper .tex-list { margin: 4px 0; padding-left: 2.4em; }
.tex-paper .tex-list > li { margin: 1px 0; }
.tex-paper .tex-list .tex-list { margin: 1px 0; }

/* 定理 / 证明 */
.tex-paper .tex-thm { margin: 7px 0; font-style: italic; }
.tex-paper .tex-thm-head { font-style: normal; font-weight: 700; margin-right: .3em; }
.tex-paper .tex-proof { margin: 7px 0; text-indent: 2em; }
.tex-paper .tex-qed { float: right; }

/* 表格（三线表：上下粗线 + 表头下细线） */
.tex-paper .tex-float { margin: 12px 0; text-align: center; }
.tex-paper .tex-float-body { display: inline-block; max-width: 100%; }
.tex-paper .tex-float-cap { font-size: 12px; margin-top: 6px; }
.tex-paper .tex-tabular {
  border-collapse: collapse; margin: 0 auto;
  border-top: 1.4px solid #111; border-bottom: 1.4px solid #111;
}
.tex-paper .tex-tabular th,
.tex-paper .tex-tabular td { padding: 2px 12px; text-align: left; font-weight: normal; }
.tex-paper .tex-tabular th { font-weight: 700; }
.tex-paper .tex-tabular tr.tex-rule { border-top: .8px solid #111; }
.tex-paper .tex-tabular tr.tex-rule th,
.tex-paper .tex-tabular tr.tex-rule td { padding-top: 3px; }
.tex-paper .tex-fig-ph { color: #888; padding: 22px 60px; border: 1px dashed #bbb; }
.tex-paper .tex-fig-raw { font-size: 10px; color: #999; margin-top: 6px; word-break: break-all; }

/* 算法 */
.tex-paper .tex-alg {
  margin: 12px 0; padding: 6px 0;
  border-top: 1px solid #111; border-bottom: 1px solid #111;
}
.tex-paper .tex-alg-cap { font-weight: 700; margin-bottom: 4px; }
.tex-paper .tex-alg-body {
  font-family: var(--ds-font-family-code);
  font-size: 11px; line-height: 15px; margin: 0;
  white-space: pre; overflow-x: auto;
}

/* 参考文献 */
.tex-paper .tex-bib { font-size: 12px; }
.tex-paper .tex-bibitem { text-indent: -1.8em; padding-left: 1.8em; margin: 2px 0; text-align: left; }

/* 引用 / 未知标记 —— ★ 未知命令必须可见（静默吞掉 = 人不知道少了东西） */
.tex-paper .tex-unknown { color: #c00; font-family: var(--ds-font-family-code); font-size: .92em; }
.tex-paper .tex-unknown-env { color: #c00; border: 1px dashed #c00; padding: 2px 4px; display: block; }
.tex-paper .tex-math-err { color: #fff; background: #c00; padding: 0 3px; font-size: .85em; }
.tex-paper .tex-verbatim { font-family: var(--ds-font-family-code); font-size: 11px; white-space: pre-wrap; }

/* KaTeX 微调：行内公式别把行距撑开 */
.tex-paper .katex { font-size: 1.08em; }
.tex-paper .katex-display { margin: .55em 0; }

/* ★★ 行内公式【不可断行】（2026-10-23 主人报的：B = ⟨m₁,…,mₙ⟩ 被断成两截 ——
 *   ⟨m₁… 在上一行、⟩ 掉到下一行跟"设缓冲区为序列 B ="叠在一起）。
 *
 *   根因：KaTeX 的 .katex 没设 display（默认 inline），内部是一串 inline span ——
 *   浏览器可以在任意两个 inline 元素之间断行，公式就被劈开。
 *
 *   ★ 第一版我用的 display:inline-block，那是【错的】：
 *     inline-block 会把公式变成一个独立行盒，KaTeX 那套靠 inline + strut 撑出来的
 *     高度/基线就失效了 —— 实测含下标的公式盒子高 37px 而内容只有 13px，
 *     上方空出 19px，反而把文字顶歪（第 66-68 行块里 4 个公式的 top 从 826 到 863
 *     散成四行）。断行照旧没解决。
 *
 *   ★ 正确修法：white-space: nowrap —— 它禁掉的是【所有软换行机会】
 *     （UAX #14 的断行点，含 inline 元素边界），而不改变盒子类型，
 *     KaTeX 的基线和 strut 全部照旧。
 *     （我一开始以为 nowrap 只禁空白处的断行 —— 那是 word-break/overflow-wrap 的范畴，记错了。）
 *
 *   ★ 代价：超长公式会向右溢出纸面（跟 LaTeX 的 overfull hbox 一致）。 */
.tex-paper .katex { white-space: nowrap; }

/* 行间公式 + 右侧编号（LaTeX article 的 equation 自动编号） */
.tex-paper .tex-eq {
  position: relative;
  margin: .55em 0;
  text-align: center;
}
.tex-paper .tex-eq .katex-display { margin: 0; }
.tex-paper .tex-eq-no {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  font-size: 13.33px;
}

/* 工具条上那个「渲染不全」的警告 —— 跟 yaml 的 ? 是同一类提醒，但要更醒目 */
.tex-warn {
  color: var(--dsw-alias-state-error-primary);
  border-color: currentColor;
}

/* ─────────── PDF 预览（2026-10-23）─────────── */
/* 工具条沿用 .jt-bar 那套观感；页码/缩放按钮用文字，跟 .md-codefoot 的「复制」一致 */
.pdf-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  background: var(--dsw-alias-bg-layer-2);
  border: 1px solid var(--dsw-alias-border-l1);
  border-radius: 6px;
  font-size: 11.5px;
  margin-bottom: 6px;
}
.pdf-pg, .pdf-zv { color: var(--dsw-alias-label-tertiary); }
/* ★ 它是个按钮（点一下回自适应），但要长得像文字 —— 不然工具条上多出一个"框" */
.pdf-zv {
  min-width: 46px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font: inherit;
  font-size: 11.5px;
  line-height: 1;
  padding: 3px 4px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  color: var(--dsw-alias-label-tertiary);
  cursor: pointer;
}
.pdf-zv:hover {
  background: var(--dsw-alias-interactive-bg-hover);
  color: var(--dsw-alias-label-secondary);
}
.pdf-z {
  font: inherit;
  line-height: 1;
  padding: 2px 7px;
  border: 1px solid var(--dsw-alias-border-l1);
  border-radius: 4px;
  background: var(--dsw-alias-bg-layer-1);
  color: var(--dsw-alias-label-secondary);
  cursor: pointer;
}
.pdf-z:hover { background: var(--dsw-alias-interactive-bg-hover); }

/* 页面容器：灰底 + 每页白纸带阴影，跟成品阅读器一个观感 */
.pdf-pages {
  max-height: 72vh;
  overflow: auto;
  background: var(--dsw-alias-bg-layer-2);
  border-radius: 6px;
  padding: 8px;
}
.pdf-page {
  /* ★ min-height 给占位一个体积，否则空 div 高度 0，IO 永远不触发（踩过类似的） */
  min-height: 40px;
  margin: 0 auto 10px;
  width: fit-content;
  max-width: 100%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .18);
  border-radius: 2px;
  overflow: hidden;
}
.pdf-page:last-child { margin-bottom: 0; }
.pdf-page canvas { display: block; }

/* 窄容器：对照视图退化成上下叠（左右各 168px 谁都读不了） */
@media (max-width: 760px) {
  .texv[data-view="split"] .texv-body { flex-direction: column; }
  .texv[data-view="split"] .texv-pane-src {
    /* ★ 必须覆盖掉上面的百分比宽度 —— 在 column 方向里 flex-basis 是【高度】，
       留着 calc(42%) 会让源码 pane 占 42% 高度，跟 max-height 打架。 */
    flex: 1 1 auto;
    border-bottom: 1px solid var(--dsw-alias-border-l1);
    max-height: 36vh;
  }
  /* 上下叠时分隔条没意义（拖的是高度，手势会和滚动打架）——藏掉，用固定 50/50 */
  .texv[data-view="split"] .texv-div { display: none; }
  .texv[data-view="split"] .texv-pane-pdf { max-height: 36vh; }
}

/* ════════════════════════════════════════════════════════════════════
   SKIN —— 皮肤（2026-10-23）
   ────────────────────────────────────────────────────────────────────
   ★ 原理：vendor 里的 190 个 --dsw-alias-* 全是 var(--dsw-static-*) 的引用。
     所以皮肤【只覆写底层色阶】，上面那一层自动跟着变 —— 一个皮肤 ≈ 25 行，
     不用重写 190 个变量，也【一行 vendor 都不用改】。

   ★ 挂法：<html data-theme="light|dark" data-skin="sea">。
     data-theme 决定用哪套 alias 基线，data-skin 只做覆写。
     ★ 2026-10-23 改成【双条件】：每套皮肤都有亮/暗两块。
     特异性 (0,2,2) 比 vendor 的 html[data-theme=...] body (0,1,2) 高，
     所以不依赖加载顺序 —— 但仍放在 style.css 末尾，别往前挪。

   ★ 加一个新皮肤：色阶 00→1000 + 强调色，亮/暗各复制一块改色值即可。
     00=最浅面 50/60/75=浅色块 100/150=预览/浮层 200/300=边框
     400/500/600=三级文字 700=次要文字 950/1000=主文字
     （dark 基底是镜像：950=面，50=主文字）
   ════════════════════════════════════════════════════════════════════ */

/* ── 墨·深海 | 亮版：暖白纸 + 墨绿 ───────────────────────────────── */
html[data-skin="sea"][data-theme="light"] body {
  --dsw-static-neutral-bluish-00:   #fbfbf8;
  --dsw-static-neutral-bluish-50:   #f6f6f1;
  --dsw-static-neutral-bluish-60:   #f1f1ea;
  --dsw-static-neutral-bluish-75:   #ecece4;
  --dsw-static-neutral-bluish-100:  #e7e7de;
  --dsw-static-neutral-bluish-150:  #e4e3da;
  --dsw-static-neutral-bluish-200:  #dcdcd1;
  --dsw-static-neutral-bluish-300:  #cecdc0;
  --dsw-static-neutral-bluish-400:  #b0afa0;
  --dsw-static-neutral-bluish-500:  #8d8c7e;
  --dsw-static-neutral-bluish-600:  #6c6b5f;
  --dsw-static-neutral-bluish-700:  #4c4b42;
  --dsw-static-neutral-bluish-800:  #393831;
  --dsw-static-neutral-bluish-950:  #282720;
  --dsw-static-neutral-bluish-1000: #1e1d18;
  --dsw-static-neutral-200:         #dcdcce;
  --dsw-static-neutral-300:         #cecebd;
  --dsw-static-deepseek-50:         #eef5f0;
  --dsw-static-deepseek-100:        #dcece3;
  --dsw-static-deepseek-300:        #a6cdbc;
  --dsw-static-deepseek-400:        #7cb79f;
  --dsw-static-deepseek-500:        #2f6b52;
  --dsw-static-deepseek-600:        #245441;
  --dsw-static-blue-500:            #3f8a6b;
  --dsw-static-blue-900:            #1f4a39;
  --dsw-static-blue-950:            #17362a;
  --dsw-static-red-600:             #ac3a33;
  --dsw-static-green-500:           #2b7d4a;
  --dsw-static-amber-500:           #b0760f;
  /* 强调色 —— vendor 里这两个 alias 是【硬编码】的，不是变量，必须显式覆盖 */
  --dsw-alias-brand-primary-new-colorprimary-new-color: #2f6b52;
  --dsw-alias-brand-primary-new-color: #2f6b52;
  /* 边界线（2026-10-23 补）—— vendor 里这几条是硬编码 rgba，不覆盖就【跟着别的主题走】。
   * 取色原则：用【本主题自己的】neutral 色阶（l1 = n100 · l2 = n200），不另外发明色值。
   * ★ 为什么必须每套都写：漏一套那套的边框就会用 vendor 的纯黑 4%，
   *   而在这个米白底上会偏冷 —— 一眼看不出错，但整体"脏"了一点。
   */
  --dsw-alias-border-l1: #e7e7de;
  --dsw-alias-border-l2: #dcdcd1;
  --dsw-alias-border-l3: #cecdc0;
}

/* ── 墨·深海 | 暗版：深青黑 + 青绿（色阶镜像）────────────────────── */
html[data-skin="sea"][data-theme="dark"] body {
  --dsw-static-neutral-bluish-00:   #ffffff;
  --dsw-static-neutral-bluish-50:   #eef5f6;
  --dsw-static-neutral-bluish-60:   #e8eff1;
  --dsw-static-neutral-bluish-75:   #dfe8ea;
  --dsw-static-neutral-bluish-100:  #d5e0e3;
  --dsw-static-neutral-bluish-150:  #cbd8db;
  --dsw-static-neutral-bluish-200:  #bfccd0;
  --dsw-static-neutral-bluish-300:  #a8bcc1;
  --dsw-static-neutral-bluish-400:  #7f959c;
  --dsw-static-neutral-bluish-500:  #5d737b;
  --dsw-static-neutral-bluish-600:  #46585f;
  --dsw-static-neutral-bluish-700:  #35464d;
  --dsw-static-neutral-bluish-750:  #2b3a41;
  --dsw-static-neutral-bluish-800:  #23303a;
  --dsw-static-neutral-bluish-850:  #1b262d;
  --dsw-static-neutral-bluish-875:  #172027;
  --dsw-static-neutral-bluish-900:  #131b21;
  --dsw-static-neutral-bluish-950:  #0f161b;
  --dsw-static-neutral-bluish-1000: #0a1014;
  --dsw-static-neutral-200:         #223039;
  --dsw-static-neutral-300:         #2c3d46;
  --dsw-static-deepseek-50:         #102421;
  --dsw-static-deepseek-100:        #143029;
  --dsw-static-deepseek-300:        #2a6b5d;
  --dsw-static-deepseek-400:        #4fcab2;
  --dsw-static-deepseek-500:        #35b39c;
  --dsw-static-deepseek-600:        #2a8f7d;
  --dsw-static-blue-500:            #3aa0c9;
  --dsw-static-blue-900:            #0e3f52;
  --dsw-static-blue-950:            #0a2c3a;
  --dsw-static-red-400:             #f2706f;
  --dsw-static-red-600:             #d94b4b;
  --dsw-static-green-500:           #35b37a;
  --dsw-static-amber-500:           #d99a2b;
  /* 强调色 —— vendor 里这两个 alias 是【硬编码】的，不是变量，必须显式覆盖 */
  --dsw-alias-brand-primary-new-colorprimary-new-color: #35b39c;
  --dsw-alias-brand-primary-new-color: #35b39c;
  /* 边界线（2026-10-23 补）—— 取本主题自己的 neutral 色阶（l1 = n800 · l2 = n750） */
  --dsw-alias-border-l1:                          #23303a;
  --dsw-alias-border-l2:                          #2b3a41;
  --dsw-alias-border-l3: #35464d;
  --dsw-alias-border-l2-darkmode-thin:            #23303a;
}

/* ── 雪璐 · 夜里的房间（dark 基底）────────────────────────────────
 * 需求原话：「纯黑看久了像洞；深蓝灰是夜里的房间 —— 有边界但没人盯着你」。
 * 强调色只一个（暖琥珀），且不跟明暗变 —— 那是「她」的颜色。
 * 所以边框/悬停都用色阶做出层次，不靠阴影堆。
 */
/* ── 雪璐 | 暗版 ──────────────────────────────────────────────────── */
html[data-skin="xuelu"][data-theme="dark"] body {
  --dsw-static-neutral-bluish-00:   #ffffff;
  --dsw-static-neutral-bluish-50:   #e6e6e6;   /* 正文：暖白，不是纯白 */
  --dsw-static-neutral-bluish-60:   #dcdcdc;
  --dsw-static-neutral-bluish-75:   #cfd6de;
  --dsw-static-neutral-bluish-100:  #c4ccd5;
  --dsw-static-neutral-bluish-150:  #b8c1cb;
  --dsw-static-neutral-bluish-200:  #a5aeb9;
  --dsw-static-neutral-bluish-300:  #9aa4b0;   /* 次要 */
  --dsw-static-neutral-bluish-400:  #7d8794;   /* 三级 */
  --dsw-static-neutral-bluish-500:  #666f7b;
  --dsw-static-neutral-bluish-600:  #55606c;
  --dsw-static-neutral-bluish-700:  #454f5a;
  --dsw-static-neutral-bluish-750:  #363f49;
  --dsw-static-neutral-bluish-800:  #232a33;   /* 悬停 / 浮起 */
  --dsw-static-neutral-bluish-850:  #1f262e;   /* 层 2 */
  --dsw-static-neutral-bluish-875:  #1a1f26;   /* 卡片 */
  --dsw-static-neutral-bluish-900:  #141a20;
  --dsw-static-neutral-bluish-950:  #0f1419;   /* 最底：深蓝灰，不是黑 */
  --dsw-static-neutral-bluish-1000: #0a0e12;
  --dsw-static-neutral-200:         #232a33;
  --dsw-static-neutral-300:         #2a323c;
  /* 强调色：琥珀。deepseek 系是品牌位，气泡与选中都走它 */
  --dsw-static-deepseek-50:         #231c14;   /* 我的气泡底：暗底上一点点暖，正文 13.49 */
  --dsw-static-deepseek-100:        #2d2418;
  --dsw-static-deepseek-300:        #8a6a3f;
  --dsw-static-deepseek-400:        #c08d52;
  --dsw-static-deepseek-500:        #d9a066;
  --dsw-static-deepseek-600:        #b87c3c;
  --dsw-static-blue-500:            #d9a066;
  --dsw-static-blue-900:            #3a2a17;
  --dsw-static-blue-950:            #2a1f12;
  --dsw-static-red-400:             #ef7a76;
  --dsw-static-red-600:             #d9605c;
  --dsw-static-green-500:           #5cb87a;
  --dsw-static-amber-500:           #d9a066;
  /* 边界线：1px 实线，不堆阴影。vendor 里这几条是硬编码 rgba，必须显式覆盖 */
  --dsw-alias-border-l1:                          #2a323c;
  --dsw-alias-border-l2:                          #323b47;
  --dsw-alias-border-l3: #3d4753;
  --dsw-alias-border-l2-darkmode-thin:            #2a323c;
  --dsw-alias-brand-primary-new-color:            #d9a066;
  --dsw-alias-brand-primary-new-colorprimary-new-color: #d9a066;
}

/* ── 雪璐 | 亮版 ────────────────────────────────────────────────────
 * ★ 唯一偏离需求的一条：强调色没法【像素级】共用。
 *   同一个 #d9a066 在浅底上只有 2.18:1（远低于 AA 4.5），字读不清。
 *   所以浅色版取同色相加深到 #8c591e（5.60:1）—— 看着仍是同一支琥珀。
 */
html[data-skin="xuelu"][data-theme="light"] body {
  --dsw-static-neutral-bluish-00:   #fdfcfa;
  --dsw-static-neutral-bluish-50:   #f7f5f1;
  --dsw-static-neutral-bluish-60:   #f2efe9;
  --dsw-static-neutral-bluish-75:   #ede9e2;
  --dsw-static-neutral-bluish-100:  #e8e4db;
  --dsw-static-neutral-bluish-150:  #e4e0d6;
  --dsw-static-neutral-bluish-200:  #dbd6ca;
  --dsw-static-neutral-bluish-300:  #c9c3b5;
  --dsw-static-neutral-bluish-400:  #a9a294;
  --dsw-static-neutral-bluish-500:  #8a8375;
  --dsw-static-neutral-bluish-600:  #6b7480;
  --dsw-static-neutral-bluish-700:  #5c6670;   /* 次要 */
  --dsw-static-neutral-bluish-750:  #4c5560;
  --dsw-static-neutral-bluish-800:  #404954;
  --dsw-static-neutral-bluish-850:  #38404a;
  --dsw-static-neutral-bluish-875:  #313841;
  --dsw-static-neutral-bluish-900:  #2a3139;
  --dsw-static-neutral-bluish-950:  #232a33;
  --dsw-static-neutral-bluish-1000: #1f242b;   /* 正文 */
  --dsw-static-neutral-200:         #dbd6ca;
  --dsw-static-neutral-300:         #c9c3b5;
  --dsw-static-deepseek-50:         #f8f0e4;   /* 我的气泡底 */
  --dsw-static-deepseek-100:        #f2e5d4;
  --dsw-static-deepseek-300:        #d9b98d;
  --dsw-static-deepseek-400:        #c69a63;
  --dsw-static-deepseek-500:        #8c591e;
  --dsw-static-deepseek-600:        #7a4c18;
  --dsw-static-blue-500:            #8c591e;
  --dsw-static-blue-900:            #5c3a14;
  --dsw-static-blue-950:            #452b0f;
  --dsw-static-red-600:             #a8403a;
  --dsw-static-green-500:           #3d7a4d;
  --dsw-static-amber-500:           #8c591e;
  --dsw-alias-brand-primary-new-color:            #8c591e;
  --dsw-alias-brand-primary-new-colorprimary-new-color: #8c591e;
  /* 边界线（2026-10-23 补）—— 取本主题自己的 neutral 色阶（l1 = n100 · l2 = n200）。
   * ★ 暖白底上如果用 vendor 的 rgba(0,0,0,.04)，线会偏冷 —— 那是"别人的灰"。
   */
  --dsw-alias-border-l1: #e8e4db;
  --dsw-alias-border-l2: #dbd6ca;
  --dsw-alias-border-l3: #c9c3b5;
}

/* ── SNOW | 亮版：雪后清晨 ──────────────────────────────────────────
 * 底色是极淡的冷蓝白（不是纯白，纯白在屏幕上是"纸"不是"雪"），
 * 文字用深蓝黑（不是纯黑，纯黑和冷白撞在一起发硬）。
 * 强调色只有一支冷青蓝 —— 雪的清冷，但饱和度压低，避免科技风。
 */
html[data-skin="snow"][data-theme="light"] body {
  --dsw-static-neutral-bluish-00:   #ffffff;
  --dsw-static-neutral-bluish-50:   #f8fafb;   /* 最底 */
  --dsw-static-neutral-bluish-60:   #f2f6f9;
  --dsw-static-neutral-bluish-75:   #eaf0f4;
  --dsw-static-neutral-bluish-100:  #e2eaf0;
  --dsw-static-neutral-bluish-150:  #d8e2ea;
  --dsw-static-neutral-bluish-200:  #c9d5de;
  --dsw-static-neutral-bluish-300:  #b0bfc9;
  --dsw-static-neutral-bluish-400:  #8a9aa6;
  --dsw-static-neutral-bluish-500:  #6f7f8a;   /* 弱化 */
  --dsw-static-neutral-bluish-600:  #5e6f7b;   /* 三级 4.97 */
  --dsw-static-neutral-bluish-700:  #4a5a66;   /* 次要 6.81 */
  --dsw-static-neutral-bluish-800:  #36434c;
  --dsw-static-neutral-bluish-950:  #1a232b;
  --dsw-static-neutral-bluish-1000: #101820;   /* 正文 17.09 */
  --dsw-static-neutral-200:         #c9d5de;
  --dsw-static-neutral-300:         #b0bfc9;
  --dsw-static-deepseek-50:         #eaf2f5;   /* 我的气泡底 */
  --dsw-static-deepseek-100:        #d5e8ee;
  --dsw-static-deepseek-300:        #7fb4c4;
  --dsw-static-deepseek-400:        #4a97ad;
  --dsw-static-deepseek-500:        #26738a;   /* 强调 5.14 */
  --dsw-static-deepseek-600:        #1e5e70;
  --dsw-static-blue-500:            #26738a;
  --dsw-static-blue-900:            #0f3d4a;
  --dsw-static-blue-950:            #0a2c36;
  --dsw-static-red-600:             #c0392b;   /* 5.19 */
  --dsw-static-green-500:           #2b7d4a;   /* 4.85 */
  --dsw-static-amber-500:           #96600f;   /* 5.04 */
  --dsw-alias-brand-primary-new-colorprimary-new-color: #26738a;
  --dsw-alias-brand-primary-new-color: #26738a;
  /* 边界线（2026-10-23 补）—— 取本主题自己的 neutral 色阶（l1 = n100 · l2 = n200）。
   * ★ "雪后清晨"的线该是冷白偏蓝的灰，不是 vendor 的纯黑 4%。
   */
  --dsw-alias-border-l1: #e2eaf0;
  --dsw-alias-border-l2: #c9d5de;
  --dsw-alias-border-l3: #b0bfc9;
}

/* ── SNOW | 暗版：雪夜 ──────────────────────────────────────────────
 * 比雪璐的夜再冷一点、再匀一点：她的底色带一点暖（夜间房间），
 * 这套是"窗外有雪的反光"—— 蓝一点点，但同样不做黑洞。
 */
html[data-skin="snow"][data-theme="dark"] body {
  --dsw-static-neutral-bluish-00:   #ffffff;
  --dsw-static-neutral-bluish-50:   #dfe8ee;   /* 正文 14.94 */
  --dsw-static-neutral-bluish-60:   #d3dee6;
  --dsw-static-neutral-bluish-75:   #c6d3dc;
  --dsw-static-neutral-bluish-100:  #b8c7d1;
  --dsw-static-neutral-bluish-150:  #a9bac5;
  --dsw-static-neutral-bluish-200:  #94a7b4;
  --dsw-static-neutral-bluish-300:  #9aadb9;   /* 次要 8.00 */
  --dsw-static-neutral-bluish-400:  #7d919f;   /* 三级 5.67 */
  --dsw-static-neutral-bluish-500:  #66798a;   /* 弱化 4.12 */
  --dsw-static-neutral-bluish-600:  #546775;
  --dsw-static-neutral-bluish-700:  #455663;
  --dsw-static-neutral-bluish-750:  #36454f;
  --dsw-static-neutral-bluish-800:  #232d35;   /* 悬停 */
  --dsw-static-neutral-bluish-850:  #1c252c;   /* 层 2 */
  --dsw-static-neutral-bluish-875:  #161d23;   /* 卡片 */
  --dsw-static-neutral-bluish-900:  #121920;
  --dsw-static-neutral-bluish-950:  #0e1418;   /* 最底 */
  --dsw-static-neutral-bluish-1000: #090e12;
  --dsw-static-neutral-200:         #232d35;
  --dsw-static-neutral-300:         #2c3a44;
  --dsw-static-deepseek-50:         #142229;   /* 我的气泡底 */
  --dsw-static-deepseek-100:        #1a2d36;
  --dsw-static-deepseek-300:        #3d8399;
  --dsw-static-deepseek-400:        #4fa3bc;
  --dsw-static-deepseek-500:        #5cb8d0;   /* 强调 8.15 */
  --dsw-static-deepseek-600:        #4a9cb5;
  --dsw-static-blue-500:            #5cb8d0;
  --dsw-static-blue-900:            #0d3340;
  --dsw-static-blue-950:            #0a2530;
  --dsw-static-red-400:             #ef7a76;
  --dsw-static-red-600:             #d9605c;
  --dsw-static-green-500:           #5cb87a;
  --dsw-static-amber-500:           #d9a066;
  --dsw-alias-border-l1:                          #232d35;
  --dsw-alias-border-l2:                          #2c3a44;
  --dsw-alias-border-l3: #374653;
  --dsw-alias-border-l2-darkmode-thin:            #232d35;
  --dsw-alias-brand-primary-new-color:            #5cb8d0;
  --dsw-alias-brand-primary-new-colorprimary-new-color: #5cb8d0;
}
