/* 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）—— 放设置那类面板，比左侧栏宽一点 */
  --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);
}

* { 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; }

.nav { flex: 1; overflow-y: auto; padding: 4px 8px; }
.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; }

.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;
  background: var(--dsw-alias-brand-primary-new-color);
  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; }

.ws-thumb {
  flex: none; width: 56px; height: 56px; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px;
  background: var(--dsw-specific-input-major); overflow: hidden;
  font-size: 10px; font-weight: 600; letter-spacing: .04em;
  color: var(--dsw-alias-label-tertiary);
  transition: border-color var(--dur) var(--ease);
}
.ws-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.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);
}

.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);   /* 图还没加载出来时不至于一片空白 */
}

/* 页面内看大图（★ 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-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%，多留白只会让长消息更高。 */
.m:not(.me) > div { max-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: #edf3fe;                     /* 亮 */
  color: var(--dsw-alias-label-primary);
  border-bottom-right-radius: 6px;
}
html[data-theme="dark"] .m.me .b { background: #2c2c2e; }   /* 暗 */
.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));
}

/* 转发来的（这句话不是在这个平台上说的）—— 左边一条细线，跟本地消息分开。
   ★ 标头（[来自 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; }

/* ─────────── 中间过程（思考 / 工具调用 / 结果）───────────
 *
 * ★ 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);
  /* ★ 卡片的底 —— 同 --card-bg（默认 transparent）。
   *   暗色下它原本是 --dsw-specific-input-major = bluish-850，
   *   比页面底（--dsw-alias-bg-base = bluish-950）亮一档，就是"卡片看得见"的原因。
   * ★ 亮色下两层本来就是同一个值（都是 bluish-00），改动看不出来 ——
   *   别以为没生效。 */
  background: var(--card-bg);
  transition: border-color var(--dur) var(--ease);
  overflow: hidden;
}
/* 聚焦时整个卡片亮一下（而不是单个 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); }

/* 消息流里的附件：同一套单元，大一号 + 换行排列 */
.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; }
#modal {
  /* ★ 主人说"这个弹框有点小气，太窄了" —— 560 → 880，
     而且高度放到 85vh（表格多的时候少滚一点） */
  /* ★ 再调（主人 10-12）：880 → 1100 —— 表格列一多，880 就得横向滚了。 */
  width: min(1100px, 100%); max-height: 85vh; overflow-y: auto;
  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 {
  width: min(1100px, 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%;
    transform: translateX(-100%); width: var(--sb-w) !important;
    transition: transform var(--dur) var(--ease);
  }
  #app.sb-open #sidebar { transform: none; }
  /* ★ 手势拖动期间禁掉 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; } }

@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); }
/* 用量靠右（它跟左边的状态不是一类东西：一个是"我脑子里多满"，一个是"花了多少"） */
#usage { margin-left: auto; }
.conn { font-size: 12px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; }


/* ─────────── 弹窗：头/尾 + 数据表 ─────────── */

.mhead { margin-bottom: 14px; }
.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; }
.mfoot { display: flex; justify-content: flex-end; margin-top: 16px;
  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; }
.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 五列版）──
 *   配置项 | 默认值 | yaml | db 值 | 生效方式
 * ★ 老的 .srcb（来源徽章）和 .cfgdots 随重设计一起删了 —— JS 里已零引用。
 *
 * ★★ 固定列宽（主人 2026-10-15）："配置项列长一点、默认值/yaml/db 等宽、
 *    生效方式保持现状"。
 *    靠 table-layout: fixed —— 列宽由【表头】定，不随每行的内容跳。
 *    （不固定的话每行按自己内容算，同一张表在不同分组下长得都不一样。）
 */
.dt.cfgt { table-layout: fixed; }
.dt.cfgt th:nth-child(2),
.dt.cfgt th:nth-child(3),
.dt.cfgt th:nth-child(4) { width: 150px; }   /* ★ 三层【等宽】—— 好横向对照 */
.dt.cfgt th:nth-child(5) { width: 76px; }    /* 生效方式：两个字，保持原来的窄 */
/* ★ 第 1 列【不写 width】= auto → 吃掉剩下的全部宽（"配置项列长一点"） */

/* ★ 长的不换行：截断 + 悬停看全（主人 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;
}
/* db 格：列宽已定，输入框跟着列走（不再用固定 220px）*/
.dt.cfgt .cfgin { max-width: 100%; }
.dt.cfgt .cfgedit { flex-wrap: wrap; }       /* ★ 兜底：窄到放不下时按钮掉下一行，别溢出 */

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

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

/* 不能改的那格（凭证 / 数组）—— 说清为什么 + 该去哪改 */
.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); }
.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); }

/* 长文本（比如 /skill 现在只给 text） */
.md-pre {
  margin: 0; padding: 12px 14px; border-radius: var(--dsw-radius-sm, 8px);
  background: var(--dsw-alias-markdown-code-block);
  font-family: var(--dsw-font-family, 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(--dsw-font-family, 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(--dsw-font-family, monospace);
  font-size: 12.5px; line-height: 1.5; overflow-x: auto; white-space: pre;
  position: relative;
}
.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-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; }   /* 那个 ★（必填）*/
