/* ══════════════════════════════════════════════════════════════
   设计 token
   ══════════════════════════════════════════════════════════════
   两套主题共用同一组变量名，只在 [data-theme] 下重定义取值。
   这样所有组件样式都不需要关心当前是明还是暗。

   变量分层：
     --bg / --bg-2 / --bg-3      背景三级（越往后越靠前/越浅）
     --border                    常规边框
     --text / --muted / --faint  正文 / 次要 / 极弱文字
     --accent                    强调色（品牌蓝）
     --green / --red / --orange  状态色
     --hover / --active          交互态叠加层（中性，随主题变化）
     --overlay                   模态遮罩
     --shadow*                   阴影（亮色更明显，暗色更弱）
*/
/* ── 暗色（默认，与改造前保持一致）──
   注意：:root 与 [data-theme='dark'] 写在同一条规则里，"默认"和"显式选暗色"共用一套值。
   不要再把 :root 单独拆出来写（曾经拆出过一个只有 color-scheme 的 :root 块，
   既冗余又容易让人误以为变量定义在别处）。 */
/* ══════════════════════════════════════════════════════════════
   值班台色板（2026-10-09）
   ══════════════════════════════════════════════════════════════
   这个控制台的物理类比不是 SaaS 仪表盘，是「值班台 / 通报板」：原材料是一群人在群里说话、
   一个机器人半夜替你说几句、每次说话花掉的钱、以及"它是不是开始跑偏了"。

   ▸ `--voice`（冷青）是**唯一代表「这是机器人说的」的强调色**，不是第二个主题色。
     它出现在上下文里的机器人回合、会话列表的"已发言"徽章、归档表里它自己发的消息，
     以及总览页"机器人"那张卡的图标。这一笔只花在一处才有意义 —— 别拿它当第二个强调色。
     （2026-10-19：原来是黄铜；亮色下 #8a5a06 在会话列表里是一块块棕黄，与靛蓝/冷色壁纸
     不协调，用户点名要换。换成冷青后仍是唯一身份色，但落在主题的冷色区间里。）

   ▸ **底色保持原值，不动。** 2026-10-09 试过一版"更冷、更中性"的底（#0f141b 系列，
     把蓝味抽掉，理由是"深海军蓝＝默认款"、且中性底能让黄铜更突出），实测后**退回**，
     理由记在这里免得再提一遍：
       · 它明度几乎没变（ΔL* < 1.5），真正变的是彩度 —— 也就是说用户说不出哪里变了，
         只觉得"哪里不对"，这是最难解释的一类改动；
       · 暗色的 `--muted` 对比度从 7.52 掉到 6.43（量过），而暗色正是实际在用的那套；
       · 身份那点收益主要来自 `--voice` 本身，冷青压在旧深蓝底上一样跳；
       · 仓库本来就有 `dark-intensity` 与主题微调两条轴，底色深浅/冷暖是**用户**的决定，
         不该由默认值替他选。
     一句话："更好看"不是可测量的改进，而它影响每一屏。要改请先问。

   ▸ 若哪天真要换色值：正文/次级/描边三道对比度门槛（≥7 / ≥4.4 / ≥1.2）与"五套色板
     两两看得见差别"都在 test/appearance-schemes.test.mjs 里钉着；改前把那条用例跑一遍，
     改完还要同步设置页那份预览取色表（SCHEME_PREVIEW，第二份真源）。 */
:root,
[data-theme='dark'] {
  color-scheme: dark;

  --bg: #0b1220;
  --bg-2: #111b2d;
  --bg-3: #182742;
  --border: #22304d;
  --text: #e7edf8;
  --muted: #96a5bf;
  --voice: #2dd4bf;              /* 冷青（原黄铜）。只给"机器人说的"，别挪作他用 */
  --accent: #4c8dff;
  --green: #2fd07a;
  --red: #ff6b6b;
  --orange: #f7b955;

  /* 交互态：暗色下用白色提亮 */
  --hover: rgba(255, 255, 255, .05);
  --active: rgba(255, 255, 255, .09);
  --overlay: rgba(0, 0, 0, .55);

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, .3);
  --shadow-md: 0 6px 20px rgba(0, 0, 0, .35);
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, .45);

  --mono: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;

  /* 强调色的派生态（柔和底 / 描边 / 焦点环）不在这里写：它们由「外观 v3」那节的 :root
     用 color-mix 从 --accent 现算。这里原来留了两行 `--accent-soft: var(--accent-soft)`，
     是自引用（循环）→ 计算值直接失效，只有 JS 每次都把内联值写上才看不出来。 */

  /* 外观刻度（--r-scale / --zoom）与圆角标度的 CSS 真源都在下面的 :root 设计标度节。
     这里原先还有一份（圆角三档 + 两个刻度默认），其中圆角三档被 :root 同特异性覆盖成
     死声明 —— --r-lg 的死值是 14px、生效值 12px，改它会"改了没反应"（2026-10-09 复核）——
     已删；两个刻度默认也一并搬去 :root（原先只有暗色有兜底、亮色没有，不对称）。 */
}

/* ── 亮色 ── */
[data-theme='light'] {
  color-scheme: light;

  --bg: #f4f7fc;
  --bg-2: #ffffff;
  --bg-3: #eef3fb;
  --border: #e0e7f2;
  --text: #14202f;
  --muted: #5b6b83;
  --voice: #0f766e;              /* 亮色下的冷青（原黄铜 #8a5a06）：压暗才压得住白底。
                                    底色与上面那六项一律保持原值，见顶部那段说明。 */
  --accent: #2f6fe4;
  --green: #1f9d55;
  --red: #d94a4a;
  --orange: #c97a12;

  /* 交互态：亮色下用黑色压暗 */
  --hover: rgba(0, 0, 0, .045);
  --active: rgba(0, 0, 0, .08);
  --overlay: rgba(20, 24, 33, .35);

  --shadow-sm: 0 1px 3px rgba(16, 24, 40, .08);
  --shadow-md: 0 6px 20px rgba(16, 24, 40, .1);
  --shadow-lg: 0 12px 40px rgba(16, 24, 40, .14);

  --mono: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
}

/* ══════════════════════════════════════════════════════════════
   动效 token
   ══════════════════════════════════════════════════════════════
   设计取向：Linear 的丝滑转场 + Vercel 的克制配色。
   控制台是要长时间盯着的界面，动效的目标是"让变化可追踪"，
   而不是炫技 —— 所以位移都控制在 12px 以内、时长都在 360ms 以内，
   强调色只用于传达状态，不用于装饰。

   缓动曲线（为什么选这几条）：
     --ease-out-quart  减速收尾，元素"到位"感最强。用于入场、悬浮抬升
     --ease-in-out     两端慢中间快。用于颜色/背景这类对称变化
     --ease-spring     轻微过冲，给交互一点"手感"。用于弹窗、按压
   时长分级：越快 = 越接近即时反馈；越慢 = 越像"场景切换"
     --dur-fast   120ms  按压、hover 这类即时反馈
     --dur-base   180ms  颜色/边框变化
     --dur-slow   260ms  位移、展开收起
     --dur-slower 360ms  模态框、页面转场
*/
:root {
  --ease-out-quart: cubic-bezier(.25, 1, .5, 1);
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);

  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 260ms;
  --dur-slower: 360ms;

  /* 位移尺度：统一控制"动多远"，避免各处乱写 px */
  --shift-sm: 4px;
  --shift-md: 8px;
  --shift-lg: 12px;
}

/* ── 全局基础过渡 ──
   给常见交互元素一个默认过渡，这样新增组件"天然就有"动效，
   而不是每次都要记得补 transition。具体组件可按需覆盖。 */
a,
button,
input,
textarea,
select,
summary,
[role='button'],
[role='tab'],
.clickable {
  /* 六个属性统一走 --dur-interaction（150ms）：参照实现里颜色与 scale 都是 0.15s，
     而这条基础规则原来颜色用 180ms、位移用 120ms —— 同一个按钮上两种速度，看着发"散"。 */
  transition:
    background-color var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out),
    color var(--dur-interaction) var(--ease-in-out),
    box-shadow var(--dur-interaction) var(--ease-in-out),
    transform var(--dur-interaction) var(--ease-out-quart),
    opacity var(--dur-interaction) var(--ease-in-out);
}

/* ── 主题切换平滑过渡 ──
   切换明暗时让颜色渐变而不是硬切，避免"闪一下"的廉价感。
   注意：只过渡颜色相关属性，不过渡 transform —— 否则所有元素会一起抖动。 */
body,
.topbar,
.settings-menu,
.model-modal,
.usage-card,
.tier-bar,
.tier-param,
.usage-table th,
.usage-table td {
  transition:
    background-color var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out),
    color var(--dur-interaction) var(--ease-in-out);
}

/* ══════════════════════════════════════════════════════════════
   无障碍：尊重"减少动态效果"系统偏好
   ══════════════════════════════════════════════════════════════
   部分用户会因动画产生眩晕/恶心（前庭功能障碍）。开启后：
     - 位移、缩放、旋转全部禁用（保留 opacity 淡入淡出，那是最温和的）
     - 时长压到 1ms 让过渡"瞬间完成"，避免残留动画
   这是 WCAG 2.1 的建议项，不是可选项。
*/
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  /* 位移/缩放类动画直接停掉，只留纯淡入淡出 */
  .model-modal,
  .usage-card,
  .tier-btn,
  [class*='itemIn'] {
    animation: none !important;
    transform: none !important;
  }
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: 'Segoe UI', 'Microsoft YaHei', system-ui, sans-serif;
  font-size: var(--fs-md);
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ── 启动 loading 壳 ── */
/* 启动提示改成"浮层"而不是"整屏遮罩"：
   以前它会用背景色盖住整页，等 /api/status 就绪才淡出——点进控制台就是白屏闪一下。
   现在外壳（侧栏 + 视图）立即可见，只有一张小卡片浮在顶部，服务慢或没起来时才看得到。 */
.loading-overlay {
  position: fixed; inset: 0; z-index: 999;
  background: transparent;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 78px 16px 16px;
  pointer-events: none;              /* 不挡操作；卡片本身恢复可点击 */
  opacity: 0;                        /* 默认静态隐藏：正常进入时一秒都不出现 */
  transition: opacity var(--dur-base) var(--ease-in-out);
}
/* 只有 html 上没有 boot-silent（= 脚本判定启动过慢，或 8 秒兜底被触发）时才显示 */
html.boot-show .loading-overlay { opacity: 1; }
.loading-card {
  width: min(400px, 88vw);
  padding: 16px 18px;
  background: color-mix(in srgb, var(--bg-2) 94%, transparent);
  backdrop-filter: blur(10px) saturate(150%);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 10px;
  pointer-events: auto;
}
.loading-title { font-size: var(--fs-md); font-weight: 600; }
.loading-sub { color: var(--muted); font-size: var(--fs-sm); }
.loading-spinner {
  width: 22px; height: 22px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-logs {
  margin-top: 6px;
  max-height: 180px;
  overflow-y: auto;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--muted);
  white-space: pre-wrap;
  word-break: break-all;
}

/* ══ 外壳：左侧导航 + 右侧工作区 ══
   结构性重排：原来"横排 11 个页签 + 顶栏塞状态"改成侧栏导航 + 工作区状态条。
   所有元素 ID / class 保持不变，JS 只按 ID 与 .tab/.view 查找，因此无需改脚本。 */
#shell {
  flex: 1;
  min-height: 0;
  min-width: 0;
  display: flex;
}

/* ── 侧栏 ── */
#sidebar {
  width: 236px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 12px 12px;
  background: var(--bg-2);
  border-right: 1px solid var(--border);
}
.side-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 6px 6px;
}
.side-head .brand-mark { width: 30px; height: 30px; border-radius: var(--r-input); display: block; }
.side-title { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.side-title strong { font-size: var(--fs-md); font-weight: 650; letter-spacing: .2px; }
.brand-sub { color: var(--muted); font-size: var(--fs-xs); font-weight: 400; }
.side-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0 0;
  border-top: 1px solid var(--border);
}
.side-foot .runtime-control { flex: 1 1 0; min-width: 0; display: flex; }
.side-foot #runtime-mode { width: 100%; }
/* 三个按钮都不参与收缩：宁可下拉框变窄，也不让图标被挤出去（下拉框内容只有两个字 + 箭头） */
.side-foot .icon-btn, .side-foot #pause-btn { flex: 0 0 auto; }
.side-foot .icon-btn { width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center; }
/* 固定按钮：钉住时点亮，未钉住（图标条模式）时把图钉转 45° —— 一眼看出当前是哪种 */
#side-pin-btn.pinned { color: var(--accent-side); }
#side-pin-btn:not(.pinned) .ico { transform: rotate(45deg); }

/* ── 导航（垂直）── */
#tabs {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px;
  overflow-y: auto;
  background: none;
  border: 0;
}
/* 一级导航不再有分组标题：改成"主名 + 一行说明"两行结构，组与组之间靠空白分隔
   （十二项各带一行说明之后再叠四个分组标题就太吵了；说明那行已经接住了分组的语义）。 */
/* ⚠️ .tab 的基础定义**已并入文件末尾「一级导航」区块**（那里是唯一权威定义，含 font-family/
   white-space 这两条独有属性）。这里原来是它的第一份定义，与末尾那份在 gap / padding /
   border / 圆角 / 字号 / 过渡上互相打架 —— 同权重，谁在后面谁赢，于是"改这里等于没改"
   （2026-10-08 审查发现）。已撤掉，只留指针。 */
/* 图标：内联 SVG（ui/icons.js 注入 <span class="ico">）。原来这里是
   content: attr(data-icon) 直接画 emoji —— emoji 自带配色、各系统形状不同，
   选中/悬停态也没法统一。改成 SVG 后颜色跟随 currentColor。 */
.ico { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 18px; height: 18px; }
/* 图标统一"描边 + 跟文字色"。选择器必须同时覆盖 `.ico-svg`（SVG 自带的类）：
   总览的 KPI/卡片头图标、刷新按钮、顶栏主题按钮都是直接调 iconSvg() 的，没有 .ico 包装 ——
   只写 `.ico svg` 时它们会退回 SVG 默认的 fill:black，深色下 1.22:1。 */
.ico svg, .ico-svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.tab .ico { opacity: .85; transition: transform 220ms var(--ease-spring), opacity var(--dur-base) var(--ease-in-out); }
.tab:hover .ico { opacity: 1; }
.tab.active .ico { opacity: 1; color: var(--accent); transform: scale(1.08); }
.tab:active .ico { transform: scale(.94); }
.tab:not(.active):hover { background: var(--hover); color: var(--text); }
/* 选中态的底色/左缘条现在归 .side-pill（会滑动，见「一级导航」区块），这里不再重复定义 ——
   原来这里那一版会与滑块叠成两层色。字重由下面 .tab.active { font-weight: 600 } 负责。 */

/* ── 工作区与状态条 ── */
#workspace { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
#topbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg-2) 86%, transparent);
  backdrop-filter: saturate(160%) blur(10px);
  flex-shrink: 0;
}
.top-status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.top-status > .muted {
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--bg-3);
  color: var(--muted);
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.top-status .sep { display: none; }
#topbar .dot { margin-left: 2px; }

/* 窄屏：侧栏折叠成顶部的横向导航条 */
@media (max-width: 900px) {
  #shell { flex-direction: column; }
  #sidebar {
    width: 100%;
    gap: 6px;
    padding: 10px 12px;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .side-head { padding: 0 2px 4px; }
  #tabs { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; gap: 6px; padding-bottom: 4px; }
  .tab { width: auto; flex-shrink: 0; }
  .side-foot { border-top: 0; padding-top: 4px; }
}

/* ── 布局 ── */
#layout { flex: 1; display: flex; min-width: 0; min-height: 0; overflow: hidden; }
.view { display: none; flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
/* 页签切换的入场（2026-10-19，对齐参考实现的 spring 观感）：
   位移 10px + 轻微缩小，360ms 收尾。display:none → flex 会重启动画，所以每次切页都会跑。
   动画期间的 transform 会让 position:fixed 的子元素暂时以视图为包含块 —— 时长只有 360ms，
   且这一层没有常驻 transform（to 帧就是 none），不影响粘性栏/弹窗。 */
.view.active { display: flex; }
/* 总览的卡片依次浮起：纯位移、每块错开 45ms。**不加 opacity 从 0 起** ——
   2026-10-09 实测过：切页后有一段同步渲染，若起始帧是透明，用户会先看到空白。
   只给直接子元素，避免深层节点一起动。页签本身的入场见下方「视图切换」一节。 */
.view.active .overview-pane > * { animation: riseIn 420ms var(--ease-out-quart) both; }
.view.active .overview-pane > *:nth-child(2) { animation-delay: 45ms; }
.view.active .overview-pane > *:nth-child(3) { animation-delay: 90ms; }
.view.active .overview-pane > *:nth-child(4) { animation-delay: 135ms; }
.view.active .overview-pane > *:nth-child(5) { animation-delay: 180ms; }
@keyframes riseIn {
  from { transform: translateY(10px); }
  to { transform: none; }
}
html[data-motion] .view.active .overview-pane > * { animation: none; }

.list-pane {
  width: 320px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  /* 列表行 hover 时有 translateX(2px)（见文件末尾的外观 v4 动效块）。overflow-y: auto 会把
     overflow-x 一起算成 auto，于是鼠标划过任意一行，列表底部就冒出一条横向滚动条 ——
     2026-10-19 在「会话记忆」实测复现。列表本来就不该横向滚，直接裁掉。 */
  overflow-x: hidden;
  overflow-y: auto;
  background: var(--bg-2);
}
.list-head:has(> #session-count:empty) { display: none; }
/* 列表头：字号从 11px 提到 12px 并加字重，此前过小且与正文层级脱节 */
.list-head {
  padding: 11px 14px;
  font-size: var(--fs-sm);
  font-weight: 500;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg-2);
  z-index: 2;
  /* 父级 .list-pane 是圆角玻璃；sticky 头的方角会从圆角里"戳"出来，留下一个缺口。
     跟随父级圆角（父级没有圆角时 inherit 也是 0，不改变其他布局）。 */
  border-top-left-radius: inherit;
  border-top-right-radius: inherit;
}
/* 表头文字要躲开 18px 的圆角弧线；列表行同步到同一左缘，否则表头与内容差 6px。 */
html[data-glass='liquid'] .list-head { padding-left: 20px; padding-right: 20px; }
html[data-glass='liquid'] .session-item,
html[data-glass='liquid'] .chat-item { padding-left: 20px; padding-right: 20px; }
html[data-glass='liquid'] .session-item.selected,
html[data-glass='liquid'] .chat-item.selected { padding-left: 17px; }

.detail-pane { flex: 1; min-width: 0; overflow-x: hidden; overflow-y: auto; padding: 22px 26px 40px; }
/* 空状态：用 flex 居中而非固定 vh 偏移，窗口高度变化时始终居中 */
.empty-hint {
  color: var(--muted);
  text-align: center;
  padding: 60px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 240px;
}
.empty-hint::before {
  content: '';
  width: 40px; height: 40px;
  border: 2px dashed var(--border);
  border-radius: var(--r-input);
  opacity: .7;
}

/* ── 会话列表 ── */
.session-item, .chat-item {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.session-item:hover, .chat-item:hover { background: var(--bg-3); }
.session-item.selected, .chat-item.selected { background: var(--bg-3); border-left: 3px solid var(--accent); padding-left: 11px; }
.session-title { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.session-chat { font-weight: 600; font-size: var(--fs-md); }
.session-time { color: var(--muted); font-size: var(--fs-xs); white-space: nowrap; }
.session-trigger {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  color: var(--muted);
  font-size: var(--fs-sm);
  margin-top: 3px;
  overflow: hidden;
  white-space: nowrap;
}
.session-trigger > span:last-child { overflow: hidden; text-overflow: ellipsis; }
.trigger-method { flex: 0 0 auto; color: color-mix(in srgb, var(--mode-color, var(--text)) 85%, var(--text)); font-weight: 550; }
.session-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; font-size: var(--fs-xs); color: var(--muted); align-items: center; }

/* 字号/内边距/圆角归下面的徽章组统一（--fs-sm / 2px 9px / --r-pill）——本处旧值被它覆盖成
   死声明，2026-10-09 复核删掉，只留它没有的两条：长标签要能换行。 */
.status-badge { max-width: 100%; white-space: normal; }
/* （.status-done 的旧副本已删，2026-10-09 复核：本处 16%/88% 的一套被会话徽章区的权威规则
   完整覆盖成死声明 —— token 改在死副本上会"改了没反应"。语义说明随权威规则走。） */
.status-noreply { background: rgba(139,147,167,.15); color: var(--muted); }
.status-running { background: rgba(91,140,255,.18); color: var(--accent); }
.status-waiting { background: rgba(255,180,84,.18); color: var(--orange); }
.status-error { background: rgba(255,107,107,.15); color: var(--red); }
.status-aborted { background: rgba(255,180,84,.15); color: var(--orange); }
.session-wait { font-size: var(--fs-xs); color: var(--orange); font-variant-numeric: tabular-nums; }
.session-item.session-waiting-row { border-left: 3px solid var(--orange); padding-left: 11px; }
.session-activity { font-size: var(--fs-xs); color: var(--accent); }
.mode-legacy { --mode-color: var(--green); --mode-soft: rgba(63, 206, 122, .11); }
.mode-threaded { --mode-color: var(--accent); --mode-soft: rgba(91, 140, 255, .12); }
/* 生命周期模式原来用橙色：亮色下 --orange 是 #c97a12，配 12% 暖底，在列表里就是一条条棕黄，
   与靛蓝强调色和冷色壁纸不搭（2026-10-19 用户反馈"棕黄色不协调"）。换成冷紫：
   三档仍是绿 / 蓝 / 紫三个能区分的色相，且都在主题的冷色区间里。暗色用亮一档的紫。 */
.mode-lifecycle { --mode-color: #8b5cf6; --mode-soft: rgba(139, 92, 246, .12); }
[data-theme='dark'] .mode-lifecycle { --mode-color: #a78bfa; --mode-soft: rgba(167, 139, 250, .14); }
.mode-chip,
.thread-pill {
  max-width: 100%;
  border: 1px solid color-mix(in srgb, var(--mode-color) 30%, transparent);
  border-radius: var(--r-sm);
  background: var(--mode-soft);
  color: color-mix(in srgb, var(--mode-color) 82%, var(--text));
  padding: 1px 6px;
  font-size: var(--fs-2xs);
  line-height: 1.4;
  white-space: nowrap;
}
.session-item.session-thread-group {
  box-shadow: inset 2px 0 0 color-mix(in srgb, var(--mode-color) 60%, transparent);
}

/* ── 会话详情 ── */
.detail-header { margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.detail-header h2 { font-size: var(--fs-lg); margin-bottom: 6px; }
.detail-header .sub { color: var(--muted); font-size: var(--fs-sm); display: flex; gap: 14px; flex-wrap: wrap; }
.session-mobile-back { display: none; margin-left: 8px; vertical-align: middle; }
.session-mode-band {
  display: grid;
  grid-template-columns: minmax(100px, .7fr) minmax(110px, .8fr) minmax(220px, 2fr) auto;
  align-items: center;
  gap: 12px;
  margin: -2px 0 16px;
  padding: 10px 12px;
  border-top: 1px solid color-mix(in srgb, var(--mode-color) 35%, var(--border));
  border-bottom: 1px solid color-mix(in srgb, var(--mode-color) 35%, var(--border));
  background: var(--mode-soft);
  font-size: var(--fs-sm);
}
/* 原始 --mode-color 在浅色下（紫 #8b5cf6）约 3.5:1，12px 粗体仍偏淡；与 .mode-chip / 时间线标题同口径压深一档。 */
.session-mode-name { color: color-mix(in srgb, var(--mode-color) 82%, var(--text)); font-weight: 700; }
.session-mode-state { font-weight: 600; }
.session-mode-detail { color: var(--muted); min-width: 0; }
.session-mode-ref { color: var(--faint); font-family: var(--mono); font-size: var(--fs-xs); white-space: nowrap; }
.lifecycle-overview {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 0 0 16px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.lifecycle-overview > div {
  min-width: 0;
  padding: 10px 12px;
}
.lifecycle-overview > div + div { border-left: 1px solid var(--border); }
.lifecycle-overview span,
.lifecycle-overview strong,
.lifecycle-overview small { display: block; overflow-wrap: anywhere; }
.lifecycle-overview span { color: var(--muted); font-size: var(--fs-2xs); }
.lifecycle-overview strong { margin-top: 3px; font-size: var(--fs-md); }
.lifecycle-overview small { margin-top: 3px; color: var(--faint); font-size: var(--fs-2xs); }
.lifecycle-remaining { color: var(--mode-color, var(--orange)); font-variant-numeric: tabular-nums; }
.thread-timeline {
  min-width: 0;
  margin: 0 0 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.thread-timeline-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 9px;
}
.thread-timeline-head strong,
.thread-timeline-head span { display: block; }
.thread-timeline-head strong { color: color-mix(in srgb, var(--mode-color) 85%, var(--text)); font-size: var(--fs-md); }
.thread-timeline-head > div > span {
  margin-top: 2px;
  color: var(--faint);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  overflow-wrap: anywhere;
}
.thread-timeline-head > span { color: var(--muted); font-size: var(--fs-xs); white-space: nowrap; }
.thread-run-list {
  display: flex;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  gap: 7px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-gutter: stable;
  touch-action: pan-x pan-y;
  padding: 1px 0 7px;
}
.thread-run-list::-webkit-scrollbar { height: 7px; }
.thread-run-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text) 22%, var(--border)); border-radius: var(--r-sm);
  border: 2px solid transparent; background-clip: padding-box;   /* 与全局同口径：裁出透明边，不描背景色的边 */
}
.thread-run {
  flex: 0 0 148px;
  min-width: 0;
  height: 58px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-2);
  color: var(--muted);
  padding: 6px 8px;
  cursor: pointer;
  text-align: left;
}
.thread-run > span,
.thread-run > strong,
.thread-run > small { display: block; }
.thread-run > span { color: var(--faint); font-size: var(--fs-2xs); }
.thread-run > strong { margin: 1px 0; color: var(--text); font-family: var(--mono); font-size: var(--fs-xs); }
.thread-run > small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-2xs); }
.thread-run:hover { border-color: color-mix(in srgb, var(--mode-color) 55%, var(--border)); }
.thread-run.active {
  border-color: var(--mode-color);
  background: var(--mode-soft);
  color: var(--mode-color);
}
@media (max-width: 760px) {
  .lifecycle-overview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lifecycle-overview > div:nth-child(3) { border-left: 0; border-top: 1px solid var(--border); }
  .lifecycle-overview > div:nth-child(4) { border-top: 1px solid var(--border); }
}
@media (max-width: 600px) {
  #view-sessions .list-pane { width: 100%; border-right: 0; }
  #view-sessions .detail-pane { display: none; width: 100%; padding: 16px; }
  #view-sessions.mobile-detail-open .list-pane { display: none; }
  #view-sessions.mobile-detail-open .detail-pane { display: block; }
  .session-mobile-back { display: inline-flex; }
  .session-mode-band {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 8px;
  }
  .session-mode-detail { grid-column: 1 / -1; grid-row: 2; }
  .session-mode-ref { grid-column: 1 / -1; grid-row: 3; white-space: normal; }
  /* 其余「列表 + 详情」视图没有会话页那套 mobile-detail-open 切换：窄屏直接上下堆叠。
     原先它们保持 320px 的列表 + 剩余宽度详情，390px 屏上详情只剩 40px，文字竖着排。
     堆叠比"点开再返回"少一步，也不需要给每个页面补返回按钮。 */
  #view-chats, #view-memory, #view-people-memory { flex-direction: column; overflow-y: auto; }
  #view-chats > .list-pane, #view-memory > .list-pane, #view-people-memory > .list-pane {
    width: 100%; flex: 0 0 auto; max-height: 40vh;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  #view-chats > .detail-pane, #view-memory > .detail-pane, #view-people-memory > .detail-pane {
    display: block; width: 100%; flex: 1 1 auto; min-height: 0; padding: 16px;
  }
}
.context-inspector {
  margin: 0 0 18px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.context-inspector-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
}
.context-inspector-head strong,
.context-inspector-head span { display: block; }
.context-inspector-head strong { font-size: var(--fs-md); }
.context-inspector-head > div > span { margin-top: 3px; color: var(--muted); font-size: var(--fs-xs); }
.context-layout {
  max-width: 48%;
  color: var(--faint);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  overflow-wrap: anywhere;
  text-align: right;
}
.context-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.context-metrics > div {
  min-width: 0;
  padding: 9px 10px;
  background: var(--bg);
}
.context-metrics span,
.context-metrics small { display: block; color: var(--muted); font-size: var(--fs-2xs); }
.context-metrics strong {
  display: block;
  margin: 3px 0 1px;
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-md);
  overflow-wrap: anywhere;
}
.context-request-summary {
  margin-top: 8px;
  color: var(--faint);
  font-size: var(--fs-2xs);
  line-height: 1.5;
}
.context-call-table-wrap { margin-top: 10px; overflow-x: auto; }
.context-call-table { width: 100%; border-collapse: collapse; font-size: var(--fs-xs); }
.context-call-table th,
.context-call-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--border);
  text-align: right;
  white-space: nowrap;
}
.context-call-table th { color: var(--muted); font-weight: 500; }
.context-call-table th:first-child,
.context-call-table td:first-child { text-align: left; }
.context-tabs {
  display: flex;
  gap: 18px;
  margin-top: 12px;
  border-bottom: 1px solid var(--border);
}
.context-tabs button {
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--muted);
  padding: 8px 1px;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-sm);
}
.context-tabs button:hover { color: var(--text); }
.context-tabs button.active { border-bottom-color: var(--accent); color: var(--accent); }
.identity-pilot-people {
  margin-top: 12px;
  max-height: 420px;
  overflow: auto;
  border: 1px solid var(--border);
}
.identity-pilot-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-xs);
}
.identity-pilot-table th,
.identity-pilot-table td {
  padding: 7px 8px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  white-space: nowrap;
}
.identity-pilot-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  color: var(--muted);
  background: var(--bg-3);
  font-weight: 500;
}
.identity-pilot-table td:nth-child(n+4) { text-align: right; }
.identity-pilot-table tr:last-child td { border-bottom: none; }
.identity-pilot-table td small {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  white-space: normal;
}
#identity-friend-proposal-box {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
#identity-friend-proposal-box h4 {
  margin: 0 0 12px;
  font-size: var(--fs-md);
}
#identity-friend-proposals .proposal-decision + .proposal-decision {
  margin-left: 6px;
}
.context-tab-body {
  min-height: 140px;
  max-height: 520px;
  overflow: auto;
  padding: 12px 0;
}
.context-json,
.reasoning-entry pre {
  margin: 0;
  color: var(--muted);
  font-family: var(--mono);
  font-size: var(--fs-xs);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* 完整输入的可读转写：按角色分块 + 最小 markdown 渲染 */
.context-msg {
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  margin: 0 0 10px;
  overflow: hidden;
  background: rgba(127, 127, 127, 0.04);
}
.context-msg-head {
  display: flex;
  justify-content: space-between;
  padding: 5px 12px;
  font-size: var(--fs-xs);
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}
.context-msg-system .context-msg-head { color: var(--accent); }
/* 机器人自己的回合：一道 2px 冷青左沿 + 冷青标签。全上下文视图里“哪几段是它说的”一眼可扫，
   不需要读标签文字；system 仍走强调蓝（它代表“系统/规则”，不是“它说的”）。 */
.context-msg-assistant { border-left: 2px solid var(--voice); }
.context-msg-assistant .context-msg-head { color: color-mix(in srgb, var(--voice) 88%, var(--text)); }
.context-msg-body { padding: 8px 12px; }
.context-msg-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--text);
}
.context-msg-text code {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  background: rgba(127, 127, 127, 0.12);
  border-radius: var(--r-sm);
  padding: 1px 4px;
}
.context-msg-dim { color: var(--muted); }
.context-msg-tool { padding: 6px 0; font-size: var(--fs-sm); color: var(--muted); }
.context-msg-tool pre.context-json { margin: 4px 0 0; }
.context-raw-json { margin-top: 10px; }
.context-raw-json summary { cursor: pointer; color: var(--muted); font-size: var(--fs-sm); padding: 4px 0; }
.context-raw-json[open] summary { color: var(--accent); }
/* 存档筛选（关键字 + 类型） */
.archive-filter { display: flex; gap: 6px; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.archive-filter input {
  flex: 1; min-width: 0;
  background: var(--bg-3); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--r-sm); padding: 6px 9px; font-size: var(--fs-sm); outline: none;
}
.archive-filter select {
  background: var(--bg-3); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--r-sm); padding: 6px 6px; font-size: var(--fs-sm); outline: none;
}
/* 用量按天“走势”列（比例条独立成列，不跟金额挤在一格） */
.usage-trend-td { min-width: 72px; }
.usage-cost-bar {
  display: inline-block; width: 64px; height: 6px;
  background: var(--bg-3); border-radius: var(--r-sm); overflow: hidden;
  vertical-align: middle;
}
.usage-cost-bar i { display: block; height: 100%; background: var(--accent); opacity: .75; }
/* 添加提供商：两列模型行给足呼吸空间 */
.model-rows-table input { padding: 8px 10px; font-size: var(--fs-sm); }
.model-rows-table th { padding: 6px 8px; }
.model-rows-table td { padding: 6px 8px; }
/* 前端构建戳变了时的刷新提示（部署后单页应用不会自己换新前端） */
#ui-build-banner {
  position: fixed; top: 12px; left: 50%; transform: translateX(-50%);
  z-index: 9999; cursor: pointer;
  border: 1px solid var(--accent); border-radius: var(--r-pill);
  background: var(--bg-2); color: var(--accent);
  font-size: var(--fs-sm); padding: 6px 14px; box-shadow: var(--shadow-md);
}
#ui-build-banner:hover { background: var(--bg-3); }
@media (max-width: 600px) {
  /* 移动端：状态 pills 允许整段换行显示，不再 OneB…/模型：dee… 截断 */
  .top-status > .muted { white-space: normal; overflow: visible; text-overflow: clip; }
}
.context-empty {
  min-height: 116px;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: center;
}
.context-warning {
  margin-bottom: 10px;
  border-left: 3px solid var(--orange);
  background: rgba(255, 180, 84, .08);
  color: var(--muted);
  padding: 7px 10px;
  font-size: var(--fs-xs);
}
.reasoning-entry { padding: 0 0 14px; margin-bottom: 14px; border-bottom: 1px solid var(--border); }
.reasoning-entry:last-child { margin-bottom: 0; border-bottom: none; }
.reasoning-entry-head { margin-bottom: 7px; color: var(--accent); font-size: var(--fs-xs); font-weight: 600; }

.collapsible { border: 1px solid var(--border); border-radius: var(--r-sm); margin-bottom: 14px; background: var(--bg-2); }
.collapsible > summary::before { content: '▸ '; }
.collapsible[open] > summary::before { content: '▾ '; }
.collapsible .coll-body { padding: 4px 14px 12px; white-space: pre-wrap; font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); line-height: 1.65; max-height: 300px; overflow-y: auto; }
.memory-handoff .coll-body { max-height: none; overflow: visible; white-space: normal; font-family: inherit; color: var(--text); font-size: var(--fs-md); line-height: 1.6; }
/* 表单区不再是一整块死白：跟页面其他表面一样带一点透感，但**不加 backdrop-filter**
   （父级已经是玻璃，嵌套会让子层采到空 backdrop root，整块发白）。 */
.memory-handoff { background: color-mix(in srgb, var(--bg-2) 82%, transparent); }
.memory-handoff-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 10px; }
.memory-handoff-actions .muted { margin-right: auto; font-size: var(--fs-xs); }

/* ── 会话记忆详情：先给结论，再给表单 ──
   2026-10-19 用户反馈"详情页太丑、不知道这页干什么"。原先是六个空 textarea 直接铺满第一屏，
   既没有状态也没有说明。现在：标题行给状态，下面一张摘要卡回答"当前是什么情况"，
   表单只在需要时展开，结构化字段再收一层。 */
.memory-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.memory-state {
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .2px;
}
.memory-state.is-live {
  border-color: color-mix(in srgb, var(--green) 42%, var(--border));
  background: color-mix(in srgb, var(--green) 12%, transparent);
  color: var(--green);
}
.memory-summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: color-mix(in srgb, var(--bg-2) 72%, transparent);
}
.memory-summary.is-empty { border-style: dashed; background: transparent; }
.memory-summary-main { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.memory-summary-kicker { color: var(--muted); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .4px; }
.memory-summary-main strong { font-size: var(--fs-md); line-height: 1.45; }
.memory-summary-main p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.65;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.memory-summary-meta { flex: 0 0 auto; color: var(--faint); font-size: var(--fs-xs); line-height: 1.65; text-align: right; }
.memory-section { padding: 14px 0; border-top: 1px solid var(--border); }
.memory-section:first-child { padding-top: 2px; border-top: 0; }
.memory-section-head h3 { margin: 0 0 3px; font-size: var(--fs-sm); font-weight: 600; }
.memory-section-head p { margin: 0 0 10px; color: var(--muted); font-size: var(--fs-xs); line-height: 1.6; }
.memory-field-hint { display: block; margin-top: 5px; color: var(--faint); font-size: var(--fs-2xs); }
/* 两列就够：这一页是"表单里的一个折叠区"，三轨会把每个框压到 380px 以下。
   用两级类名压过后面的全局 .field-row（含 1240/760 的降列规则），窄屏再手动还原。 */
.memory-handoff .field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.memory-handoff .memory-row-topic { grid-template-columns: minmax(0, 2fr) minmax(180px, 1fr); }
.memory-advanced {
  margin: 12px 0;
  padding: 10px 12px 2px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--bg-3) 55%, transparent);
}
.memory-advanced > summary { cursor: pointer; font-size: var(--fs-sm); font-weight: 600; }
.memory-advanced > .memory-field-hint { margin: 4px 0 8px; }
.memory-advanced textarea { min-height: 64px; }
.memory-handoff textarea { resize: vertical; }
@media (max-width: 760px) {
  .memory-handoff .field-row,
  .memory-handoff .memory-row-topic { grid-template-columns: minmax(0, 1fr); }
  .memory-summary { flex-direction: column; gap: 8px; }
  .memory-summary-meta { text-align: left; }
}

.msg-flow { display: flex; flex-direction: column; gap: 10px; }

.bubble {
  max-width: 82%;
  padding: 9px 13px;
  border-radius: var(--r-card);
  line-height: 1.6;
  word-break: break-word;
}
.bubble-user { align-self: flex-start; background: var(--bg-3); border: 1px solid var(--border); white-space: pre-wrap; }
.bubble-assistant { align-self: flex-end; background: rgba(91,140,255,.12); border: 1px solid rgba(91,140,255,.3); }
.bubble-assistant .asr-label { color: var(--muted); font-size: var(--fs-xs); margin-bottom: 4px; }

.tool-card {
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-2);
  padding: 9px 13px;
  font-size: var(--fs-sm);
}
.tool-card.tool-error { border-color: rgba(255,107,107,.5); }
.tool-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.tool-name { color: var(--orange); font-family: var(--mono); font-size: var(--fs-sm); font-weight: 600; }
.tool-args { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); white-space: pre-wrap; }
.tool-result { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); margin-top: 5px; white-space: pre-wrap; border-top: 1px dashed var(--border); padding-top: 5px; }
.tool-result.is-error { color: var(--red); }

.sent-badge {
  align-self: flex-end;
  background: rgba(63,206,122,.12);
  border: 1px solid rgba(63,206,122,.4);
  color: var(--green);
  border-radius: var(--r-card);
  padding: 7px 13px;
  font-size: var(--fs-md);
  line-height: 1.6;
}
.sent-badge .asr-label { color: rgba(63,206,122,.7); font-size: var(--fs-xs); margin-bottom: 3px; }

.session-error {
  border: 1px solid rgba(255,107,107,.5); background: rgba(255,107,107,.08);
  color: var(--red); border-radius: var(--r-sm); padding: 10px 14px; white-space: pre-wrap;
}

/* ── 存档 ── */
.chat-item-title { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.chat-item-title .chat-runtime-control { margin-left: auto; flex: 0 0 28px; width: 28px; height: 28px; }
.incident-severity { font-weight: 600; }
.incident-severity.severity-critical { color: var(--red); }
.incident-severity.severity-error { color: var(--orange); }
.incident-severity.severity-warning { color: var(--yellow, #e6b94a); }
.incident-severity.severity-info { color: var(--muted); }
.incident-table td { vertical-align: top; }
.incident-table summary { cursor: pointer; max-width: 420px; white-space: normal; }
.incident-detail { margin-top: 8px; color: var(--muted); line-height: 1.6; max-width: 520px; overflow-wrap: anywhere; }
.incident-actions { min-width: 72px; flex-wrap: nowrap; }
.chat-item.unread-row { background: rgba(255,180,84,.07); }
.unread-pill { background: var(--red); color: #fff; font-size: var(--fs-xs); border-radius: var(--r-input); padding: 0 7px; }
.chat-item-sub { color: var(--muted); font-size: var(--fs-sm); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-chip { cursor: pointer; font-size: var(--fs-sm); color: var(--text); background: var(--bg-3); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 4px 8px; }
.member-chip:hover { border-color: var(--accent); }
.member-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.member-table td, .member-table th { padding: 5px 6px; border-bottom: 1px solid var(--border); text-align: left; }
.member-table th { color: var(--muted); font-weight: 600; position: sticky; top: 0; background: var(--bg-2); }

.chat-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.chat-action-result { max-width: 220px; font-size: var(--fs-xs); overflow-wrap: anywhere; }
#test-send-text {
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--r-sm);
  padding: 5px 10px;
  font-size: var(--fs-sm);
  outline: none;
}
#test-send-text:focus { border-color: var(--accent); }

.archive-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.archive-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
.archive-table .t { color: var(--muted); white-space: nowrap; font-family: var(--mono); font-size: var(--fs-xs); }
.archive-table .w { white-space: nowrap; max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
.archive-table .w.self { color: var(--voice); }   /* 它自己发的消息 = 冷青（原来错用了强调蓝）*/
.archive-table .unread td { background: rgba(255,180,84,.05); }
.archive-table .text { word-break: break-word; white-space: pre-wrap; }

/* ── 设置 ── */
.settings-sidebar {
  width: 220px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  background: var(--bg-2);
  overflow-y: auto;          /* 菜单比窗口还高时自己滚（shadcn 的 SidebarContent 也是这个做法） */
  /* 滚到底之后不要把手势"传染"给外层表单（滚动链）；滚动条出现/消失也别让菜单宽度跳一下 */
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  padding: 14px 10px;
  /* 设置页整体滚动后：侧栏钉在视图顶部、保持一屏高（边框与底色不会中途断掉）。
     它自己没超出内容时不拦截滚轮 —— Chrome 会把手势交给外层可滚动祖先。 */
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100%;
  max-height: 100%;
}
.settings-runstate {
  border: 1px solid var(--border);
  border-radius: var(--r-input);
  background: var(--bg);
  padding: 10px 12px;
  margin-bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--fs-sm);
}
.settings-runstate .rs-title { font-weight: 600; font-size: var(--fs-md); }
.settings-runstate .rs-row { display: flex; align-items: center; gap: 8px; }
.settings-runstate .rs-row .dot { width: 8px; height: 8px; }
/* 分区菜单：与主导航同一套"选中块滑过去"（滑块实现见 ui/core/nav-pill.js）。
   这里给它定位上下文、内缩 0（菜单项本来就是满宽）、圆角与菜单项对齐。 */
.settings-menu { position: relative; display: flex; flex-direction: column; gap: 2px; --pill-inset: 0px; }
.settings-menu .side-pill { border-radius: var(--r-sm); }
.settings-menu-item {
  position: relative;
  background: none; border: none; text-align: left;
  color: var(--muted); padding: 8px 12px; border-radius: var(--r-sm);
  cursor: pointer; font-size: var(--fs-md); width: 100%;
  /* line-height: normal 会按字形度量算行高：中文 ≈1.43、纯拉丁文字（「OneBot」）≈1.2，
     同一列菜单里那一行会矮 4px（实测 31.3 vs 35.3）。给一个数值口径，中文/拉丁同高。 */
  line-height: 1.4;
}
/* 选中项的底色交给滑块 —— 否则滑块滑过来时旧格子还留着自己那一层底色 */
.settings-menu-item:not(.active):hover { background: var(--bg-3); color: var(--text); box-shadow: none; }
.settings-menu-item.active { background: transparent; color: var(--accent); font-weight: 600; }
/* 历史遗留块（文件后半段的 `.chat-item.selected, …, .settings-menu-item.active`）会给选中项
   再上一层 --accent-soft 底 + 3px 左边框，`:hover` 也会盖一层 —— 权重相同但它更靠后，所以赢。
   分区菜单的底色与左缘条都归滑块，这里多一层 .settings-menu 把权重提上去压回来：
   否则选中项是"两层色 + 两条边"，而且那 3px 边框会把文字推右 3px（换分区时标签抖一下）。 */
.settings-menu .settings-menu-item.active { background: transparent; border-left: 0; }
.settings-menu .settings-menu-item.active:hover { background: transparent; box-shadow: none; }

/* ── 设置页：滚动轴放在整个视图上（窄屏本来就是这么做的）──
   原来 .settings-pane 自己是 max-width:860px 居中的滚动容器：它左右两侧的空白
   没人接滚轮，光标落在那边时滚轮"没反应"，只有落在中间那一列（或它贴着的滚动条）
   才滚得动 —— 反馈就是"滚轮只有放滚动条旁边才能滚"。现在整片视图一起滚。 */
#view-settings { overflow-x: hidden; overflow-y: auto; }
.settings-menu { padding-top: 2px; }
/* 阅读宽度限制放在容器内的"一列"上，而不是给容器加居中 padding。
   百分比 padding 是按父容器宽度解析的，用它给窄内容居中会把内容越挤越窄
   （1920 宽的窗口里只挤出 640px），表单网格随之从多列掉成 1 列 —— 字段全竖着排。
   也不能用 `> * { max-width; margin-inline: auto }`：h3、分隔线这些有自己的
   margin 简写（如 `margin: 22px 0 12px`）会把 auto 覆盖掉，于是标题停在内容盒最左边、
   下面的框居中，标题看着比框"偏左"一大截。
   用单列网格统一约束：所有直接子元素都落在同一列里，谁都不用管居中的事。 */
.settings-pane {
  flex: 1; width: 100%; min-width: 0;
  max-width: none; margin: 0; overflow: visible;
  /* 盒子要跟着内容长高（不拉伸成一屏高）：否则里面的 sticky「保存设置」条
     会以为父容器只有一屏，滚过一屏就不粘了。 */
  align-self: flex-start;
  display: grid;
  grid-template-columns: minmax(0, 860px);
  justify-content: center;
  align-content: start;
  /* 底部不留内边距：这一页的最后一个元素永远是保存条，留着 40px 会在它下面露出一条
     与应用圆角不一致的空白带（"圆角有间隙"）。保存条自己带 12px 上下内边距。 */
  padding: 22px 26px 0;
}
.settings-pane h3 { margin: 22px 0 12px; font-size: var(--fs-md); color: var(--accent); }
.settings-pane h3:first-child { margin-top: 0; }
.settings-divider { border: none; border-top: 1px dashed var(--border); margin: 22px 0; }
.conversation-mode-shell { margin-bottom: 16px; }
.conversation-mode-switch {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.conversation-mode-option {
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-2);
  color: var(--muted);
  padding: 10px 12px;
  cursor: pointer;
  text-align: left;
}
.conversation-mode-option span,
.conversation-mode-option small { display: block; overflow-wrap: anywhere; }
.conversation-mode-option span { color: var(--text); font-size: var(--fs-md); font-weight: 650; }
.conversation-mode-option small { margin-top: 3px; color: var(--muted); font-size: var(--fs-xs); }
.conversation-mode-option:hover { border-color: color-mix(in srgb, var(--mode-color) 35%, var(--border)); }
.conversation-mode-option.active {
  border-color: color-mix(in srgb, var(--mode-color) 45%, var(--border));
  background: var(--mode-soft);
  box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--mode-color) 60%, transparent);
}
.conversation-mode-option.active span { color: color-mix(in srgb, var(--mode-color) 80%, var(--text)); }
.conversation-mode-stage {
  min-height: 190px;
  border-bottom: 1px solid var(--border);
}
.conversation-mode-panel {
  padding: 16px 2px 14px;
  animation: pageIn var(--dur-base) var(--ease-out-quart);
}
.conversation-mode-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}
.conversation-mode-heading strong,
.conversation-mode-heading span { display: block; }
.conversation-mode-heading strong { color: color-mix(in srgb, var(--mode-color) 85%, var(--text)); font-size: var(--fs-lg); }
.conversation-mode-heading > div > span { margin-top: 3px; color: var(--muted); font-size: var(--fs-sm); }
.mode-state-token {
  flex: 0 0 auto;
  border: 1px solid color-mix(in srgb, var(--mode-color) 30%, transparent);
  border-radius: var(--r-sm);
  background: var(--mode-soft);
  color: color-mix(in srgb, var(--mode-color) 82%, var(--text));
  padding: 4px 8px;
  font-size: var(--fs-xs);
}
.conversation-mode-flow {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  margin-bottom: 14px;
  color: var(--text);
  font-size: var(--fs-sm);
}
.conversation-mode-flow span {
  flex: 0 0 auto;
  border: 1px solid color-mix(in srgb, var(--mode-color) 32%, var(--border));
  border-radius: var(--r-sm);
  background: var(--bg-2);
  padding: 5px 8px;
}
.conversation-mode-flow i {
  flex: 1 1 24px;
  min-width: 12px;
  height: 1px;
  background: color-mix(in srgb, var(--mode-color) 45%, var(--border));
}
.conversation-mode-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin: 0;
}
.conversation-mode-facts div { min-width: 0; }
.conversation-mode-facts dt { color: var(--muted); font-size: var(--fs-xs); }
.conversation-mode-facts dd { margin: 4px 0 0; color: var(--text); font-size: var(--fs-sm); overflow-wrap: anywhere; }
.conversation-mode-fields { margin-bottom: 8px; }
.conversation-mode-fields .field { min-width: 180px; }
.conversation-scope {
  margin: 10px 0 20px;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.conversation-scope-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}
.conversation-scope-heading strong { font-size: var(--fs-md); }
.conversation-scope-heading span { color: var(--muted); font-size: var(--fs-xs); text-align: right; }
.conversation-scope-actions { display: flex; align-items: center; gap: 8px; }
.conversation-scope-actions .hint { margin: 0; }
.conversation-trigger-hint { margin: -6px 0 12px; }
.persona-role-text { min-height: 360px; font-size: var(--fs-sm); line-height: 1.7; font-family: var(--mono); }

/* ── 人设卡库 + 结构化正文视图 ──
   角色正文是 markdown，只给一个 textarea 看不出这张卡会让它怎么说话：
   卡库里一张卡一个方块，下面把正文按小节渲染 —— 招牌是标签、
   黑名单是打叉标签、示例是聊天气泡（✓ 可用 / ✗ 禁用）。 */
.persona-lib { margin-bottom: 12px; }
.persona-lib-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.persona-lib-head .pl-title { font-size: var(--fs-sm); color: var(--muted); }
.persona-lib-head .spacer { flex: 1; }
.persona-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(216px, 1fr)); gap: 10px; }
.persona-card {
  display: flex; flex-direction: column; gap: 6px; text-align: left; cursor: pointer;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--r-input); background: var(--bg-2);
  color: inherit; font: inherit;
  transition: border-color var(--dur-interaction) var(--ease-in-out), background var(--dur-interaction) var(--ease-in-out),
    transform var(--dur-interaction) var(--ease-out-quart);
}
.persona-card:hover { background: var(--bg-3); border-color: var(--accent); }
.persona-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.persona-card.selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.persona-card .pc-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.persona-card .pc-name { font-size: var(--fs-md); font-weight: 600; }
.persona-card .pc-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.persona-card .pc-tag { font-size: var(--fs-2xs); padding: 1px 7px; border-radius: var(--r-pill); border: 1px solid var(--border); color: var(--muted); }
.persona-card .pc-src { font-size: var(--fs-2xs); color: var(--faint); }
.persona-card .pc-desc { font-size: var(--fs-xs); color: var(--faint); line-height: 1.55; }

.persona-detail { border: 1px solid var(--border); border-radius: var(--r-card); background: var(--bg-2); overflow: hidden; }
.persona-detail .pd-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 12px; border-bottom: 1px solid var(--border); background: var(--bg-3);
}
.persona-detail .pd-head .pd-title { font-size: var(--fs-md); font-weight: 600; }
.persona-detail .pd-head .spacer { flex: 1; }
.chip {
  font-size: var(--fs-xs); padding: 1.5px 8px; border-radius: var(--r-pill); white-space: nowrap;
  border: 1px solid var(--border); color: var(--muted);
}
.chip.ok { color: var(--green); border-color: rgba(47, 208, 122, .45); background: rgba(47, 208, 122, .08); }
.chip.warn { color: var(--orange); border-color: rgba(247, 185, 85, .45); background: rgba(247, 185, 85, .08); }
.chip.star { color: var(--accent); border-color: var(--accent-border); background: var(--accent-soft); }

/* 正文视图不自己滚：整页只留 .settings-pane 一个纵向滚轮轴。
   之前这里是 max-height:520px + overflow:auto，卡片正文却有三千多像素，
   光标停在上面时滚轮全被内层吃掉，外面几乎不动 —— 用起来就像"滚轮失灵"。 */
.pd-body { padding: 12px 14px; }
.pd-headline { font-size: var(--fs-sm); color: var(--muted); margin-bottom: 8px; font-family: var(--mono); }
.pd-sec { border-top: 1px dashed var(--border); padding: 9px 0 3px; }
.pd-sec:first-child { border-top: none; padding-top: 0; }
.pd-sec-head { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.pd-sec-head .idx { font-family: var(--mono); font-size: var(--fs-xs); color: var(--faint); min-width: 14px; }
.pd-sec-head .name { font-size: var(--fs-sm); font-weight: 600; }
.pd-sec-head .caret { margin-left: 6px; color: var(--faint); transition: transform var(--dur-base) var(--ease-in-out); }
/* 逐节编辑：平时淡淡的，鼠标移上去才明显（触屏没有 hover，所以不做成完全隐藏） */
.pd-sec-actions { margin-left: auto; display: flex; gap: 6px; opacity: .5; transition: opacity var(--dur-fast) var(--ease-in-out); }
.pd-sec:hover .pd-sec-actions, .pd-sec.editing .pd-sec-actions { opacity: 1; }
.pd-sec-actions button {
  font: inherit; font-size: var(--fs-xs); padding: 1px 8px; border-radius: var(--r-sm); cursor: pointer;
  border: 1px solid var(--border); background: none; color: var(--muted);
}
.pd-sec-actions button:hover { color: var(--text); border-color: var(--accent); background: var(--hover); }
.pd-sec.editing .pd-sec-head .name { color: var(--accent); }
.pd-edit { padding: 6px 0 2px; }
.pd-edit-text {
  width: 100%; min-height: 170px; box-sizing: border-box; resize: vertical;
  background: var(--bg); color: var(--text); border: 1px solid var(--accent); border-radius: var(--r-sm);
  padding: 9px 11px; font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.65; outline: none;
}
.pd-edit-text:focus { box-shadow: 0 0 0 3px var(--accent-soft); }
.pd-edit-row { display: flex; align-items: center; gap: 8px; margin-top: 7px; flex-wrap: wrap; }
.pd-edit-hint { font-size: var(--fs-xs); }
.pd-sec.collapsed .caret { transform: rotate(-90deg); }
.pd-sec.collapsed .pd-sec-body { display: none; }
.pd-sec-body { padding: 7px 0 2px; font-size: var(--fs-sm); line-height: 1.75; color: var(--text); }
.pd-sec-body p { margin: 0 0 6px; }
.pd-list { margin: 0 0 6px; padding-left: 18px; }
.pd-list li { margin-bottom: 4px; }
.pd-body code { font-family: var(--mono); font-size: var(--fs-xs); background: var(--bg-3); border-radius: var(--r-sm); padding: 1px 4px; }
.pd-quote {
  margin: 8px 0; padding: 8px 10px; border-left: 3px solid var(--accent); border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--accent-soft);
}
.pd-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }
.pd-tag { font-size: var(--fs-xs); padding: 3px 9px; border-radius: var(--r-pill); border: 1px solid var(--border); background: var(--bg-3); }
.pd-tag.star { border-color: var(--accent-border); background: var(--accent-soft); }
.pd-tag.bad { border-color: rgba(255, 107, 107, .4); background: rgba(255, 107, 107, .08); color: #ffc9c9; }
.rule-chip { cursor: pointer; color: var(--muted); font: inherit; font-size: var(--fs-xs); }
.rule-chip:hover { color: var(--text); border-color: var(--accent); background: var(--hover); }
.rule-chip:active { background: var(--active); }
/* 人设身份区：四个短字段排一行。全局 .field-row 是固定三轨（为了跨行对齐），
   这一行有四个字段，塞三轨会把「参与度」挤到下一行、第一列还会因为带提示而更高。
   这里单独给四轨，窄屏降两列/一列；提示统一放在行下方（一个字段带提示、其余不带会显得突兀）。 */
/* 两段类名：.field-row 的基础定义在文件更后面，单类名会被它按顺序覆盖。 */
.field-row.persona-identity-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* 全站字段已统一「标签在上、控件在下」，这里不需要再单独覆盖排版。 */
.persona-identity-hint > div + div { margin-top: 4px; }
@media (max-width: 1240px) {
  .field-row.persona-identity-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
  .field-row.persona-identity-row { grid-template-columns: minmax(0, 1fr); }
}
/* 按群人设：会话多时按卡片网格排（每张卡：名称 + 下拉），不再一列拉到底。
   auto-fit 让宽屏 3 列、中等 2 列、窄屏 1 列；名称过长省略，不把下拉挤出去。 */
.per-chat-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 8px 14px;
  margin-top: 8px;
}
.per-chat-row {
  display: grid;
  grid-template-columns: minmax(0, .9fr) minmax(140px, 1.1fr);
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-input);
  background: var(--bg-2);
}
.per-chat-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-sm); color: var(--muted);
}
.per-chat-select { width: 100%; min-width: 0; max-width: none; }
@media (hover: hover) and (pointer: fine) {
  .per-chat-row:hover { border-color: var(--accent-border); }
}
@media (max-width: 760px) {
  .per-chat-list { grid-template-columns: minmax(0, 1fr); }
}
.pd-chat { margin: 6px 0 10px; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--r-input); background: var(--bg); }
.pd-msg { display: flex; align-items: flex-start; gap: 6px; margin: 4px 0; font-size: var(--fs-sm); }
.pd-msg .mark { font-family: var(--mono); font-size: var(--fs-xs); width: 12px; text-align: center; line-height: 1.6; }
.pd-msg .bubble { padding: 5px 9px; border-radius: var(--r-input); border: 1px solid var(--border); background: var(--bg-3); line-height: 1.6; }
.pd-msg .bubble .who { color: var(--muted); font-size: var(--fs-xs); }
.pd-msg.bad .mark { color: var(--red); }
.pd-msg.bad .bubble { opacity: .72; border-color: rgba(255, 107, 107, .35); }
.pd-msg.ok .mark { color: var(--green); }
.pd-empty { color: var(--faint); font-size: var(--fs-sm); padding: 10px 0; }
.model-rows-table { width: 100%; table-layout: fixed; border-collapse: collapse; margin-bottom: 6px; }
.model-rows-table th { text-align: left; font-size: var(--fs-xs); color: var(--muted); font-weight: 400; padding: 4px 8px; }
.model-rows-table td { padding: 6px 8px; vertical-align: middle; }
/* 表头首格要留出表格圆角的安全区，同时和下面输入框里的文字（border 0 + input padding 10）
   对齐；数据格保持 0，输入框自己贴齐这一列的左边。 */
.model-rows-table th:first-child { width: 46%; padding-left: 10px; }
.model-rows-table td:first-child { width: 46%; padding-left: 0; }
.model-rows-table th:nth-child(2), .model-rows-table td:nth-child(2) { width: 46%; }
.model-rows-table th:last-child { width: 8%; text-align: right; padding-right: 10px; }
.model-rows-table td:last-child { width: 8%; text-align: right; padding-right: 0; }
.model-rows-table input { width: 100%; background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: var(--r-sm); padding: 8px 10px; font-size: var(--fs-sm); outline: none; }
.model-rows-table input:focus { border-color: var(--accent); }

/* 模型选择/添加/删除 模态框 */
.model-modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: var(--overlay);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;             /* 小屏时留出边距，避免贴边 */
}
/* ⚠️ .model-modal 的基础定义必须完整写在这一处。
   曾经因为在动效段又写了一条 `.model-modal { animation: ... }`，
   清理重复时被当成"旧版"删掉，弹窗变成没有背景/边框的裸内容。
   追加属性请合并进下面这条规则，不要另开一条 .model-modal。 */
.model-modal {
  width: 760px; max-width: 94vw; max-height: 86vh;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--r-card);
  display: flex; flex-direction: column;
  padding: 16px; gap: 10px;
  box-shadow: var(--shadow-lg);
  /* 关键：允许内部收缩，否则内容会把弹窗撑出屏幕 */
  min-height: 0; overflow: hidden;
}
.model-modal.danger { border-color: rgba(255,107,107,.6); box-shadow: 0 0 0 1px rgba(255,107,107,.25), 0 12px 40px rgba(255,107,107,.12); }
.model-modal.danger .model-modal-head { color: var(--red); }

/* 头部：标题可换行，关闭按钮不参与压缩 */
.model-modal-head {
  font-weight: 600; display: flex; justify-content: space-between;
  align-items: flex-start; gap: 12px; flex-shrink: 0;
  font-size: var(--fs-lg); letter-spacing: .2px;
}
.model-modal-head > span {
  min-width: 0; overflow-wrap: anywhere;   /* 长模型名换行而非撑宽 */
}
.model-modal-close {
  background: none; border: none; color: var(--muted); font-size: var(--fs-xl);
  cursor: pointer; flex-shrink: 0; line-height: 1; padding: 0 2px;
}

/* 主体：min-height:0 是 flex 子项能正确收缩/滚动的前提。
   默认**纵向**堆叠 —— 明细、批量编辑这类弹窗都是从上往下排的。
   需要左右分栏的（模型目录）请显式加 class="model-modal-body row"。 */
.model-modal-body {
  display: flex; flex-direction: column; gap: 12px;
  min-height: 0; flex: 1; overflow: hidden;
}
.model-modal-body:not(.row) { overflow-y: auto; overflow-x: hidden; }
.model-modal-body.row { flex-direction: row; }
/* 纵向模式下表格自己滚动，不再依赖外层横向布局 */
.model-modal-body > .usage-table-scroll { flex: 1; min-height: 0; overflow: auto; }
.model-modal-left {
  /* 用 flex-basis 而非固定 width：窄屏时按比例缩小，不会把右侧挤成竖条 */
  flex: 0 0 200px; max-width: 40%;
  overflow-y: auto; border-right: 1px solid var(--border); padding-right: 8px;
  min-width: 96px;
}
.model-modal-right { flex: 1; min-width: 0; overflow-y: auto; }

/* 窄屏：纵向堆叠，彻底避免挤压 */
@media (max-width: 560px) {
  .model-modal { padding: 12px; }
  .model-modal-body { flex-direction: column; }
  .model-modal-left {
    flex: 0 0 auto; max-width: none;
    border-right: none; border-bottom: 1px solid var(--border);
    padding-right: 0; padding-bottom: 8px; max-height: 30vh;
  }
}

/* 弹窗内的表格：容器可滚动，单元格不撑破 */
.model-modal .usage-table { width: 100%; table-layout: auto; }
.model-modal .usage-table td,
.model-modal .usage-table th {
  overflow-wrap: anywhere;      /* 长内容换行，避免"字竖着排" */
  white-space: normal;
}
.model-modal .usage-table td:first-child,
.model-modal .usage-table th:first-child { max-width: 22ch; }
.mm-prov { padding: 7px 10px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-md); color: var(--muted); }
.mm-prov:hover { background: var(--bg-3); color: var(--text); }
.mm-prov.active { background: var(--bg-3); color: var(--accent); font-weight: 600; }
.mm-model { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-md); }
.mm-model:hover { background: var(--bg-3); }
.mm-model .mm-check { color: var(--accent); width: 16px; }
.mm-model .mm-del { margin-left: auto; border: 1px solid rgba(255,107,107,.5); color: var(--red); background: none; border-radius: var(--r-sm); padding: 2px 8px; cursor: pointer; font-size: var(--fs-xs); }
.mm-model .mm-del:hover { background: rgba(255,107,107,.12); }
.model-modal-foot { display: flex; gap: 10px; justify-content: flex-end; }
/* 旧的 .field-row 定义（display:flex + gap:14px + margin-bottom:10px）与 `.field { flex:1;
   min-width:220px }` 都已删除：它们被下面那份网格定义（.field-row 的 grid 版、.field 的
   min-width:0）整条覆盖，留着只会让人以为 .field-row 还是 flex 容器 —— 2026-10-01 修对齐时
   顺手清掉，两处都是实测确认无效的死规则。 */
.field label { display: block; font-size: var(--fs-sm); color: var(--muted); margin-bottom: 4px; }
/* 这一版（bg-2 / 7px 圆角 / padding 7 10 / 13px）已被文件末尾「表单控件 v2」整条取代 ——
   它的几何与字号跟别处各写一套，正是"同一页里输入框高低不一"的来源。只剩宽度还有人读。 */
.field input[type=text], .field input[type=password], .field input[type=number],
.field input[type=search], .field select, .field textarea { width: 100%; }
.checkbox-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-size: var(--fs-md); }
.experimental-settings .checkbox-row { flex-wrap: wrap; }
.experimental-settings .checkbox-row .muted { flex: 1 1 220px; min-width: 0; }
.hint { font-size: var(--fs-xs); color: var(--muted); margin-top: 3px; line-height: 1.5; }
.settings-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 14px 0; }
.daily-moments-status { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.daily-moments-content { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6; }
.daily-moments-status .field { min-width: 0; margin-bottom: 12px; overflow-wrap: anywhere; }
.daily-moments-status .table-wrap { overflow-x: auto; }
.moment-window-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(60px, .7fr) 40px; align-items: end; gap: 10px; margin-bottom: 10px; }
.moment-window-row .field { min-width: 0; margin: 0; }
/* 与全站表单控件同高（40px）：原来写死 34px，时间范围这一行比同页其它输入框矮一截。 */
.moment-window-row input { min-width: 0; width: 100%; height: 40px; box-sizing: border-box; }
.moment-window-remove { width: 40px; height: 40px; padding: 0; }
.moment-schedule { margin-bottom: 14px; min-width: 0; }
.moment-schedule summary { cursor: pointer; padding: 8px 0; font-size: var(--fs-md); }
@media (max-width: 420px) {
  .moment-window-row { gap: 6px; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 58px 40px; }
  .moment-window-row input { padding: 6px 3px; }
}
.moment-error { color: var(--red); overflow-wrap: anywhere; margin: 12px 0; font-size: var(--fs-md); }
.qzone-run-detail-row td { padding: 0 10px 10px; background: var(--bg-2); }
.qzone-run-details { display: grid; gap: 10px; min-width: 280px; }
.qzone-run-detail { display: grid; gap: 4px; padding-left: 10px; border-left: 2px solid var(--border); }
.qzone-run-detail div { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.5; }
.qzone-run-detail small { display: block; color: var(--muted); overflow-wrap: anywhere; }
.moment-summaries { padding: 12px 0; border-top: 1px solid var(--border); }
.moment-summaries summary { cursor: pointer; font-size: var(--fs-md); margin-bottom: 12px; }
.time-control-settings { max-width: 960px; min-width: 0; }
.time-control-settings > .field, #tc-rule-editor > .field { margin: 14px 0; }
.time-control-summary { display: flex; gap: 16px; flex-wrap: wrap; color: var(--muted); font-size: var(--fs-sm); margin: 12px 0; }
.time-control-preset { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 10px; font-size: var(--fs-md); }
.time-control-preset dd { margin: 0; overflow-wrap: anywhere; }
.tc-window { display: grid; grid-template-columns: minmax(0, 1fr) 130px 110px 28px; align-items: end; gap: 12px; padding: 16px 0; border-top: 1px solid var(--border); }
.tc-window .field { min-width: 0; }
.tc-days { display: flex; flex-wrap: wrap; gap: 10px; align-self: center; }
.tc-days label { display: flex; align-items: center; gap: 4px; font-size: var(--fs-sm); white-space: nowrap; }
.tc-window input[type=time] { width: 100%; min-width: 0; background: var(--bg-2); color: var(--text); border: 1px solid var(--border); padding: 7px 10px; border-radius: var(--r-sm); font: inherit; }
@media (max-width: 900px) {
  .tc-window { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 28px; }
  .tc-days { grid-column: 1 / -1; }
}
@media (max-width: 600px) {
  #view-settings { flex-direction: column; overflow-y: auto; }
  #view-settings .settings-sidebar {
    width: 100%; flex: 0 0 auto; border-right: 0; border-bottom: 1px solid var(--border);
    /* 窄屏是"菜单横条在上、内容在下"的单列布局：这里不钉住，跟着一起滚 */
    position: static; height: auto; max-height: none;
  }
  #view-settings .settings-runstate { display: none; }
  #view-settings .settings-menu {
    flex-direction: row; overflow-x: auto; gap: 6px;
  }
  #view-settings .settings-menu-item { flex: 0 0 auto; width: auto; }
  /* 横排时滑块的位移只算 Y，会盖住整条菜单 → 不用滑块，选中态回到逐项底色（与主导航窄屏同一口径） */
  #view-settings .settings-menu .side-pill { display: none; }
  #view-settings .settings-menu-item.active { background: var(--bg-3); }
  #view-settings .settings-pane {
    padding: 16px; min-width: 0; flex: 0 0 auto; overflow: visible;
  }
  .asset-pane { padding: 16px; }
  .asset-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .asset-toolbar { align-items: stretch; flex-direction: column; }
  .asset-kinds { overflow-x: auto; }
  .asset-kinds button { flex: 0 0 auto; }
  .asset-search input { width: 100%; min-width: 0; }
  .asset-sticker-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .asset-table-wrap { max-height: none; }
  .time-control-settings .field { min-width: 0; }
}
/* 保存条：渲染在内容末尾 + 贴底悬浮，长页面里从顶部就能看到、一直悬在视口底部，
   滚到底时正好落在内容末尾。全站只有一个保存按钮（分区里不再各放一个）。
   2026-10-19 用户反馈"单独占一块、固定在屏幕上，太生硬"：原来它是贴着面板左右和下缘的一条
   直角玻璃带（顶边一条 1px 直线 + 上投影），看起来像页脚。现在不做"条"了 ——
   改成**渐进模糊（progressive blur）**：没有边框、没有底色块，内容滚到底部时被逐渐糊掉、
   淡出，按钮浮在这层雾里。视觉上是一条柔和的下沉带，而不是页面被切成两截。
   用 mask 让模糊与底色一起从下往上淡出，所以看不到任何矩形边缘。 */
.save-bar {
  position: sticky; bottom: 0; z-index: 3;
  margin: 0;
  padding: 34px 0 16px;
  display: flex; gap: 10px; align-items: center; justify-content: flex-end;
  border: 0;
  background: linear-gradient(to top,
      color-mix(in srgb, var(--bg) 86%, transparent) 0%,
      color-mix(in srgb, var(--bg) 55%, transparent) 60%,
      transparent 100%);
  backdrop-filter: blur(7px) saturate(140%);
  -webkit-backdrop-filter: blur(7px) saturate(140%);
  -webkit-mask-image: linear-gradient(to top, #000 55%, transparent 100%);
  mask-image: linear-gradient(to top, #000 55%, transparent 100%);
  box-shadow: none;
  /* 上半截是"看不见的雾"：必须让点击穿过去，否则会挡住底下正在滚动的输入框。
     雾里唯一该响应点击的是按钮（状态文字只是文本）。 */
  pointer-events: none;
}
.save-bar > .btn { pointer-events: auto; order: 2; flex: 0 0 auto; }
/* 状态文字可长可短（"已保存 ✓" / "保存失败：…"）：让它能在条里缩短并省略，
   完整内容挂 title（见 settings-bind.js）。不给 min-width: 0 的话 nowrap 的 flex 项不肯收缩。 */
#cfg-save-result {
  order: 1;
  min-width: 0;
  max-width: 40ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-sm);
  /* 父级 .save-bar 是 pointer-events: none 的"雾"：状态文字必须把指针事件拿回来，
     否则保存失败的错误既不能选中复制，title 的原生 tooltip 也不会出现。 */
  pointer-events: auto;
  user-select: text;
}

/* ── 动效 ── */
/* 入场：新增条目（会话/存档列表的新行）。参照实现的 log-row-in 同款（透明度 + 4px 位移）。 */
@keyframes itemIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@keyframes checkPop { 0% { transform: scale(.85); } 60% { transform: scale(1.18); } 100% { transform: scale(1); } }
@keyframes savedFlash { 0% { color: var(--green); } 100% { color: var(--muted); } }
/* 页面切换：轻微上浮淡入，比纯淡入多一点"到位"感 */
@keyframes pageIn { from { opacity: 0; transform: translateY(var(--shift-sm)); } to { opacity: 1; transform: none; } }
/* 抽屉/面板从右侧滑入 */
/* 呼吸：用于"运行中"这类持续状态，很轻，不抢注意力 */
@keyframes breathe { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
/* 脉冲环：保存成功、新消息这类瞬时正反馈 */
@keyframes pulseRing {
  0% { box-shadow: 0 0 0 0 rgba(63, 206, 122, .45); }
  70% { box-shadow: 0 0 0 8px rgba(63, 206, 122, 0); }
  100% { box-shadow: 0 0 0 0 rgba(63, 206, 122, 0); }
}
/* 骨架微光：加载占位（如果以后用到） */
@keyframes shimmer { 100% { transform: translateX(100%); } }

/* ── 组件动效挂载 ──
   统一用 token 里的时长/曲线，不要在这里再写死 ms 值。 */


.session-item.new-item, .chat-item.new-item { animation: itemIn var(--dur-base) var(--ease-out-quart); }
/* 表格行不要挂入场动画：表格每次刷新都会重建行，动画会跟着重放＝整片闪。
   需要入场感的地方用「新增行才加类」的方式（见 .session-item.new-item）。 */
.member-table tr { animation: none; }

/* 页面容器上的入场动画已移除：这些容器的内容会被整块重建，
   动画会在每次刷新时重放（观感＝整页闪一下）。页签切换本身是即时切换，不需要它。 */

/* 统计卡片：悬浮轻微抬升（1px 位移 + 阴影），让"可看"变成"可感" */
.usage-card {
  transition:
    transform var(--dur-slow) var(--ease-out-quart),
    box-shadow var(--dur-slow) var(--ease-out-quart),
    background-color var(--dur-slow) var(--ease-in-out),
    border-color var(--dur-slow) var(--ease-in-out);
}
.usage-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}
/* 成本卡片是重点数字，给更强的抬升 */
.usage-card.accent:hover { box-shadow: var(--shadow-lg); }

/* 表格行：悬浮时背景 + 左侧强调条，帮助横向阅读时不串行 */
.usage-table tbody tr {
  transition: background-color var(--dur-base) var(--ease-in-out);
}
.usage-table.clickable tbody tr:active { background: var(--active); }
/* 可点击行的 hover 由 .clickable 提供；这里补一个左侧指示条 */
.usage-table.clickable tbody tr:hover td:first-child {
  box-shadow: inset 2px 0 0 var(--accent);
}

/* 会话/会话列表条目：入场 + 悬浮位移（右侧一点点，暗示"可展开"） */
.session-item, .chat-item {
  transition:
    background-color var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out),
    transform var(--dur-interaction) var(--ease-out-quart);
}
/* 悬浮位移（translateX(2px)）搬到文件末尾「外观 v4」的 @media (hover: hover) 里了 ——
   触屏上 tap 会先触发 hover，列表行会横着抽一下。底色那条在 L421 已经有了，这里不再重复。 */
.session-item.selected, .chat-item.selected {
  transition:
    background-color var(--dur-base) var(--ease-in-out),
    border-color var(--dur-base) var(--ease-in-out),
    padding var(--dur-base) var(--ease-out-quart);
}

/* 状态点：运行/等待中呼吸，让用户一眼看出"还活着" */
.dot-wait { animation: breathe 1.8s var(--ease-in-out) infinite; }
/* 连接正常时不呼吸（静态更稳），只有异常态才动 —— 避免常态干扰 */

/* 页签：切换时的"下划线滑动"与选中条都由 .side-pill 承担（2026-10-08 改成会滑动的滑块，
   见「一级导航」区块）。这里原来那条 ::after 下划线 + tabSlide 动画是横向页签时代的遗留，
   现在会渲染成选中项底下多出的一根横线，和滑块重复，已删。 */

/* 档位按钮：按下时轻微缩放，选中态用 spring 弹一下 */
.tier-btn:active { transform: scale(.97); }
.tier-btn.on { animation: checkPop var(--dur-slow) var(--ease-spring); }

/* 保存成功：绿色脉冲环 + 文字闪一下 */
.saved-pulse { animation: pulseRing var(--dur-slower) var(--ease-out-quart); }
.saved-flash { animation: savedFlash var(--dur-slower) var(--ease-in-out); }

/* 折叠面板（<details>）的展开/收起高度过渡在文件末尾「外观 v4」里 ——
   那里用 ::details-content 做整个内容盒的过渡。原来这里给子元素单发一条淡入，
   会和那层高度过渡叠在一起（内容看着像闪两下），所以删掉。 */

/* ── 输入框：聚焦时边框/阴影变化，让"我在这里输入"很明确 ──
   .field 是布局容器不需要动效，动的是里面的控件。 */
/* 控件过渡与聚焦环统一在文件末尾「表单控件 v2」里定义。
   这里原来给 .field 的聚焦硬编码了一圈 `rgba(91,140,255,.12)` —— 换强调色时不跟着变，
   而且是"第二条焦点样式"，与 :focus-visible 那条互相不知道对方存在。 */

/* 页签：基础态过渡（.tab:hover 已有，这里补基础） */
.tab {
  transition:
    color var(--dur-base) var(--ease-in-out),
    background-color var(--dur-base) var(--ease-in-out),
    border-color var(--dur-base) var(--ease-in-out);
}

/* 模型目录：左侧厂商 / 右侧模型条目，悬浮高亮 + 选中态。
   ⚠️ 过渡定义统一在下面的「长列表性能」块里，这里不要再写一份
   （曾经写过，两处重复容易改错地方）。 */
.mm-prov.active,
.mm-model:hover { background: var(--hover); }
/* 勾选框弹一下，确认"选上了" */
.mm-model input[type=checkbox]:checked { animation: checkPop var(--dur-base) var(--ease-spring); }

/* 会话/会话条目：已有基础过渡，这里补入场与选中态的平滑 */
.session-item.selected,
.chat-item.selected { border-left-color: var(--accent); }
.chat-item.unread-row { animation: itemIn var(--dur-base) var(--ease-out-quart); }

/* 折叠面板头部：三角形旋转 */
.collapsible > summary::before {
  transition: transform var(--dur-base) var(--ease-out-quart);
}
.collapsible[open] > summary::before { transform: rotate(90deg); }
.collapsible { transition: border-color var(--dur-base) var(--ease-in-out); }

/* 复选框：勾选时的缩放反馈 */
.checkbox-row input[type=checkbox]:checked { animation: checkPop var(--dur-base) var(--ease-spring); }

/* 档位参数块：明暗切换（未生效档位变暗）的平滑过渡 */
.tier-param {
  transition:
    opacity var(--dur-slow) var(--ease-in-out),
    background-color var(--dur-slow) var(--ease-in-out),
    border-color var(--dur-slow) var(--ease-in-out);
}

/* ══════════════════════════════════════════════════════════════
   性能：长列表要降低动效成本
   ══════════════════════════════════════════════════════════════
   模型目录可能有 1000+ 条（"共 N 个模型"）。如果每条都挂 6 个属性的
   transition，浏览器要为上千个元素维护过渡状态，滚动/搜索时明显掉帧。

   对策：长列表条目只保留 background-color / color 这类**合成层属性**
   （不触发重排，GPU 友好），去掉 transform / box-shadow（会触发重排与重绘）。
   视觉上几乎无差别 —— 列表项本来也不需要"抬升"效果。 */
.mm-prov,
.mm-model,
.model-rows-table tr,
.member-table tr,
.usage-table tbody tr {
  transition:
    background-color var(--dur-fast) var(--ease-in-out),
    color var(--dur-fast) var(--ease-in-out);
}

/* 不要用 will-change 做"提前优化"：
   它会强制每个元素单独建合成层，上百个条目就是上百个层，反而吃内存、
   甚至更卡。只在真正需要持续动画的元素（呼吸状态点）上用。 */
.dot-wait { will-change: opacity; }

/* ── 按钮（完整定义，务必保持在一处）──
   ⚠️ 这里曾经只剩一条 `transition`，基础样式（背景/边框/圆角/padding/cursor）被
   前面的"重复清理"误删，导致所有按钮回落到浏览器默认外观。
   追加属性请合并进这条规则，不要另开 `.btn { ... }`，否则清理时会再次误删。 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--r-sm);
  padding: 7px 16px;
  cursor: pointer;
  font-size: var(--fs-md);
  font-family: inherit;
  line-height: 1.4;
  white-space: nowrap;
  transition:
    transform var(--dur-fast) var(--ease-out-quart),
    border-color var(--dur-base) var(--ease-in-out),
    color var(--dur-base) var(--ease-in-out),
    background-color var(--dur-base) var(--ease-in-out),
    box-shadow var(--dur-base) var(--ease-in-out),
    opacity var(--dur-base) var(--ease-in-out);
}
/* 悬浮：轻微抬升 + 阴影，给"可点"的暗示（位移只有 1px，克制） */
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn:not(:disabled):hover { box-shadow: var(--shadow-sm); }
/* 按压：回弹曲线，模拟物理按下的手感 */
.btn:active { transform: scale(.96); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent); border-color: var(--accent); color: #fff; opacity: .92; }
.btn-small { padding: 4px 12px; font-size: var(--fs-sm); }
.btn-danger { border-color: rgba(255,107,107,.5); color: var(--red); }
.btn-danger:hover { border-color: var(--red); color: var(--red); background: rgba(255,107,107,.1); }

.checkbox-row input[type=checkbox] { accent-color: var(--accent); transition: transform .1s ease; }
.checkbox-row input[type=checkbox]:checked { animation: checkPop .18s ease; }
.collapsible > summary { transition: color var(--dur-interaction) var(--ease-in-out); }
.collapsible > summary:hover { color: var(--text); }

#cfg-save-result.saved-flash { animation: savedFlash 1.1s ease; }

/* ── 横幅（预算/连接提示） ── */
.banner {
  padding: 8px 18px;
  font-size: var(--fs-md);
  background: rgba(255, 180, 84, .12);
  border-bottom: 1px solid rgba(255, 180, 84, .4);
  color: var(--orange);
  flex-shrink: 0;
}
.banner a { color: var(--accent); }
.banner.hidden { display: none; }

/* ── 体检卡 ── */
.h-item { display: flex; gap: 10px; align-items: baseline; padding: 3px 0; font-size: var(--fs-md); }
.h-item.ok { color: var(--green); }
.h-item.bad { color: var(--orange); }
.h-item.mute { color: var(--muted); }
.h-label { flex: 1; }

/* ── 白名单选择弹窗 ── */
.modal-overlay {
  position: fixed; inset: 0;
  background: var(--overlay);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
}
.modal {
  width: 480px; max-height: 70vh;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  display: flex; flex-direction: column;
  padding: 16px;
}
.modal-head { font-weight: 600; margin-bottom: 12px; }
.modal-list { overflow-y: auto; flex: 1; min-height: 120px; }
.pick-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-md);
}
.pick-item:hover { background: var(--bg-3); }
.pick-item span:first-of-type { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal-foot { display: flex; gap: 10px; justify-content: flex-end; margin-top: 12px; }

/* ── 滚动条贴边：列表/详情/设置面板都是块级容器，把滚动条放到容器最边缘而不是内容中间 */
#session-list, #chat-list, .detail-pane, .settings-pane, .modal-list {
  scrollbar-gutter: stable;
}

/* ── 模型目录面板（替代下拉框） ── */
.model-pick { position: relative; flex: 1; }
.model-pick-btn {
  width: 100%; display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--r-input);
  background: var(--bg-2); color: var(--text); cursor: pointer; font-size: var(--fs-md);
  text-align: left;
}

/* ── 模型目录弹窗 ── */
.model-pick-btn {
  flex: 1; display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--r-input);
  background: var(--bg-2); color: var(--text); cursor: pointer; font-size: var(--fs-md);
  text-align: left;
}
.model-pick-btn:hover { border-color: var(--accent, #6c8cff); }
.model-pick-btn .chev { margin-left: auto; opacity: .6; font-size: var(--fs-sm); }
.model-pick-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── 两栏悬停下拉（左供应商 / 右模型） ── */
.model-dd-wrap { position: relative; flex: 1; }
.model-dd {
  position: absolute; z-index: 70; top: calc(100% + 6px); left: 0;
  width: 560px; max-width: 80vw; height: 340px;
  display: flex; overflow: hidden;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--r-input);
  box-shadow: var(--shadow-lg);
}
.model-dd-left {
  width: 220px; flex-shrink: 0; overflow-y: auto; padding: 5px;
  border-right: 1px solid var(--border);
}
.model-dd-right { flex: 1; overflow-y: auto; padding: 5px 6px; }
.mdd-prov {
  display: flex; flex-direction: column; gap: 1px;
  padding: 7px 10px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-md);
}
.mdd-prov:hover, .mdd-prov.active { background: var(--bg-3); }
.mdd-prov-name { font-weight: 500; }
.mdd-prov-meta { font-size: var(--fs-xs); opacity: .6; }
.mp-provider {
  font-size: var(--fs-xs); opacity: .72; padding: 7px 8px 4px;
  display: flex; gap: 8px; align-items: baseline;
}
.mp-provider .mp-url { font-size: var(--fs-2xs); opacity: .7; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-md);
}
.mp-row:hover { background: var(--bg-3); }
.mp-row.current { background: var(--bg-3); }
.mp-row .mp-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vbadge {
  font-size: var(--fs-xs); padding: 1px 7px; border-radius: var(--r-pill); white-space: nowrap;
  border: 1px solid transparent;
}
.vbadge.ok { color: #3fae6a; border-color: rgba(63, 174, 106, .4); background: rgba(63, 174, 106, .1); }
.vbadge.no { color: #d06a5c; border-color: rgba(208, 106, 92, .4); background: rgba(208, 106, 92, .1); }
.vbadge.unk { color: var(--text); opacity: .5; border-color: var(--border); }

/* ── 全局：hidden 属性必须永远生效 ──
   作者样式里的 display（如 .model-dd { display:flex }）会压过浏览器对 [hidden] 的
   默认 display:none，导致元素"收不回去"（模型目录下拉两次踩同一个坑）。
   这条规则保证任何带 hidden 属性的元素一律不可见。 */
[hidden] { display: none !important; }
/* .hidden 工具类：与 [hidden] 属性同义（JS 侧用 classList 切换更方便）。
   曾经只有 .banner.hidden 一条专属规则 —— 其他地方（确认金句按钮、删除人设按钮）
   加了 hidden 类却没有任何效果，按钮常态显示。 */
.hidden { display: none !important; }

/* ── 用量/成本看板 ── */
.usage-pane { flex: 1; overflow-y: auto; padding: 22px 26px 40px; }

/* ── 服务与访问控制 ── */
.control-pane {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 22px 26px 40px;
}
.control-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.control-head h2 { margin: 0 0 3px; font-size: var(--fs-xl); }
.control-head span { font-size: var(--fs-xs); }
.control-service-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 9px;
}
.control-service {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  min-height: 66px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  /* 卡片角色（同一页的 .update-deploy-summary 也是卡片）：用 --r-card，别落回 --r-sm。
     --r-sm 是"小件"档，卡片用它会和下面对照表差 4px 圆角。 */
  border-radius: var(--r-card);
  background: var(--bg-2);
  color: var(--text);
  text-decoration: none;
}
.control-service:hover { border-color: var(--accent); background: var(--bg-3); }
.control-service-mark {
  display: grid;
  width: 36px;
  height: 36px;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-3);
  color: var(--accent);
  font-family: var(--mono);
  font-size: var(--fs-xs);
  font-weight: 700;
}
.control-service-copy { display: grid; min-width: 0; }
.control-service-copy strong,
.control-service-copy small { overflow-wrap: anywhere; }
.control-service-copy strong { font-size: var(--fs-md); }
.control-service-copy small { margin-top: 3px; color: var(--muted); font-size: var(--fs-2xs); }
.control-service-state { font-size: var(--fs-2xs); white-space: nowrap; }
.control-service-state.online { color: var(--green); }
.control-service-state.offline { color: var(--red); }
/* 旧架构服务没部署（不是故障）：走灰色，不再报警色 */
.control-service-state.idle { color: var(--muted); }
.control-section {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.control-section h3 { margin: 0 0 10px; font-size: var(--fs-md); }
.control-section-title {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.control-section-title h3 { margin-bottom: 3px; }
.control-section-title span { display: block; font-size: var(--fs-xs); }
.control-key-list {
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  background: var(--bg-2);
}
.control-key-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  width: 100%;
  min-height: 54px;
  padding: 9px 12px;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  text-align: left;
  text-decoration: none;
}
.control-key-row:last-child { border-bottom: 0; }
.control-key-row:hover { background: var(--bg-3); }
.control-key-row > span { display: grid; min-width: 0; }
.control-key-row strong { font-size: var(--fs-sm); }
.control-key-row small { margin-top: 2px; color: var(--muted); font-size: var(--fs-2xs); }
.control-key-row b { color: var(--accent); font-size: var(--fs-xs); font-weight: 500; }
.control-password-form {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
  align-items: end;
  gap: 9px;
  margin-top: 12px;
}
.control-password-form label { display: grid; gap: 5px; min-width: 0; }
.control-password-form label > span { color: var(--muted); font-size: var(--fs-sm); font-weight: 600; letter-spacing: .2px; }
.control-password-form input { min-width: 0; }
.control-password-form .btn { min-height: 34px; white-space: nowrap; }
.control-result { min-height: 18px; margin-top: 8px; font-size: var(--fs-xs); }
.control-result.success { color: var(--green); }
.control-result.error { color: var(--red); }
.update-deploy-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: 12px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  /* 四格各自有 --bg-2 底色：不给外层圆角就是一条方角白条，与旁边的圆角卡片同屏很扎眼 */
  border-radius: var(--r-card);
  overflow: hidden;
}
.update-deploy-summary > div { min-width: 0; padding: 12px 14px; }
.update-deploy-summary > div + div { border-left: 1px solid var(--border); }
.update-deploy-summary span,
.update-deploy-summary strong { display: block; overflow-wrap: anywhere; }
.update-deploy-summary span { color: var(--muted); font-size: var(--fs-2xs); }
.update-deploy-summary strong { margin-top: 4px; font-family: var(--mono); font-size: var(--fs-xs); }
/* 更新进度行：更新在跑时显示当前阶段与已耗时，避免"点了立即更新没反应"的观感 */
.update-deploy-progress {
  display: flex; align-items: center; gap: 8px;
  margin-top: 8px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--hover);
  font-size: var(--fs-sm); line-height: 1.5;
}
.update-deploy-progress .loading-spinner { width: 14px; height: 14px; border-width: 2px; flex: none; }
.update-deploy-progress-text { min-width: 0; overflow-wrap: anywhere; }
.update-deploy-progress-elapsed { color: var(--muted); flex: none; }
.update-deploy-settings {
  display: grid;
  /* auto-fill：字段铺满整行宽度，不留右侧空带（此前 1fr/.5fr/auto 的组合
     加上数字字段的 220px 限宽，整个表单挤在左半边） */
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  align-items: end;
  gap: 9px;
  margin-top: 12px;
}
.update-deploy-settings label { display: grid; gap: 5px; min-width: 0; }
.update-deploy-settings label > span { color: var(--muted); font-size: var(--fs-xs); }
.update-deploy-settings input { min-width: 0; width: 100%; }

@media (max-width: 600px) {
  .control-pane { padding: 16px; }
  .control-service-grid { grid-template-columns: 1fr; }
  .control-password-form { grid-template-columns: 1fr; }
  .update-deploy-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .update-deploy-summary > div:nth-child(3) { border-left: 0; border-top: 1px solid var(--border); }
  .update-deploy-summary > div:nth-child(4) { border-top: 1px solid var(--border); }
  .update-deploy-settings { grid-template-columns: 1fr; }
  .control-section-title {
    align-items: stretch;
    flex-direction: column;
  }
  .control-section-title .btn { align-self: flex-start; }
}

/* ── AI 资产观测 ── */
.asset-pane {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 22px 26px 40px;
}
.asset-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.asset-head h2 { margin: 0 0 3px; font-size: var(--fs-xl); }
.asset-head > div > span { font-size: var(--fs-xs); }
.asset-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
}
.asset-summary-item {
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--bg-2);
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.asset-summary-item:hover { border-color: var(--accent); background: var(--bg-3); }
.asset-summary-item span,
.asset-summary-item small { display: block; color: var(--muted); font-size: var(--fs-2xs); }
.asset-summary-item strong { display: block; margin: 3px 0; font-size: var(--fs-xl); }
.asset-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 16px 0 12px;
}
.asset-toolbar-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  min-width: 0;
}
.asset-toolbar-actions .asset-search { flex: 1; }
.asset-toolbar-actions > .btn { flex: 0 0 auto; }
.asset-kinds {
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  background: var(--bg-2);
  /* 分段控件：外壳圆角 + 裁掉子按钮的方角（否则选中块会在两端戳出直角） */
  border-radius: var(--r-sm);
  overflow: hidden;
}
.asset-kinds button {
  border: 0;
  background: transparent;
  color: var(--muted);
  padding: 6px 11px;
  cursor: pointer;
}
.asset-kinds button.active { background: var(--active); color: var(--accent); }
.asset-search { display: flex; gap: 6px; min-width: 0; }
.asset-search input { width: min(240px, 32vw); }
.asset-sticker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}
.asset-sticker {
  min-width: 0;
  /* 卡片的动作区必须贴在底边：同一行里描述有长有短，flex 列 + margin-top:auto 才能让
     「编辑/删除」在整行对齐（原来按内容自然流排，描述一行和五行差出一整行按钮的高度）。 */
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--bg-2);
}
.asset-sticker-media {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--bg-3);
  flex: 0 0 auto;
}
.asset-sticker-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.asset-sticker-body { padding: 8px; flex: 1; display: flex; flex-direction: column; }
.asset-sticker-body strong,
.asset-sticker-body > span,
.asset-sticker-body small {
  display: block;
  overflow-wrap: anywhere;
}
/* 标题与描述各最多两行：卡片高度不再由"最长的那条备注"决定（超出的全文仍在 title 里）。 */
.asset-sticker-body strong {
  font-size: var(--fs-sm);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.asset-sticker-body > span {
  margin-top: 3px;
  color: var(--muted);
  font-size: var(--fs-2xs);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.asset-sticker-body small { margin-top: 6px; color: var(--faint); font-size: var(--fs-2xs); }
.asset-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 8px;
  white-space: nowrap;
}
/* 贴底：描述短的卡片也把按钮压到与邻卡同一高度。 */
.asset-sticker-body > .asset-actions { margin-top: auto; padding-top: 8px; }
.asset-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.asset-tags span {
  padding: 2px 5px;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: var(--fs-2xs);
}
/* 异常处理页的表格外壳。原来 markup 里写了 class="table-wrap" 但 **CSS 里根本没有这条规则** ——
   于是它既不裁也不滚：820px 窗口下实测表格 1016px 撑出 758px 的卡片外面 258px
   （虽然外层 .asset-pane 能横滚，但表格明显越出卡片边界，看着就是坏的）。
   与 .asset-table-wrap / .usage-table-scroll 同一口径：外壳负责横向滚动。 */
.table-wrap { overflow-x: auto; }

.asset-table-wrap {
  max-height: calc(100vh - 330px);
  overflow: auto;
  border: 1px solid var(--border);
}
.asset-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-xs);
}
.asset-table th,
.asset-table td {
  padding: 7px 9px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.asset-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-3);
  color: var(--muted);
  font-weight: 500;
}
.asset-table td small { display: block; margin-top: 3px; color: var(--faint); }
.asset-table .r { text-align: right; }
/* 操作列：桌面端与 .asset-actions(flex-end) 右缘对齐；窄屏按钮改 flex-start，表头同步靠左。 */
.asset-table th.actions-col { text-align: right; }
@media (max-width: 600px) { .asset-table th.actions-col { text-align: left; } }
.asset-table tr:last-child td { border-bottom: 0; }

@media (max-width: 600px) {
  .asset-toolbar-actions { width: 100%; align-items: stretch; flex-direction: column; }
  .asset-toolbar-actions .asset-search { width: 100%; }
  .asset-actions { justify-content: flex-start; }
}

.usage-wrap { max-width: 940px; margin: 0 auto; }
.usage-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; gap: 12px; flex-wrap: wrap; }
.usage-head h2 { font-size: var(--fs-xl); font-weight: 600; }
.usage-days { display: flex; gap: 6px; }
/* 五张统计卡片固定一行排开（估算成本 / 调用 / 搜索 / 输入 / 缓存命中率）。
   minmax(0,1fr) 防止长数字把列撑爆；曾经第一张跨两列、五张卡占两行，
   空间利用率低，已按需求改为一行。 */
.usage-cards { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
/* 窗口太窄时退回自动换行，不把卡片压成纸条 */
@media (max-width: 860px) {
  .usage-cards { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
}
.usage-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--r-input); padding: 14px 16px; min-width: 0; }
.usage-card.accent { border-color: rgba(91,140,255,.45); background: rgba(91,140,255,.07); }
.uc-label { font-size: var(--fs-sm); color: var(--muted); margin-bottom: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.uc-value { font-size: var(--fs-2xl); font-weight: 600; font-family: var(--mono); line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 主指标（估算成本）靠 accent 描边/底色突出 —— 一行五列的布局里各卡同宽，
   不再靠放大字号做视觉重心（大数字在长金额下会溢出卡片）。 */
.uc-sub { font-size: var(--fs-xs); color: var(--muted); margin-top: 6px; }
.usage-bar { height: 5px; background: var(--bg-3); border-radius: var(--r-pill); overflow: hidden; margin-top: 8px; }
.usage-bar-fill { height: 100%; border-radius: var(--r-pill); background: var(--accent); }
.usage-bar-fill.ok { background: var(--green); }
.usage-note { font-size: var(--fs-sm); color: var(--muted); background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 10px 12px; margin-bottom: 20px; line-height: 1.6; }
.usage-h3 { font-size: var(--fs-md); font-weight: 600; margin: 22px 0 8px; color: var(--muted); }
/* 表格基础：统一一份（此前有两处定义、padding 还不一致，合并后取更舒适的行高） */
.usage-table {
  width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-sm);
  background: var(--bg-2);
  border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden;
}
/* border-collapse: collapse 时 border-radius 不生效（表头底色仍是方的，四个角会戳出白角）。
   改成 separate + 0 间距后，把四角交给首末行/列单元格 —— 视觉与 collapse 一致，圆角才是圆的。
   单元格的角比外框少 1px：外框有 1px 描边，内层用同一个值会在描边内侧多出一点白边。 */
.usage-table > thead > tr:first-child > th:first-child,
.usage-table > tr:first-child > :first-child { border-top-left-radius: calc(var(--r-card) - 1px); }
.usage-table > thead > tr:first-child > th:last-child,
.usage-table > tr:first-child > :last-child { border-top-right-radius: calc(var(--r-card) - 1px); }
.usage-table > tbody > tr:last-child > :first-child,
.usage-table > tr:last-child > :first-child { border-bottom-left-radius: calc(var(--r-card) - 1px); }
.usage-table > tbody > tr:last-child > :last-child,
.usage-table > tr:last-child > :last-child { border-bottom-right-radius: calc(var(--r-card) - 1px); }
/* 设置页的模型表同样是"圆角面板里一张方角白表" */
.model-rows-table { border-collapse: separate; border-spacing: 0; border-radius: var(--r-sm); overflow: hidden; }
.model-rows-table > thead > tr:first-child > th:first-child { border-top-left-radius: calc(var(--r-sm) - 1px); }
.model-rows-table > thead > tr:first-child > th:last-child { border-top-right-radius: calc(var(--r-sm) - 1px); }
.model-rows-table > tbody > tr:last-child > :first-child { border-bottom-left-radius: calc(var(--r-sm) - 1px); }
.model-rows-table > tbody > tr:last-child > :last-child { border-bottom-right-radius: calc(var(--r-sm) - 1px); }
.usage-table th, .usage-table td {
  padding: 8px 12px; text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.usage-table th { color: var(--muted); font-weight: 500; background: var(--bg-3); }
.usage-table tr:last-child td { border-bottom: none; }
.usage-table .r, .usage-table th.r { text-align: right; font-family: var(--mono); }
/* 名称列允许换行，其余列不换行 —— 长模型名不会撑爆布局，也不会被挤成竖排 */
.usage-table td:first-child,
.usage-table th:first-child {
  white-space: normal; overflow-wrap: anywhere; min-width: 120px; max-width: 260px;
}
.usage-table td:not(:first-child):not(.r) { overflow: hidden; text-overflow: ellipsis; }
/* 表头吸顶：长表格滚动时始终能看到列名 */
.usage-table thead th { position: sticky; top: 0; z-index: 1; background: var(--bg-3); }

/* ── 模型添加弹窗：搜索 + 双列（中转站常有几百个模型）── */
.ma-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-shrink: 0; }
.ma-toolbar input[type=text] {
  flex: 1; min-width: 0; padding: 7px 10px; font-size: var(--fs-md);
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text); outline: none;
}
.ma-toolbar input[type=text]:focus { border-color: var(--accent); }
/* 限高：中转站可能返回上千个模型，不限高会把弹窗撑出屏幕。
   两列各自滚动，搜索框和底部按钮始终可见。 */
.ma-body {
  display: flex; gap: 10px; min-height: 0; flex: 1; overflow: hidden;
  max-height: min(56vh, 460px);
}
.ma-body .model-modal-left,
.ma-body .model-modal-right { overflow-y: auto; min-height: 120px; }
.ma-body.single .model-modal-right { border-left: none; }
.ma-body.dual .model-modal-left { width: 190px; }
.mm-model-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mm-model input[type=checkbox] { flex-shrink: 0; accent-color: var(--accent); }

/* ── 峰谷拆分（仅 DeepSeek 等分时段计价的厂商会显示）── */
.usage-peak {
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 12px 14px; margin-bottom: 20px;
}
.up-title { font-size: var(--fs-sm); color: var(--muted); margin-bottom: 10px; }
.up-row { display: flex; align-items: center; gap: 10px; font-size: var(--fs-sm); margin-bottom: 7px; }
.up-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.up-dot.peak { background: var(--orange, #ffb454); }
.up-dot.off { background: var(--green, #3fce7a); }
.up-label { width: 62px; flex-shrink: 0; color: var(--muted); }
.up-val { width: 78px; flex-shrink: 0; font-family: var(--mono); }
.up-bar { flex: 1; min-width: 40px; height: 5px; background: var(--bg-3); border-radius: var(--r-pill); overflow: hidden; }
.up-bar i { display: block; height: 100%; background: var(--orange, #ffb454); }
.up-bar i.off { background: var(--green, #3fce7a); }
.up-pct { width: 78px; flex-shrink: 0; text-align: right; color: var(--muted); font-size: var(--fs-xs); }
.up-hint { font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; margin-top: 8px; opacity: .85; }

/* ── 用量页：可点击表格行 + 模态框内页签 ── */
.usage-hint { font-size: var(--fs-xs); color: var(--muted); font-weight: 400; margin-left: 6px; }
.usage-table.clickable tbody tr { cursor: pointer; }
.usage-table.clickable tbody tr:hover { background: var(--bg-3); }

/* 弹窗内的表格：可点击行 + 页签换行（弹窗比页面窄，页签要能折行） */
.ub-tabs { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
.ub-tabs .btn-small { white-space: nowrap; }
/* 弹窗里的峰谷块不需要页面版那么大的下边距 */
.usage-peak { margin-bottom: 12px; }

/* ── 模型单价卡片 + 批量编辑 ── */
.price-card {
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 12px 14px; margin-top: 8px;
}
.price-card.locked { opacity: .82; border-style: dashed; }
.pc-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.pc-title { font-size: var(--fs-sm); color: var(--muted); }
.pc-model { font-size: var(--fs-md); font-family: var(--mono); color: var(--accent); word-break: break-all; }
.pc-rows { display: flex; gap: 16px; flex-wrap: wrap; }
.pc-row { display: flex; align-items: center; gap: 6px; }
.pc-label { font-size: var(--fs-sm); color: var(--muted); width: 52px; }
.pc-row input[type=number] {
  width: 82px; padding: 5px 8px; font-size: var(--fs-sm); font-family: var(--mono);
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text); outline: none;
}
.pc-row input[type=number]:focus { border-color: var(--accent); }
.pc-row input[type=number]:disabled { opacity: .6; cursor: not-allowed; }
.pc-unit { font-size: var(--fs-xs); color: var(--muted); }
/* 生效价：只读展示（与下面可编辑的「自填单价」是两组控件） */
.pc-effective { font-size: var(--fs-md); font-family: var(--mono); color: var(--text); }
.pc-subhead { font-size: var(--fs-xs); color: var(--muted); margin: 12px 0 6px; padding-top: 8px; border-top: 1px dashed var(--border); }
.pc-note { font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; margin-top: 10px; }
.bp-del {
  border: 1px solid var(--border); background: none; color: var(--muted);
  border-radius: var(--r-sm); padding: 2px 8px; cursor: pointer; font-size: var(--fs-xs);
}
.bp-del:hover { color: var(--red); border-color: rgba(255,107,107,.5); }
.bp-in, .bp-out, .bp-cached {
  width: 74px; padding: 4px 6px; font-size: var(--fs-sm); font-family: var(--mono);
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text); outline: none;
}
.bp-in:focus, .bp-out:focus, .bp-cached:focus { border-color: var(--accent); }

/* ── 下钻明细弹窗 ── */
/* 表格外层可滚动，避免行多时撑破弹窗 */
.model-modal .usage-table { display: table; }
.model-modal > table.usage-table { display: block; overflow-y: auto; min-height: 0; }
.model-modal > table.usage-table thead,
.model-modal > table.usage-table tbody { display: table; width: 100%; }
.model-modal > table.usage-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-2);
}
/* 页签下方的说明文字（峰谷提示等）不参与压缩 */
.model-modal .hint,
.model-modal .up-hint { flex-shrink: 0; }
.model-modal #ub-peak { flex-shrink: 0; }

/* 下钻弹窗：整体纵向布局，表格区独占滚动 */
.ub-wrap {
  display: flex; flex-direction: column;
  min-height: 0; flex: 1; gap: 10px;
}
.ub-scroll {
  flex: 1; min-height: 120px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--r-sm);
}
.ub-scroll .usage-table { width: 100%; }
.ub-scroll thead th {
  position: sticky; top: 0; z-index: 1; background: var(--bg-2);
}

/* ── 读取历史档位：长条按钮 + 参数区 ── */
.tier-bar {
  display: flex; gap: 6px; margin: 8px 0 4px;
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 4px;
}
.tier-btn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 8px 4px; border: none; border-radius: var(--r-sm);
  background: transparent; color: var(--muted); cursor: pointer;
  font-size: var(--fs-sm); transition: background var(--dur-interaction) var(--ease-in-out), color var(--dur-interaction) var(--ease-in-out);
  min-width: 0;
}
.tier-btn:hover { background: var(--hover); }
.tier-btn.on {
  background: var(--accent); color: #fff;
}
.tier-num { font-size: var(--fs-lg); font-weight: 700; line-height: 1; }
.tier-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

.tier-params { margin-top: 12px; display: flex; flex-direction: column; gap: 10px; }
.tier-param {
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 10px 12px;
}
.tier-param.dim { opacity: .42; }
.tier-param label { display: block; font-size: var(--fs-sm); margin-bottom: 4px; }
.tier-param input[type=number] {
  width: 68px; padding: 3px 6px; font-family: var(--mono);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text); outline: none;
}
.tier-param input[type=number]:focus { border-color: var(--accent); }
.tier-param textarea {
  width: 100%; margin-top: 4px; padding: 6px 8px;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text); font-size: var(--fs-sm);
  font-family: var(--mono); resize: vertical; outline: none;
}
.tier-param textarea:focus { border-color: var(--accent); }

/* ══════════════════════════════════════════════════════════════
   UI 优化：通用控件
   ══════════════════════════════════════════════════════════════ */

/* 图标按钮（顶栏主题切换用）：方正规整，hover 有反馈，键盘可达 */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  background: transparent; color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  cursor: pointer; font-size: var(--fs-md); line-height: 1;
  transition: background var(--dur-interaction) var(--ease-in-out), border-color var(--dur-interaction) var(--ease-in-out);
}
.icon-btn:hover { background: var(--hover); border-color: var(--accent); }
.icon-btn:active { background: var(--active); }

/* 键盘可达性：焦点环统一到文件末尾那一条（"交互反馈 v3"）—— 这里原来是第二份实现，
   border-radius 与那边不一致，留着只会让下一个改的人分不清哪条生效。 */

/* 主题选择卡片已并入「设置 → 外观 → 显示模式」（分段控件），.theme-picker / .theme-option
   不再有任何标记生成它，相关样式一并删掉 —— 留着死样式最坏的情况是下一个改外观的人
   以为"改这里就改了外观"。 */

/* 滚动条样式统一到文件末尾「交互反馈 v3」那一条 —— 这里原先是最早的一版（thumb 描了
   2px 的背景色边），后面又叠了两版，四份规则叠在一起谁也说不清哪条生效。 */

/* 过渡：颜色变化时平滑，避免切换主题时"咔"一下 */
body, .btn, .tab, .model-modal, .price-card, .tier-btn, .icon-btn {
  transition: background-color var(--dur-interaction) var(--ease-in-out), color var(--dur-interaction) var(--ease-in-out), border-color var(--dur-interaction) var(--ease-in-out);
}

/* 列表底部的分页提示（滚动加载更多） */
.list-more {
  padding: 10px 14px;
  font-size: var(--fs-xs);
  text-align: center;
  border-top: 1px solid var(--border);
  min-height: 1px;
}
.list-more:empty { border-top: none; padding: 0; }

/* ── 骨架屏（用量页加载占位）──
   用微光扫过表示"正在加载"，比一行静态文字更能传达进度感。
   颜色用 --hover / --border，自动适配明暗主题。 */
.skeleton { pointer-events: none; }
.sk-line, .sk-row {
  display: block;
  border-radius: var(--r-sm);
  background: var(--hover);
  position: relative;
  overflow: hidden;
}
.sk-line { height: 13px; margin: 8px 0; width: 70%; }
.sk-line.short { width: 42%; }
.sk-row { height: 26px; margin: 6px 0; }
.sk-block { margin-top: 18px; }

/* 微光扫过：用 transform 而非 background-position（GPU 友好） */
.sk-line::after, .sk-row::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--active), transparent);
  animation: shimmer 1.4s var(--ease-in-out) infinite;
}

/* ── 响应档位滑条 ── */
.tier-slider-wrap { margin: 12px 0 4px; }
.tier-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  border-radius: var(--r-pill);
  /* 已填充部分用强调色，未填充用中性色；--pos 由 JS 实时写入（用 calc 对齐滑块球心，
     见 settings-bind.js 的 sliderFillPos —— 纯百分比在低值时会露出"球和条没连上"的缝）。
     ⚠️ 这里**不能**给 background 加 transition：两个线性渐变之间无法插值，浏览器会在
     过渡结束时整段离散切换 —— 拖动时填充色一格一格追着滑块跳，看起来"一卡一卡"
     （2026-10-02 用户反馈）。实时跟随才顺滑。 */
  background: linear-gradient(90deg,
    var(--accent) 0 var(--pos, 100%),
    var(--border) var(--pos, 100%) 100%);
  outline: none;
  cursor: pointer;
}
/* 滑条实时读数（1~60 这类整数滑条标签里的数字）：等宽数字 + 固定宽度，
   拖动时文字宽度不跳，避免小抖动 */
.slider-now {
  display: inline-block;
  min-width: 2ch;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
/* 滑块 */
.tier-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-2);
  box-shadow: var(--shadow-sm);
  cursor: grab;
  transition: transform var(--dur-fast) var(--ease-out-quart), box-shadow var(--dur-base) var(--ease-out-quart);
}
.tier-slider::-webkit-slider-thumb:hover { transform: scale(1.15); }
.tier-slider::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.05); }
.tier-slider:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px rgba(91,140,255,.25); }
.tier-slider::-moz-range-thumb {
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-2);
  box-shadow: var(--shadow-sm);
  cursor: grab;
}

/* 四段刻度：宽度按各段占比（inline flex 由 JS 给出），视觉上对齐滑条 */
.tier-scale { display: flex; gap: 2px; margin-top: 8px; }
.tier-seg {
  font-size: var(--fs-2xs);
  color: var(--faint);
  text-align: center;
  padding: 3px 2px;
  border-top: 2px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--dur-base) var(--ease-in-out), border-color var(--dur-base) var(--ease-in-out);
  min-width: 0;
}
/* 未选中的段：弱化（细线用中性色） */
.tier-seg.seg4 { border-top-color: var(--border); }

/* ── 选中态：滑到哪一档，那一档的标签 + 上边线一起变色 ──
   ⚠️ 之前颜色是按 .s1/.s4 写死的静态样式（s1 永远亮、s4 永远橙），
      跟滑条完全无关，所以拖动时刻度毫无反应。
      现在统一由 JS 加/去 .on 类驱动，各档用不同语义色：
      越往右越"话痨"，颜色也从冷到暖，扫一眼就知道当前激进度。 */
.tier-seg.on {
  color: var(--text);
  font-weight: 600;
}
.tier-seg.seg1.on { border-top-color: var(--accent); }
.tier-seg.seg2.on { border-top-color: var(--green); }
.tier-seg.seg3.on { border-top-color: var(--accent); }
.tier-seg.seg4.on { border-top-color: var(--orange); }

/* 选中段的标签给一点底色，让"当前档"更明确 */
.tier-seg.on {
  background: linear-gradient(180deg, transparent, var(--hover));
  border-radius: 0 0 var(--r-sm) var(--r-sm);
}

/* ── 用量卡片：可点击 / 小标签 ── */
.usage-card.clickable { cursor: pointer; }
.usage-card.clickable:hover { border-color: var(--accent); }
.uc-label { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* "明细 ›" 提示：默认很淡，悬浮才显现，避免干扰读数 */
.uc-more {
  font-size: var(--fs-2xs);
  color: var(--accent);
  opacity: .55;
  transition: opacity var(--dur-base) var(--ease-in-out);
}
.usage-card.clickable:hover .uc-more { opacity: 1; }
/* "不计入成本" 小标签：明确告诉用户这数字不进钱 */
.uc-tag {
  font-size: var(--fs-2xs);
  padding: 1px 5px;
  border-radius: var(--r-sm);
  color: var(--muted);
  background: color-mix(in srgb, var(--text) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 16%, var(--border));
  font-weight: 400;
}

/* ── 价格徽标：未定价 / 别名 / 近似 ──
   挂在成本单元格或模型名后面。未定价用橙色：这些调用没算钱，
   不能和"免费"混为一谈；别名/近似是中性提示。 */
.uc-chip {
  display: inline-block;
  margin-left: 6px;
  font-size: var(--fs-2xs);
  padding: 1px 5px;
  border-radius: var(--r-sm);
  color: var(--muted);
  background: color-mix(in srgb, var(--text) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 16%, var(--border));
  font-weight: 400;
  white-space: nowrap;
}
.uc-chip.warn {
  color: var(--orange, #f7b955);
  border-color: rgba(247, 185, 85, .45);
  background: rgba(247, 185, 85, .10);
}
/* 实付：这个价是用户自己填的（渠道价/自定义价），不是官方价估算 */
.uc-chip.ok {
  color: var(--green, #2fd07a);
  border-color: rgba(47, 208, 122, .45);
  background: rgba(47, 208, 122, .10);
}

/* ── 用量页顶部的"未定价"提示条 ── */
.usage-unpriced {
  margin: 0 0 14px;
  padding: 11px 14px;
  border: 1px solid rgba(247, 185, 85, .40);
  background: rgba(247, 185, 85, .07);
  border-radius: var(--r-input);
  line-height: 1.6;
}
.uu-head { display: flex; align-items: center; gap: 8px; font-size: var(--fs-md); font-weight: 600; }
.uu-icon {
  flex: none;
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: #201705;
  background: var(--orange, #f7b955);
}
.uu-list { margin-top: 7px; display: flex; flex-wrap: wrap; gap: 6px; }
.uu-chip {
  font-size: var(--fs-xs);
  padding: 2px 7px;
  border-radius: var(--r-sm);
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--text);
}
/* 未定价清单里的每一项都是"给这个模型定价"的按钮 */
button.uu-chip { cursor: pointer; font-family: inherit; }
button.uu-chip:hover { border-color: var(--accent); color: var(--text); }
.uu-chip b { margin-left: 5px; color: var(--orange, #f7b955); font-weight: 600; }
.uu-chip i { margin-left: 6px; font-style: normal; color: var(--accent); }
.uu-chip.muted { color: var(--muted); }
.uu-hint { margin-top: 7px; font-size: var(--fs-xs); color: var(--muted); }
.uu-hint b { color: var(--text); }

/* ── 渠道价目表 / 探测结果 ── */
.hint.success { color: var(--green, #2fd07a); }
.hint.error { color: var(--red, #ff6b6b); }

/* ── 成本口径三选一（设置页）── */
.radio-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-2);
  margin-bottom: 6px;
  font-size: var(--fs-md);
  line-height: 1.5;
}
.radio-row:hover { border-color: var(--accent); }
.radio-row input[type="radio"] { flex: none; }
.radio-row > span { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* 高级折叠区：和 .collapsible 一致，但内部是正常排版的表单（不是代码块） */
.settings-advanced > summary { cursor: pointer; font-size: var(--fs-sm); color: var(--muted); padding: 8px 12px; }
.settings-advanced[open] > summary { border-bottom: 1px solid var(--border); margin-bottom: 10px; }
.settings-advanced > *:not(summary) { padding-left: 12px; padding-right: 12px; }
#cost-mode-status { font-size: var(--fs-xs); color: var(--muted); }

/* ── 用量页首次引导卡（成本口径三选一） ── */
.usage-guide {
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--accent-border);
  background: var(--accent-soft);
  /* 面板角色 → --r-card；原先用 --r-input(10)，与同页的 .usage-card(12) 相差 2px。 */
  border-radius: var(--r-card);
}
.ug-title { font-size: var(--fs-md); font-weight: 600; margin-bottom: 8px; }
.ug-options { display: flex; flex-direction: column; gap: 6px; }
.ug-options .radio-row { margin-bottom: 0; background: var(--bg); }
.cf-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-2);
  margin-bottom: 6px;
}
.cf-main { display: flex; flex-direction: column; min-width: 180px; flex: 1; font-size: var(--fs-sm); }
.cf-main span { font-size: var(--fs-xs); word-break: break-all; }
.cf-state { font-size: var(--fs-xs); color: var(--muted); flex: 2; min-width: 160px; }
.cf-state.ok { color: var(--green, #2fd07a); }
.cf-state.bad { color: var(--orange, #f7b955); }
.cf-actions { display: flex; gap: 6px; }
#probe-result .usage-table { width: 100%; }

/* ── 「给模型定价」弹窗 ── */#price-dialog {
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 20px 22px;
  background: var(--bg-2);
  box-shadow: var(--shadow-lg);
  width: min(460px, 92vw);
}
#price-dialog::backdrop { background: var(--overlay); backdrop-filter: blur(3px); }
#price-dialog h2 { font-size: var(--fs-xl); margin-bottom: 4px; }
.pd-rows { margin: 12px 0 6px; display: flex; flex-direction: column; gap: 8px; }
.pd-row { display: flex; align-items: center; gap: 10px; font-size: var(--fs-md); }
.pd-row > span:first-child { width: 64px; flex: none; color: var(--muted); }
.pd-row input, .pd-row select {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  padding: 6px 9px;
  font-family: inherit;
  font-size: var(--fs-md);
}
.pd-unit { font-size: var(--fs-sm); color: var(--muted); flex: none; }
#price-dialog-hint { font-size: var(--fs-xs); line-height: 1.6; margin-top: 6px; }
#price-dialog-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }

/* ── 调用明细弹窗 ── */
.tool-breakdown { min-width: 380px; }
.tb-lead {
  font-size: var(--fs-sm);
  color: var(--text);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  margin-bottom: 14px;
  line-height: 1.6;
}
.tb-cat { margin-bottom: 14px; }
.tb-cat-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--fs-xs);
  color: var(--muted);
  font-weight: 600;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}
.tb-cat-sum { font-weight: 400; color: var(--faint); font-size: var(--fs-2xs); }
.tb-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 3px 0;
  font-size: var(--fs-sm);
}
.tb-icon { width: 16px; text-align: center; flex: none; }
.tb-name { width: 74px; flex: none; color: var(--text); }
.tb-code {
  width: 118px;
  flex: none;
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 条形：宽度按最大次数归一，视觉上能立刻看出谁是主力 */
.tb-bar {
  flex: 1;
  height: 5px;
  background: var(--hover);
  border-radius: var(--r-pill);
  overflow: hidden;
  min-width: 40px;
}
.tb-bar i {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: var(--r-pill);
  /* 入场时从 0 展开，比直接画出来更有"生长感" */
  animation: barGrow var(--dur-slower) var(--ease-out-quart);
}
@keyframes barGrow { from { width: 0 !important; } }
.tb-n {
  width: 34px;
  flex: none;
  text-align: right;
  font-family: var(--mono);
  color: var(--text);
}


#console-login {
  width: min(360px, calc(100vw - 32px));
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg, #fff);
  color: var(--text, #202020);
  padding: 24px;
}
#console-login::backdrop { background: #0008; }
#console-login-form { display: grid; gap: 12px; }
#console-login-form h2 { font-size: var(--fs-xl); margin: 0 0 8px; }
#console-token { width: 100%; min-width: 0; box-sizing: border-box; }
#console-login-error { color: #d64848; margin: 0; }
#runtime-mode { width: 84px; padding: 5px; }
@media (max-width: 760px) {
  #topbar, .top-status { flex-wrap: wrap; height: auto; gap: 8px; }
  #model-label { max-width: 220px; overflow-wrap: anywhere; }
  #tabs { flex-wrap: nowrap; height: auto; }
  .session-mode-band {
    grid-template-columns: minmax(100px, 1fr) minmax(120px, 1fr);
  }
  .session-mode-detail,
  .session-mode-ref { grid-column: 1 / -1; }
  .thread-timeline-head { display: block; }
  .thread-timeline-head > span { margin-top: 5px; white-space: normal; }
  .context-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .context-tabs { gap: 12px; overflow-x: auto; }
  .context-tabs button { flex: 0 0 auto; }
  .context-layout { max-width: 56%; }
  .conversation-mode-switch { grid-template-columns: 1fr; }
  .conversation-mode-stage { min-height: 0; }
  .conversation-mode-facts { grid-template-columns: 1fr; gap: 10px; }
  .conversation-mode-flow { flex-wrap: wrap; }
  .conversation-mode-flow i { flex-basis: 14px; }
  .conversation-scope-heading { display: block; }
  .conversation-scope-heading span { display: block; margin-top: 4px; text-align: left; }
  .conversation-scope-actions { align-items: flex-start; flex-direction: column; }
}


/* ══════════════════════════════════════════════════════════════
   视觉刷新层
   ══════════════════════════════════════════════════════════════
   只改观感，不动布局与交互：配色对齐项目标记的深海蓝，统一圆角 / 描边 / 阴影 / 间距，
   给悬停、选中、聚焦补上明确反馈。放在文件末尾，同选择器覆盖上面的旧规则。
   新增组件请优先用这里的习惯（圆角 10/12/14，边框 var(--border)，阴影 var(--shadow-*)）。
*/

/* ── 页面底色与排版 ── */
body {
  background-image:
    radial-gradient(1100px 520px at 12% -10%, var(--accent-soft), transparent 60%),
    radial-gradient(900px 420px at 88% -18%, rgba(47, 208, 122, .07), transparent 62%);
  background-attachment: fixed;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
    'Microsoft YaHei', 'Noto Sans SC', Roboto, Helvetica, Arial, sans-serif;
  font-size: var(--fs-md);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
[data-theme='light'] body { background-image: none; }
::selection { background: var(--accent-border); }

/* ── 按钮 ── */
.btn {
  border-radius: var(--r-input); padding: 6px 14px; border: 1px solid var(--border);
  background: var(--bg-3); color: var(--text); font-weight: 550;
  transition: background var(--dur-interaction) var(--ease-in-out), border-color var(--dur-interaction) var(--ease-in-out), transform var(--dur-fast) var(--ease-out-quart), box-shadow var(--dur-interaction) var(--ease-in-out);
}
.btn:hover { border-color: var(--accent-border); color: var(--text); background: var(--hover); }
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: linear-gradient(180deg, #5590ff, #3f7ef0); border-color: rgba(255, 255, 255, .14);
  color: #fff; box-shadow: 0 2px 10px rgba(63, 126, 240, .28);
}
.btn-primary:hover { filter: brightness(1.06); }
.btn-danger { background: rgba(255, 107, 107, .12); border-color: rgba(255, 107, 107, .45); color: var(--red); }
.btn-small { padding: 4px 10px; border-radius: var(--r-input); font-size: var(--fs-sm); }
.btn:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }
.icon-btn {
  border-radius: var(--r-input); border: 1px solid transparent; background: transparent; color: var(--muted);
  transition: background var(--dur-interaction) var(--ease-in-out), color var(--dur-interaction) var(--ease-in-out);
}
.icon-btn:hover { background: var(--hover); color: var(--text); }

/* ── 表单控件 ── 已并入文件末尾「表单控件 v2」（全局只留一处定义） */
#runtime-mode { font-weight: 550; }

/* ── 面板、列表与卡片 ── */
.list-pane, .detail-pane, .settings-sidebar, .settings-pane, .tool-card, .asset-pane,
.control-pane, .usage-card, .price-card, .context-inspector, .conversation-mode-shell {
  border-radius: var(--r-xl);
}
/* 这一条原来把 `--hover` 同时写给了元素本身和 :hover 两份选择器（`.chat-item, …, .chat-item:hover, …`）——
   结果每一行列表、每一个设置菜单项都常驻一层灰底，浅色液态玻璃下压在彩色壁纸上就是一条条灰砖
   （2026-10-19 用户反馈"会话界面颜色生硬"）。底色只该在悬停时出现：静态行交给 .list-pane 的玻璃底
   和 1px 分隔线，选中态另有一套 --accent-soft。 */
.chat-item:hover, .session-item:hover, .pick-item:hover, .settings-menu-item:hover {
  background: var(--hover); box-shadow: var(--shadow-sm);
}
/* 工具卡有自己的 --bg-2 表面：悬停用实一档的 --bg-3，不要拿半透明的 --hover 盖掉它 */
.tool-card:hover { background: var(--bg-3); }
.chat-item.selected, .session-item.selected, .settings-menu-item.active {
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
}
.chat-item-title, .session-title { font-weight: 600; letter-spacing: .1px; }
.chat-item-sub, .session-meta, .session-time { color: var(--muted); }
.list-head, .detail-header, .control-section-title { letter-spacing: .2px; }

/* ── 状态徽标：从描边改成"淡底 + 同色文字"的胶囊 ──
   注意：这里只认 status-* 这类"徽章专用"类名。mode-lifecycle / mode-threaded /
   mode-legacy 是**上下文类**（模块面板、会话卡片、模式选项都带），
   早期把它们写进徽章组，导致徽章的 999px 圆角 + 色底 + 2px 内边距
   糊到整个面板上（"对话模式那块看着别扭"的根源，2026-09-26 修）。 */
.status-badge, .vbadge, .mode-chip, .persona-chip, .member-chip, .unread-pill, .sent-badge, .thread-pill, .mode-state-token {
  border-radius: var(--r-pill); padding: 2px 9px; font-size: var(--fs-sm); font-weight: 500;
  border: 1px solid var(--border); background: var(--bg-3); color: var(--muted);
}
.status-waiting {
  background: rgba(47, 208, 122, .09); border-color: rgba(47, 208, 122, .28); color: color-mix(in srgb, var(--green) 80%, var(--text));
}
/* 「已发言」＝这个机器人在本轮里说了话 —— `--voice`（冷青）的落点之一（2026-10-09 值班台）。
   用 color-mix 从 --voice 现算，明暗两套色板自动跟（亮色下 --voice 是压暗的冷青）。
   刻意的区分：青色**不表示“状态好”**，只表示“这是它说的” —— 等待/运行/出错/中止仍是各自的语义色。
   它原来跟 waiting 挤在一条绿色规则里；文件前段另有一份旧副本（16%/88%）被本处完整覆盖成
   死声明，2026-10-09 复核已删 —— token 改在死副本上会"改了没反应"。 */
.status-done {
  background: color-mix(in srgb, var(--voice) 10%, transparent);
  border-color: color-mix(in srgb, var(--voice) 32%, transparent);
  color: color-mix(in srgb, var(--voice) 86%, var(--text));
}
.status-running {
  background: var(--accent-soft); border-color: var(--accent-border); color: color-mix(in srgb, var(--accent) 82%, var(--text));
}
.status-error, .status-aborted {
  background: rgba(255, 107, 107, .09); border-color: rgba(255, 107, 107, .30); color: color-mix(in srgb, var(--red) 82%, var(--text));
}
.status-noreply {
  background: rgba(247, 185, 85, .09); border-color: rgba(247, 185, 85, .28); color: color-mix(in srgb, var(--orange) 82%, var(--text));
}

/* ── 气泡 ── */
.bubble { border-radius: var(--r-xl); box-shadow: var(--shadow-sm); }
.bubble-assistant { background: var(--bg-3); }
.bubble-user { background: var(--accent-soft); border: 1px solid var(--accent-soft); }

/* ── 表格 ── */
.usage-table, .asset-table, .incident-table, .member-table, .model-rows-table,
.context-call-table, .archive-table, .identity-pilot-table { border-collapse: separate; border-spacing: 0; }
.usage-table th, .asset-table th, .incident-table th, .member-table th, .model-rows-table th,
.context-call-table th, .archive-table th, .identity-pilot-table th {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-2); color: var(--muted); font-weight: 600; font-size: var(--fs-sm);
  letter-spacing: .3px; border-bottom: 1px solid var(--border);
}
.usage-table td, .asset-table td, .incident-table td, .member-table td, .model-rows-table td,
.context-call-table td, .archive-table td, .identity-pilot-table td { border-bottom: 1px solid var(--border); }
.usage-table tbody tr:hover, .asset-table tbody tr:hover, .incident-table tbody tr:hover,
.member-table tbody tr:hover, .model-rows-table tbody tr:hover, .context-call-table tbody tr:hover,
.archive-table tbody tr:hover, .identity-pilot-table tbody tr:hover { background: var(--hover); }
/* 圆角靠滚动容器裁切即可；不要在第二轮把 overflow 设成 hidden —— 会把 2183 那层的
   max-height 变成"截断"：人物印象 14 行时 scrollHeight 1076 / clientHeight 569，
   后面几行既看不到也滚不到（2026-10-19 审查实测）。auto 同时保住横向滚动。 */
.asset-table-wrap, .context-call-table-wrap { border-radius: var(--r-card); border: 1px solid var(--border); overflow: auto; }

/* ── 对话框 / 登录卡 / 加载页 ── */
/* 弹窗类才需要压暗背景；启动提示是浮层，不压暗（否则进控制台会整屏闪一下） */
.modal-overlay, .model-modal-overlay { backdrop-filter: blur(3px); background: var(--overlay); }
.modal, .model-modal, .loading-card { border-radius: var(--r-xl); box-shadow: var(--shadow-lg); border: 1px solid var(--border); }
.modal-head, .model-modal-head { border-bottom: 1px solid var(--border); }
.modal-foot, .model-modal-foot { border-top: 1px solid var(--border); }

/* ── 空状态与提示条 ── */
.empty-hint, .context-empty {
  border: 1px dashed var(--border); border-radius: var(--r-xl); color: var(--muted);
  padding: 22px 18px; background: color-mix(in srgb, var(--bg-2) 60%, transparent);
}
.banner { border-radius: var(--r-card); border: 1px solid var(--accent-border); background: var(--accent-soft); }


/* ── 骨架屏 ── */
.skeleton, .sk-line, .sk-block { border-radius: var(--r-sm); }

/* ══════════════════════════════════════════════════════════════
   表单系统
   ══════════════════════════════════════════════════════════════
   背景：原来字段容器是 flex + flex:1，输入框会被拉到整行宽（一个 QQ 号占 900px），
   标签只是 12px 灰字，复选框行的文字会被挤成三行。这里统一成：
     · 字段自适应网格，列宽 200~300px，输入框不再无限拉伸；
     · 标签统一 12px / 600 字重 / 与控件 6px 间距；
     · 控件统一 36px 高、10px 圆角、悬停与聚焦都有反馈；
     · 复选框 / 单选框行横向排列、不换行、与控件同高；
     · 提交类按钮单独占一行并右对齐（在网格里跨列）。
   仍然只动样式：所有控件 ID、class、JS 逻辑不变。
*/

/* ── 字段容器：flex 拉伸 → 自适应网格 ── */
.field-row {
  display: grid;
  /* 列数写死三轨（窄屏再降），不用 auto-fit：
     auto-fit 会把"没被占满的轨道"收掉，于是同一个页面里"两个字段的网格"会各自伸成两列、
     "十个字段的网格"会排成四列 —— 实测「控制」页第一行两个框 560px、下面几行 295px，
     同一屏里两种列宽。固定轨道数之后，任何一行的字段都落在同样的三条轨道上。 */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 1180px;
  gap: 14px 20px;
  /* 必须是 start（2026-10-01 修）：原先写 end，同一格里两个字段只要内容高度不同 ——
     一个带单行提示、一个带两行提示 —— 矮的那格整块被压到底部，标签与输入框就跟隔壁错开
     十几像素（实测 imageGen 那行差 18px）。表单要的是"标签与控件对齐"，不是底边对齐。 */
  align-items: start;
  margin-bottom: 14px;
}
/* 行里只有一个字段时**不要**占满整行：占满就成了一个 1600px 宽的空框（实测出现过 1622px 的数字框）。
   它占一轨（桌面 ≈380px），与上面几行的第一列对齐，看起来是"表格里的一格"而不是"一条横幅"。 */
/* 数字/短文本字段不限宽：此前给它们 max-width:220px，在均分列宽的网格里
   会缩在格子左侧、右边空出一截，"框都偏左"的观感主要来自这里。
   （注释写着不再限宽，规则却把 220px 留下了——2026-09-26 按注释本意删掉。） */
.update-deploy-settings > label:has(input[type=number]) { max-width: none; }
.field { min-width: 0; }
.field label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: .2px;
  color: var(--muted);
}
.field input[type=text], .field input[type=password], .field input[type=number],
.field input[type=search], .field select, .field textarea { width: 100%; }
.field input:hover:not(:disabled), .field select:hover:not(:disabled), .field textarea:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.field input:disabled, .field select:disabled, .field textarea:disabled { opacity: .6; cursor: not-allowed; }
.field input[type=number] { font-variant-numeric: tabular-nums; }
/* 全是参数行的网格收紧行距（40px 控件配 10px 行距 ≈ 50px 一拍，接近"规格表"的节奏）；
   含说明文字的网格保持 Carbon 建议的间距，别把助手文字挤到控件上。 */
.field-row:not(:has(.hint)) { gap: 10px 20px; margin-bottom: 12px; }

/* ══════════════════════════════════════════════════════════════════════════════
   参数行（2026-10-08 第四轮）：密集参数区不再"整页堆满大空框"
   ──────────────────────────────────────────────────────────────────────────────
   病症（实测）：好友管理 23 个字段里 22 个是数字，每个占 **368px** 宽，而框里只有 1–3 位数字
   （约 90% 是空的）；聊天设置 31 个字段里有 23 个数字，整段 **3027px** 高。观感 = 一页大空框。

   三份设计系统的口径（查证过，不是我自己拍的）：
     · GOV.UK：「Use fixed width inputs for content that has a specific, known length」
       —— 它按"字符数"给固定宽度类（width-2 / 3 / 4 / 5 / 10 / 20），后四位邮编用 width-5。
     · Ant Design：horizontal（标签左、控件右、标签列定宽）**是表单的默认布局**。
     · Carbon：field widths should reflect the intended length of the content；
       并建议分组与渐进披露，别一次把输入框都堆出来。
   → 所以：**标签左、控件右、标签列定宽**，控件按内容定宽；长文本/说明类保持"标签在上"。

   为什么用 `:not(:has(.hint))` 分流：带说明文字的字段需要纵向空间放助手文字
   （Carbon：助手文字在控件下方、始终可见），它们的观感问题本来就不是"空框"。
   ══════════════════════════════════════════════════════════════════════════════ */
/* 表单统一「标签在上、控件在下」（2026-10-19 UI 审查）。
   原先密集参数行用「标签左、控件右」：同一页里文本框因为 100% 宽会换行成"标签在上"，
   下拉却留在"标签在左"，同一行出现两种对齐（人设页的错位就是这么来的）。
   现在所有字段统一标签在上；数字/下拉按内容定宽，不再拉满整行。 */
.field > input[type='number'] { max-width: calc(8ch + 30px); }
.field > select { max-width: 380px; }
@media (max-width: 760px) {
  .field > input[type='number'], .field > select { max-width: none; }
}

/* ── 旧式内联字段（<label><span>标题</span><input></label>，如"更新部署"）── */
.update-deploy-settings {
  display: grid;
  /* 与 .field-row 同一套节奏，只是这一页字段更多更短，用四轨（同样写死，不用 auto-fit） */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  max-width: 1180px;
  gap: 14px 20px;
  align-items: start;
}
/* 窄屏降列：写死轨道数就必须自己管这件事，否则 1280 的笔记本上四轨只剩 200px 一列 */
@media (max-width: 1240px) {
  .field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .update-deploy-settings { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
  .field-row, .update-deploy-settings { grid-template-columns: minmax(0, 1fr); }
}
.update-deploy-settings > label { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.update-deploy-settings > label > span { font-size: var(--fs-sm); font-weight: 600; letter-spacing: .2px; color: var(--muted); }
.update-deploy-settings input[type=text],
.update-deploy-settings input[type=number],
.update-deploy-settings input[type=search] { width: 100%; }
.update-deploy-settings > label:has(> input[type=checkbox]) {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 0 2px;
  font-size: var(--fs-md);
  color: var(--text);
  white-space: nowrap;
}
.update-deploy-settings > .btn { grid-column: 1 / -1; justify-self: end; }

/* ── 勾选类控件 ── */
input[type=checkbox], input[type=radio] {
  width: 16px; height: 16px; flex-shrink: 0;
  accent-color: var(--accent);
  margin: 0;
}
/* 勾选行：内容与"第一行文字"对齐，而不是与整块文字居中。
   多行说明（如"每日花费上限"那条）写成一行时居中看不出差别，一旦折行，居中的方框会飘到两行
   中间 —— 2026-10-01 用户报的"勾选框和字体没对齐"就是这个。 */
.checkbox-row { align-items: flex-start; gap: 8px; padding: 6px 0; font-size: var(--fs-md); }
.checkbox-row > input[type=checkbox], .checkbox-row > input[type=radio] { margin-top: 2px; }
.checkbox-row label { display: inline-flex; align-items: flex-start; gap: 8px; white-space: nowrap; }
/* .field label（0,1,1）会把"勾选行"压成字段标题样式（display:block / 12px / 600 / muted）：
   `<div class="field"><label class="checkbox-row">…` 这种写法下，方框变行内基线对齐、文字变成
   字段标题的样子 —— 与群多选列表当初踩的是同一个坑，这里用更高特异度拨回来（0,2,1）。 */
.field label.checkbox-row,
.field .checkbox-row > label,
.field label.radio-row,
.field .radio-row > label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: var(--fs-md);
  font-weight: 400;
  color: var(--text);
  letter-spacing: normal;
  margin-bottom: 0;
  white-space: normal;
}
.field label.checkbox-row > input[type=checkbox],
.field .checkbox-row > input[type=checkbox],
.field label.radio-row > input[type=radio],
.field .radio-row > input[type=radio] { margin-top: 2px; }
.experimental-settings .checkbox-row { flex-wrap: nowrap; }

/* ── 群多选列表（群游戏「允许开局的群」/ 群日报「发到哪些群」）──
   行标签以前借用 .checkbox-row，但它在 .field 里会被 `.field label`（0,1,1）压成
   字段标题样式（display:block / 12px / muted），复选框与群名之间没有间距、基线还错位，
   看着就是"勾选框和群名重叠"。改用专用类（.group-checklist .group-checklist-row，
   0,2,0）显式做成 flex 行，并补上群多起来时的筛选 / 全选 / 滚动。 */
.group-checklist-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.group-checklist-bar input[type=search] {
  flex: 1 1 150px; width: auto; min-width: 110px; height: 30px; padding: 0 10px;
  border-radius: var(--r-sm); background: var(--bg-3); font-size: var(--fs-sm);
}
.group-checklist-bar .btn { height: 30px; }
.group-checklist-count { margin-left: auto; font-size: var(--fs-sm); white-space: nowrap; }
.group-checklist-list {
  max-height: 168px; overflow-y: auto; padding: 4px;
  border: 1px solid var(--border); border-radius: var(--r-input); background: var(--bg-2);
}
.group-checklist .group-checklist-row {
  display: flex; align-items: center; gap: 8px;
  margin: 0; padding: 4px 8px; border-radius: var(--r-sm);
  font-size: var(--fs-md); font-weight: 400; letter-spacing: normal; color: var(--text);
  cursor: pointer;
}
.group-checklist .group-checklist-row:hover { background: var(--bg-3); }
.group-checklist .group-checklist-row.is-hidden { display: none; }
.group-checklist .group-checklist-row:has(input:checked) { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.group-checklist-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-checklist-id { flex: 0 0 auto; font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.group-checklist-stale { flex: 0 0 auto; font-size: var(--fs-xs); color: var(--orange); }
.group-checklist-empty { margin-top: 4px; font-size: var(--fs-sm); }

/* 定时提醒页的列表行（待触发 / 最近完成） */
.reminder-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 6px 8px; border-radius: var(--r-sm); font-size: var(--fs-md);
}
.reminder-row:nth-child(odd) { background: color-mix(in srgb, var(--bg-3) 55%, transparent); }
.reminder-when { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--text); }
.reminder-chat { flex: 0 0 auto; max-width: 30%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: var(--fs-sm); }
.reminder-attr { flex: 0 0 auto; max-width: 26%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: var(--fs-sm); }
.reminder-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; color: var(--text); }
.reminder-row .btn { flex: 0 0 auto; }

/* ── 群游戏「正在进行的局」行 ── */
.gg-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 6px 8px; border-radius: var(--r-sm); font-size: var(--fs-md);
}
.gg-row:nth-child(odd) { background: color-mix(in srgb, var(--bg-3) 55%, transparent); }
.gg-chat { flex: 0 0 auto; max-width: 26%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: var(--fs-sm); }
.gg-name { flex: 0 0 auto; white-space: nowrap; font-weight: 600; color: var(--text); }
.gg-age { flex: 0 0 auto; white-space: nowrap; color: var(--muted); font-size: var(--fs-sm); }
.gg-summary { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; color: var(--text); }
.gg-row .btn { flex: 0 0 auto; }

/* ── 概览数字（当前版本 / 目标版本 / 上次检查 …）── */
.update-deploy-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin: 12px 0 16px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}
.update-deploy-summary > div {
  display: flex; flex-direction: column; gap: 4px;
  padding: 10px 12px; background: var(--bg-2);
}
.update-deploy-summary span { font-size: var(--fs-xs); color: var(--muted); }
.update-deploy-summary strong { font-family: var(--mono); font-size: var(--fs-md); font-weight: 600; }

/* ── 区块标题与说明 ── */
.control-section-title h3 { font-size: var(--fs-lg); font-weight: 650; letter-spacing: .2px; }
.control-section-title .muted { font-size: var(--fs-sm); }
.settings-pane section > h3 { font-size: var(--fs-lg); font-weight: 650; letter-spacing: .2px; margin: 0 0 6px; }
.hint { font-size: var(--fs-sm); line-height: 1.55; }
.control-result { border-radius: var(--r-input); padding: 8px 12px; font-size: var(--fs-sm); }

/* ── 操作条：按钮成组出现时统一间距与对齐 ── */
.settings-actions { gap: 10px; margin: 16px 0; }
.control-password-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 300px)); gap: 12px 16px; align-items: end; }
.control-key-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 300px)); gap: 12px 16px; align-items: end; }

/* ── 登录对话框 ── */
#console-login { border: 1px solid var(--border); border-radius: var(--r-xl); padding: 22px 24px; background: var(--bg-2); box-shadow: var(--shadow-lg); }
#console-login::backdrop { background: var(--overlay); backdrop-filter: blur(3px); }
#console-login-form { display: flex; flex-direction: column; gap: 10px; }
#console-login-form h2 { font-size: var(--fs-lg); font-weight: 650; margin: 0 0 4px; }
#console-login-form label { font-size: var(--fs-sm); font-weight: 600; letter-spacing: .2px; color: var(--muted); }
#console-login-form .btn { height: 36px; justify-content: center; }

/* 密码框 + 「显示 / 隐藏」按钮（登录框、SnowLuma 改密钥那三格；设置页里同款的用内联 flex） */
.pw-row { display: flex; gap: 8px; align-items: center; min-width: 0; }
.pw-row > input { flex: 1; min-width: 0; }
/* 输入框 40px、旁边的「显示」只有 34px：同一行里两个控件的上下缘错开 3px。字段行里的
   按钮（「获取列表」「测试连通性」）都是 40px，这里跟上同一口径。 */
.pw-row > .btn { height: 40px; }

/* 其余长文本字段（URL 等）：不再铺满整行，但保留比普通字段更宽的宽度 */
#cfg-price-remote-url, #new-baseurl, #auto-update-branch-advanced { max-width: 520px; }
#cfg-model-pick { max-width: 560px; }

/* ── 主题切换的平滑过渡与状态条对齐（消除"闪"与横向位移）──
   主题在启动时可能被后端配置调整一次；直接换色会"啪"地一下，这里给常用表面加过渡。
   状态条右对齐：文字从"模型：-"变成完整信息时会变长，右对齐让增长向左展开，位置更稳。

   ⚠️ 2026-10-08 修正：这里原来写死 `0.2s ease`，把动效 token（180ms + 自定义曲线）
   整个架空了 —— 实测所有控件的计算值都是 `0.2s ease`，所以"看起来像浏览器默认过渡"。
   现在改走 token（--dur-interaction / --ease-in-out）。 */
body, #sidebar, #topbar, #tabs, .view, .list-pane, .detail-pane, .chat-item, .session-item,
.btn, .icon-btn, input, select, textarea, .control-section, .usage-card,
.tool-card, .price-card, .modal, .banner, .empty-hint, .tab, table, th, td {
  transition:
    background-color var(--dur-interaction) var(--ease-in-out),
    color var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out),
    box-shadow var(--dur-interaction) var(--ease-in-out);
}
#topbar { justify-content: flex-end; }
.top-status { justify-content: flex-end; }

/* ── 顶栏高度固定：状态条文字变长时不再换行，避免"整块内容被顶下去再回来" ──
   实测 1024px 窗口：数据到达后状态条从 27px 变 63px（换行），顶栏 48 → 83px，
   下面所有内容整体位移 35px。这里锁死顶栏高度 + 状态条不换行 + 超长省略。 */
#topbar { height: 54px; flex-wrap: nowrap; gap: 10px; overflow: hidden; }
.top-status { flex-wrap: nowrap; overflow: hidden; min-width: 0; }
.top-status > .muted {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 34vw; flex-shrink: 1; min-width: 0;
}
@media (max-width: 760px) {
  .top-status > .muted { max-width: 42vw; }
}

/* ── 发现新版本弹窗 ── */
/* 顶部全局 reset（* { margin:0 }）把 dialog 默认的 margin:auto 也清掉了，
   弹窗会被钉在左上角；这里恢复居中，并限制高度避免长文溢出屏幕。 */
dialog { margin: auto; max-height: 88vh; }
#update-notice {
  border: 1px solid var(--border); border-radius: var(--r-xl); padding: 20px 22px;
  background: var(--bg-2); box-shadow: var(--shadow-lg);
  width: min(560px, 92vw);
}
#update-notice::backdrop { background: var(--overlay); backdrop-filter: blur(3px); }
#update-notice-panel { display: flex; flex-direction: column; gap: 10px; }
#update-notice-title { font-size: var(--fs-lg); font-weight: 650; margin: 0; }
#update-notice-sub { font-size: var(--fs-sm); margin: 0; }
#update-notice-notes {
  margin: 0; max-height: 320px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  font-family: inherit; font-size: var(--fs-md); line-height: 1.55;
  background: var(--bg-1, transparent); border: 1px solid var(--border);
  border-radius: var(--r-input); padding: 10px 12px;
}
#update-notice-notes strong { color: var(--text); }
#update-notice-notes code {
  background: var(--bg-3); border-radius: var(--r-sm); padding: 1px 4px; font-size: var(--fs-sm);
}
#update-notice-result { margin: 0; font-size: var(--fs-sm); }
#update-notice-actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }

/* 思考模式分段选择（ChatGPT 式：一排档位块，选中项实心高亮）。
   注意：轨道与选项的最终样式在文件末尾「分段控件 v2」—— 这里只保留这条注释，
   原来的那一版（自带 1px border + 10px 圆角 + 换色 transition）已经被它整体取代，
   两份叠着只会让下一个改的人分不清哪条生效。 */

/* ══════════════════════════════════════════════════════════════
   「平台能力」页（2026-10-07 重塑）
   ══════════════════════════════════════════════════════════════
   结构：能力卡片（分组）→ 卡片内逐项（checkbox + 标签 + 工具 chip）→ 配额表 → 按群覆盖面板。
   要解决的问题（用户："这个平台能力控制台做的好丑"）：
     - 工具名原来塞在标签行括号里，一长串把标签挤没了 → 改成单独一行 mono chip，标签行只留一句话；
     - 配额/按群覆盖原来用内联样式排的行，列对不齐 → 改成表格 / 网格，标签列左对齐；
     - 整页没有分组边界，十五行开关连成一片 → 每组一张卡片（.plat-card）。
   用色与圆角沿用全局 token，不再新增颜色；"默认关"复用 .chip.warn。
*/
.plat-card {
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--bg-2);
  padding: 2px 14px 6px;
  margin: 0 0 14px;
}
.plat-card-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 10px;
  padding: 10px 0 2px;
}
.plat-card-title { font-size: var(--fs-md); font-weight: 600; margin: 0; color: var(--text); }
.plat-card-note { font-size: var(--fs-xs); color: var(--faint); }

.plat-row {
  display: grid;
  /* 第一列原来是写死的 18px（给老的原生复选框留的宽）。开关变成 44px 之后那一列会"溢出"，
     标签被挤到紧贴开关 —— 改成 auto，由控件自己决定列宽。 */
  grid-template-columns: auto minmax(0, 1fr);
  gap: 2px 12px;
  align-items: start;
  padding: 8px 0;
  border-top: 1px dashed var(--border);
}
/* 卡片里第一行紧跟在标题后面：去掉它顶上那条虚线（nth-of-type 数的是元素类型，会数到标题上） */
.plat-card-head + .plat-row { border-top: 0; }
.plat-row input[type="checkbox"] { margin: 2px 0 0; }
.plat-row-main { min-width: 0; }
.plat-label { font-size: var(--fs-md); color: var(--text); cursor: pointer; }
.plat-tools { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; min-height: 18px; }
.plat-tool {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  line-height: 1.75;
  padding: 0 7px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--muted);
  white-space: nowrap;
}
.plat-tools-empty { font-size: var(--fs-xs); color: var(--faint); }
.plat-sub { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 7px; }
.plat-sub > label { font-size: var(--fs-sm); color: var(--muted); }
.plat-select {
  padding: 4px 8px;
  font-size: var(--fs-sm);
  color: var(--text);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  outline: none;
}
.plat-select:focus { border-color: var(--accent); }

.plat-quota-table { width: 100%; border-collapse: collapse; margin: 4px 0 2px; }
.plat-quota-table th {
  text-align: left;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--faint);
  padding: 4px 8px;
  border-bottom: 1px solid var(--border);
}
.plat-quota-table td {
  padding: 6px 8px;
  border-bottom: 1px dashed var(--border);
  font-size: var(--fs-sm);
  vertical-align: middle;
}
.plat-quota-table tr:last-child td { border-bottom: 0; }
.plat-quota-table input[type="number"] { width: 84px; padding: 4px 8px; font-size: var(--fs-sm); }
.plat-quota-used { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }

.plat-panel { border: 1px solid var(--border); border-radius: var(--r-input); background: var(--bg); padding: 10px 12px 6px; }
.plat-panel-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.plat-panel-head > label { font-size: var(--fs-sm); color: var(--muted); }
.plat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 22px; margin-top: 6px; }
.plat-grid-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 5px 0;
  border-top: 1px dashed var(--border);
  min-width: 0;
}
.plat-grid-row:nth-child(1), .plat-grid-row:nth-child(2) { border-top: 0; }
.plat-grid-row > label {
  font-size: var(--fs-sm);
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 窄屏：网格塌成一列，第一行的虚线也要回来 */
@media (max-width: 760px) {
  .plat-grid { grid-template-columns: minmax(0, 1fr); }
  .plat-grid-row:nth-child(2) { border-top: 1px dashed var(--border); }
}

/* ══════════════════════════════════════════════════════════════
   「总览」页 + 通用卡片系统（2026-10-08）
   ══════════════════════════════════════════════════════════════
   设计口径（对齐参照物 SnowLuma 控制台，但不照搬）：
     · 一屏三层：KPI 卡（数字）→ 两栏面板（连接 / 最近异常）→ 面板（用量走势 / 主机资源）；
     · 卡片统一 12px 圆角 + 1px 边框 + 极轻阴影；悬浮只做 1px 位移，不做发光；
     · 数字用等宽字体（--mono）并放大，标签小字灰色 —— 一眼能读出"多少"；
     · 强调色只用在：选中态、关键 chip、图表柱；其余一律中性灰。
   这些类（.panel/.kpi/.stat/.meter/.feed/.bars）不只总览页用，后续页面统一往这套上靠。
*/
/* 与「控制 / 资产 / 用量 / 插件」同一套内边距：这几页是并列的一级页签，切页时标题与
   卡片如果各差 4px，看起来就是整列内容在左右跳。（2026-10-19 实测：总览 20/22，其余 22/26） */
.overview-pane { flex: 1; min-width: 0; overflow-y: auto; padding: 22px 26px 40px; }

.pane-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.pane-head h2 { margin: 0; font-size: var(--fs-xl); font-weight: 650; letter-spacing: .01em; }
.pane-sub { margin-top: 4px; font-size: var(--fs-sm); color: var(--muted); }
.pane-head .btn { display: inline-flex; align-items: center; gap: 6px; }

/* ── 值班台：一页只留两级表面 + 细线（2026-10-09）────────────────
   原来每个内容块都套一张卡（描边 + 圆角 + 底色）—— 那是 SaaS 卡片套件的默认款：
   一屏十张同款卡片，扫起来没有层级，只有重复。改成：
     · 表面只两级：底（--bg）与浮起（--bg-2）；KPI 那一行整体**算一块**浮起表面。
     · 块与块之间用 1px 细线分层，而不是各自描边 + 各自圆角。
   细线用“格子自己的左/上投影”画进 1px 的缝里，**不是**把容器底色当线：后者在 auto-fit
   换行时，最后一行没铺满的那半格会露出一块线色的空白（2026-10-09 窄窗口实测）。
   只画左/上、不画右/下：右/下边就是容器描边，再画一层会叠成双线。 */
.kpi-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px; margin-bottom: 16px;
  background: var(--bg-2);            /* 空出来的格子也是"表面"，不是线色 */
  border: 1px solid var(--border); border-radius: var(--r-card);
  overflow: hidden;                    /* 让圆角把四角裁干净 */
}
.kpi {
  background: var(--bg-2); padding: 13px 15px 14px; min-width: 0;
  box-shadow: -1px 0 0 var(--border), 0 -1px 0 var(--border);
  transition: background-color var(--dur-interaction) var(--ease-in-out);
}
.kpi-head { display: flex; align-items: center; gap: 7px; color: var(--muted); }
.kpi-ico { display: inline-flex; color: var(--accent); }
/* "机器人"那张卡的图标走冷青 —— 它是这张台面上"那个会说话的东西"的身份证，与 --voice 同口径 */
#kpi-bot .kpi-ico { color: var(--voice); }
.kpi-label { font-size: var(--fs-sm); }
.kpi-value { margin-top: 7px; font-family: var(--mono); font-size: var(--fs-2xl); font-weight: 650; line-height: 1.15; display: flex; align-items: center; gap: 6px; }
.kpi-unit { font-size: var(--fs-sm); font-weight: 500; color: var(--muted); }
.kpi-sub { margin-top: 6px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }

.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 14px; margin-bottom: 14px; }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }

/* 内容块＝细线分节（不再是"每块一张卡"）。横向内边距归零、只留纵向节奏；
   因此密度档位对 .panel 也只改纵向（见下面 [data-density] 那两条）。 */
.panel { border: 0; border-top: 1px solid var(--border); border-radius: 0; background: transparent; padding: 14px 18px 15px; margin-bottom: 0; }
.grid-2 > .panel { margin-bottom: 0; }
.panel-head { display: flex; align-items: flex-start; gap: 9px; margin-bottom: 11px; }
/* 带图标的标题：图标占 16px + 9px 间距，正文再缩进 25px 就和标题左缘对齐（此前正文是贴边的）。 */
.panel:has(.panel-ico) .panel-body { padding-left: 25px; }
.panel-ico { display: inline-flex; color: var(--muted); margin-top: 1px; }
.panel-titles { min-width: 0; flex: 1; }
.panel-titles h3 { margin: 0; font-size: var(--fs-md); font-weight: 600; }
.panel-sub { margin-top: 3px; font-size: var(--fs-xs); color: var(--muted); }
.panel-actions { flex-shrink: 0; font-size: var(--fs-sm); }
.panel-link { color: var(--accent); text-decoration: none; }
/* 行内文字按钮：用在提示句子里（"统一在…里配置 去设置"），不带边框与内边距 */
.link-btn { background: none; border: 0; padding: 0; font: inherit; color: var(--accent); cursor: pointer; }
.link-btn:hover { text-decoration: underline; }
.panel-link:hover { text-decoration: underline; }
.panel-body { min-width: 0; }

.kv { display: grid; grid-template-columns: 74px minmax(0, 1fr); gap: 7px 10px; margin: 0; font-size: var(--fs-sm); }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; overflow-wrap: anywhere; }
.kv a { color: var(--accent); text-decoration: none; }
.kv a:hover { text-decoration: underline; }

.feed { list-style: none; margin: 0; padding: 0; font-size: var(--fs-sm); }
.feed li { display: flex; align-items: baseline; gap: 8px; padding: 6px 0; border-top: 1px dashed var(--border); }
.feed li:first-child { border-top: 0; padding-top: 0; }
.feed-time { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); flex-shrink: 0; }
.feed-text { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.meter { height: 6px; border-radius: var(--r-pill); background: color-mix(in srgb, var(--text) 12%, var(--bg-3)); overflow: hidden; margin-top: 8px; }
.meter-fill { height: 100%; border-radius: var(--r-pill); background: var(--accent); transition: width var(--dur-slow) var(--ease-out-quart); }
.meter.warn .meter-fill { background: var(--orange); }
.meter.bad .meter-fill { background: var(--red); }

.stat { min-width: 0; }
.stat-label { font-size: var(--fs-xs); color: var(--muted); }
.stat-value { margin-top: 5px; font-family: var(--mono); font-size: var(--fs-xl); font-weight: 600; }
.stat-sub { margin-top: 6px; font-size: var(--fs-xs); color: var(--muted); }

.bars { display: flex; align-items: flex-end; gap: 10px; height: 96px; padding-top: 4px; }
.bars-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; min-width: 0; }
.bars-bar { width: 100%; max-width: 46px; border-radius: var(--r-sm) var(--r-sm) 2px 2px; background: color-mix(in srgb, var(--text) 12%, var(--bg-3)); border: 1px solid color-mix(in srgb, var(--text) 16%, var(--border)); transition: background var(--dur-interaction) var(--ease-in-out); }
.bars-bar.on { background: var(--accent-border); border-color: var(--accent-border); }
.bars-x { margin-top: 6px; font-family: var(--mono); font-size: var(--fs-2xs); color: var(--faint); white-space: nowrap; }

.chip.bad { color: var(--red); border-color: rgba(255, 107, 107, .45); background: rgba(255, 107, 107, .08); }

/* 设置侧栏分组标题（2026-10-08）：17 个分区平铺太劝退，按组归位。
   这一层保留分组标题：它是一页之内的二级导航，项多、且不带说明行，标题是它唯一的结构。 */
.settings-menu-group {
  margin: 12px 0 5px;
  padding: 0 10px;
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--faint);
  text-transform: none;
}
.settings-menu-group:first-child { margin-top: 2px; }


/* ══════════════════════════════════════════════════════════════
   外观刻度：密度 / 缩放 / 圆角（2026-10-08）
   ══════════════════════════════════════════════════════════════
   · 密度：紧凑 / 舒适 / 宽松 —— 只改容器内边距，不动字号（字号交给缩放）
   · 缩放：#shell 的 zoom（Chromium/Safari/新 Firefox 都支持；老浏览器忽略 =1，不影响可用）
   · 圆角：组件读 --r-sm/--r-md/--r-lg（都乘 --r-scale），改倍率整机跟着变
*/
#shell { zoom: var(--zoom, 1); }

[data-density='compact'] .kpi { padding: 10px 12px 11px; }
[data-density='compact'] .panel { padding: 10px 18px 11px; }
[data-density='compact'] .tab { padding: 6px 10px; }
/* 密度档的横向内边距也跟插件页对齐（15/17 与 26/30）：否则同一个密度档下切页仍会横跳。 */
[data-density='compact'] .overview-pane { padding: 15px 17px 22px; }
[data-density='roomy'] .kpi { padding: 16px 18px 17px; }
[data-density='roomy'] .panel { padding: 17px 18px 18px; }
[data-density='roomy'] .tab { padding: 10px 12px; }
[data-density='roomy'] .overview-pane { padding: 26px 30px 34px; }
[data-density='roomy'] .feed li { padding: 9px 0; }

/* （.tab 的权威定义在壳层区；.kpi/.panel 是"表面内分格/细线分节"，圆角与阴影都不归卡片组 ——
   这两行旧值先后被覆盖成死声明，2026-10-09 复核删除。） */

/* 顶栏状态区：四个 chip 挤成一排显得吵 —— 去掉边框、只留文字，悬浮时才浮出边界 */
.top-status .chip { border-color: transparent; background: transparent; color: var(--muted); padding: 3px 8px; }
.top-status .chip:hover { border-color: var(--border); background: var(--bg-2); color: var(--text); }

/* ══════════════════════════════════════════════════════════════
   外观设置控件（预设色板 / 强调色 / 圆角 / 缩放 / 密度）
   ══════════════════════════════════════════════════════════════
   遵循两条：① 悬浮位移只在"真有鼠标"的设备上生效（触屏 tap 会误触发 hover）；
   ② 选中态用强调色描边 + 柔和底（--accent-soft/--accent-border），换强调色时整块跟着变。
*/
/* v2 的「主题配色」预设行（.preset-row/.preset/.preset-dot）已由外观页的强调色色板
   （.swatch/.swatch-row）取代，对应样式删掉。 */
@media (hover: hover) and (pointer: fine) {
  .preset:hover { border-color: var(--accent-border); }
  /* 悬停只换一格底色（不再抬升）。也要 gate：触屏 tap 会先触发 hover，不 gate 的话格子会闪一下。 */
  .kpi:hover { background: var(--bg-3); }
}
/* KPI 不再有"悬浮抬升"：它现在是同一块表面上的一格，抬起来等于把格子从表面里拔出来
（而且触屏 tap 会先触发 hover，看着像闪一下）。悬停只换一格底色，见上面的 .kpi:hover。 */

/* 开材质时玻璃加在**整行这块表面**（.kpi-grid）上，格子自己透明 —— 否则每格一个 backdrop-filter，
   既回到"玻璃卡片套件"，又会踩嵌套 backdrop-filter（子层采样到空 backdrop root → 发白）。 */
html[data-glass] .kpi { background: transparent; }
/* 上面这条 (0,2,1) 会把 (0,2,0) 的 .kpi:hover 压掉 —— 玻璃态下悬停没有任何反馈（2026-10-09 复核）。
   补一条同特异性的 hover：用极淡的 --text 冲一层，材质档下也看得见。 */
@media (hover: hover) and (pointer: fine) {
  html[data-glass] .kpi:hover { background: color-mix(in srgb, var(--text) 6%, transparent); }
}
/* 滑条：轨道与滑块跟随强调色 */
input[type='range'] { accent-color: var(--accent); height: 20px; }

/* ══════════════════════════════════════════════════════════════
   设计标度 v2（2026-10-08）：照参照控制台实测的数字对齐
   ══════════════════════════════════════════════════════════════
   实测它的系统是 Tailwind + shadcn 的约定：`--spacing: .25rem`（**4px 步进**）、
   `--radius: .75rem`（**统一 12px**）、body 基准 **16px**、控件 14px、卡片
   `0 1px 2px rgba(0,0,0,.05)` 极轻阴影、oklch + color-mix 派生色。

   我们的问题是"没有标度"：间距手写 7/8/9/10/11/13/14px 混用、圆角同时存在
   6/9/10/14 四种 —— 单看每个元素都不丑，整屏就是"不齐"。这一节把标度立起来，
   并把**壳层**（导航/顶栏/按钮/输入/卡片/表格头）统一到标度上；
   页面内部的历史写法逐步替换，不在这里一次全改（避免一次性打散布局）。

   字号为什么是 15px 而不是 16px：我们是"信息密度优先"的控制台（一屏要塞会话列表 +
   详情 + 用量表），16px 会把可见行数压掉一截；折中取 15px（比原来 14.5 大一档），
   控件同步提到 13.5px。这是有意的差异，不是没对齐。
*/
:root {
  /* 间距（4px 步进，和参照实现同一个节奏） */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-7: 32px;

  /* 外观刻度（「系统 → 界面」可调的圆角倍率 / 界面缩放）的 CSS 兜底默认：
     正常由 index.html 首屏脚本与 appearance.js 以内联变量写入；这里给一份 1，
     保证任何主题下都有值可 calc（2026-10-09 从暗色主题块搬来 —— 原先只有暗色有兜底、
     亮色没有，不对称；搬来后亮暗一致，且与圆角标度同处一份真源）。 */
  --r-scale: 1;
  --zoom: 1;

  /* 字号档位（只用这几档，别再出现 11.5 / 12.5 / 13.5 这类临时值）。
     2026-10-09 收口：新增 `--fs-2xs` 一档给芯片/标签/微元信息（9/9.5/10/10.5 四个历史值
     原先挤在这里，而最小档 xs 是 11 —— 全并到 11 会让 chip 整体胖一号、长标签要溢出，
     所以保留一个比 xs 更小的微档，而不是把它们往上推）。其余历史值按下表归位：
     11/11.5→xs、12/12.5→sm、13/13.5/14/14.5→md、15/16→lg、17~20→xl、22→2xl。 */
  --fs-2xs: 10px;
  --fs-xs: 11px;
  --fs-sm: 12px;
  --fs-md: 13.5px;
  --fs-lg: 15px;
  --fs-xl: 19px;
  --fs-2xl: 23px;
  --lh-tight: 1.35;
  --lh-base: 1.55;

  /* 圆角：按"角色"分四档（不是随手四种）——小件 8 / 控件 10 / 卡片与导航 12 / 大块 16。
     参照实现也是角色制（导航与卡片 12px、按钮 7px），关键是同一角色处处一致。 */
  --r-sm: calc(8px * var(--r-scale));
  --r-input: calc(10px * var(--r-scale));
  --r-md: calc(10px * var(--r-scale));
  --r-card: calc(12px * var(--r-scale));
  --r-lg: calc(12px * var(--r-scale));
  --r-xl: calc(16px * var(--r-scale));
  /* 液态玻璃档的浮动壳：侧栏/顶栏/设置正文各自一档（都跟 --r-scale 走） */
  --r-float: calc(20px * var(--r-scale));
  --r-bar: calc(16px * var(--r-scale));
  --r-sheet: calc(18px * var(--r-scale));
  /* 胶囊：几何上不随 --r-scale 变（它就是"两端全圆"），所以刻意不进 calc */
  --r-pill: 999px;

  /* 卡片阴影：极轻（它的 0 1px 2px rgba(0,0,0,.05)），只用来把卡片从底上"抬"起来 */
  --shadow-card: 0 1px 2px rgba(0, 0, 0, .05);
}
[data-theme='light'] {
  /* 亮色下同样的阴影要更明显一点才看得出来（白卡贴浅底） */
  --shadow-card: 0 1px 2px rgba(16, 24, 40, .06), 0 2px 6px rgba(16, 24, 40, .04);
}

/* ── 壳层：按标度重排 ── */
body { font-size: var(--fs-lg); line-height: var(--lh-base); }

/* 导航项：字重降到 500（选中 600），行高与内边距走标度，高度从 35 抬到 38 更接近它的手感 */
.tab { font-size: var(--fs-md); font-weight: 500; padding: var(--sp-2) 10px; gap: 10px; border-radius: var(--r-card); }
.tab.active { font-weight: 600; }
.tab .ico { width: 17px; height: 17px; }

/* 按钮：统一高度与内边距（原来 7/4px 两档落差太大） */
.btn { font-size: var(--fs-md); padding: 6px 14px; border-radius: var(--r-input); min-height: 32px; }
.btn-small { font-size: var(--fs-sm); padding: 4px 10px; border-radius: var(--r-sm); min-height: 27px; }

/* 输入/下拉/文本域：几何与聚焦统一在文件末尾「表单控件 v2」。
   这里那版（min-height 32px / padding 6 10 / 13.5px）与 .field 那版（h 36 / padding 0 12）
   同时在跑，结果是"同一页里两种高度的输入框"—— 正是用户看到的"输入框不符合美学"。 */

/* 卡片：统一 12px 圆角 + 极轻阴影（这是"浮起来"的关键，我们没有它就显得平） */
/* ⚠️ .kpi / .panel 不在这一组（2026-10-09 复核）：它们分别是"表面内的分格"与"细线分节"，
   不该有卡片圆角与投影 —— 之前在这里被 --shadow-card 覆盖掉了 .kpi 的发丝线分格（见上面
   .kpi 的 box-shadow）与 .panel 的"不再是每块一张卡"（见上面 .panel），值班台的两级表面
   等于没落地。浏览器实测（computed）：改前 .kpi/.panel = 12px 圆角 + 卡片阴影；改后 = 发丝线/none。 */
.usage-card, .tool-card, .pd-chat, .price-card, .plat-card {
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
}
/* 卡片内边距走标度（16px），比原来的 13~15 松一档 */
.panel { padding: var(--sp-4) 18px; }
.kpi { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
/* .kpi-grid 的圆角是 18px：首格图标若仍按 16px 内缩，会落进圆角弧线里（实测贴角）。 */
html[data-glass] .kpi { padding-left: 20px; padding-right: 20px; }
.kpi-value { font-size: var(--fs-2xl); }
.pane-head h2 { font-size: var(--fs-xl); }
.panel-titles h3 { font-size: var(--fs-md); }

/* 表格：行高与字号统一（原来每处各写一套） */
.usage-table th, .usage-table td, .archive-table td, .member-table td {
  font-size: var(--fs-sm);
  padding: var(--sp-2) var(--sp-3);
}

/* 顶栏：定高 + 控件字号统一，四个状态项不再各自成块 */
.topbar { min-height: 46px; }
.top-status .chip, .chip { font-size: var(--fs-xs); }


/* 选中文字用强调色柔和底（细节，但"手感"就是这些攒出来的） */
::selection { background: var(--accent-soft); }


/* ══════════════════════════════════════════════════════════════
   交互反馈 v2（2026-10-08）：按参照控制台实测的"每个可交互元素都有过渡"补齐
   ══════════════════════════════════════════════════════════════
   实测它：导航 / 按钮 / 链接 / chip **全部**带 `0.15s cubic-bezier(.4,0,.2,1)`
   （color/background/border/shadow/transform），弹层有 enter/exit 关键帧、日志行有
   log-row-in、状态点是 animate-pulse。我们那边：只有 .btn/.tab/.session-item 等少数
   有过渡（且被上面那条兜底改成了 0.2s ease），chip / 圆点 / 菜单项 / 表格行 / 卡片
   子元素都没有 —— 手感差就差在这里。

   规则：① 能点的东西统一 150ms + 标准曲线（--ease-in-out）；
        ② 键盘高频动作（Tabs/开关）不加位移，只做颜色；
        ③ 位移只在 hover/按下这类"人在操作"的时刻出现，且 ≤2px。
*/
:root {
  --dur-interaction: 150ms;   /* 与参照实现同一个量级（0.15s） */
}

.tab, .settings-menu-item, .chip, .status-badge, .unread-pill, .tab-label i,
.pick-item, .mm-prov, .mm-model, .panel-link, .feed li,
.plat-row, .plat-grid-row, .kv dd a, .incident-actions button, .list-head {
  transition:
    color var(--dur-interaction) var(--ease-in-out),
    background-color var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out),
    box-shadow var(--dur-interaction) var(--ease-in-out);
}

/* 数据行：能点的给悬浮底 + 左侧强调条（扫描长表格时不会串行） */
@media (hover: hover) and (pointer: fine) {
  .usage-table tbody tr:hover,
  .archive-table tbody tr:hover,
  .member-table tbody tr:hover { background: var(--hover); }
  .settings-menu-item:hover { background: var(--hover); color: var(--text); }
  .feed li:hover { background: var(--hover); }
  .plat-row:hover, .plat-grid-row:hover { background: var(--hover); }
}

/* 「已连接」那种状态点：参考实现是 animate-pulse（2s），我们原来是死点。
   用既有 breathe 关键帧，但放慢到 2.4s、只到 0.7 透明度 —— 是"活着"的暗示，不是闪烁。 */
.dot.dot-on { animation: breathe 2.4s var(--ease-in-out) infinite; }
@media (prefers-reduced-motion: reduce) {
  .dot.dot-on { animation: none; }
}

/* 键盘可达性：除了按钮，其它可聚焦控件也要能看出焦点在哪（原来只有 .btn 有） */
a:focus-visible, [tabindex]:focus-visible, [role='button']:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ══════════════════════════════════════════════════════════════════════════════
   外观 v3 + 交互反馈 v3（2026-10-08）
   ──────────────────────────────────────────────────────────────────────────────
   这一节是"照着参照控制台（SnowLuma WebUI）逐项实测"之后的落实，测量结论 → 我们的做法：

     · 换主题不是颜色渐变，而是整页交给 View Transitions API，再给
       ::view-transition-new(root) 挂一条 clip-path: circle() 的关键帧 ——
       新主题从鼠标点下去的地方像水波铺开（0.5s / cubic-bezier(.16,1,.3,1)，即 ease-out-expo）。
       → 见 ::view-transition-* 与 @keyframes vt-reveal，圆心/半径由 ui/core/theme-transition.js 算。

     · 开关是 44×24 的胶囊 + 18px 白圆点，圆点位移 20px，off 是 inset 描边、on 是强调色实底。
       → 直接把全站 .checkbox-row / .plat-row 的 checkbox 长成开关（不另造一套 DOM，
         这样 63 处开关的 id、label for、保存逻辑统统不用动）。
       注：参照实现的圆点没有过渡（是"跳"过去的），我们给它一条 160ms 弹簧曲线 —— 同一件事做得更好。

     · 分段控件是"轨道里平移的滑块"：轨道 9px 圆角 / 3px 内边距 / inset 阴影，滑块 6px 圆角
       + 0 1px 2px 阴影。→ .seg 由 ui/core/segment.js 插 .seg-pill，思考档位与外观面板共用一套。

     · 无障碍三态各自有全局开关：减弱动效（时长压到 0.01ms）、关闭全部动效（animation:none）、
       高对比（边框与次要文字加对比度）。→ html[data-motion] / html[data-contrast]，
       同时尊重系统 prefers-reduced-motion。

     · 可点元素全部 150ms 标准曲线；紧凑控件按下 scale(.96)；整宽行（导航/菜单）只变色不缩放 ——
       整宽行缩放会横向抽动，这是参照实现也遵守的规矩。

   新增外观设置请走这里的组件（.opt-card / .opt-row / .swatch / .scheme-card / .tweak-row），
   不要再写内联样式，也不要再另开一套开关/分段控件。
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 0. 强调色的派生色 + 首屏兜底 ──
   派生色只用 --accent 一个输入：柔和底 / 描边 / 焦点环全由 color-mix 现算。这样换强调色
   只需要重算一个值，而首屏那段内联脚本（在 <head> 里、CSS 之前跑）也只需要抄一个值 ——
   抄得越少越不会漂。前面三行是给不支持 color-mix 的老浏览器兜底（默认蓝那一套 rgba），
   支持时被后面三条覆盖。 */
:root {
  --accent-side: var(--accent);
  --accent-fg: #fff;
  /* 兜底值：不支持 color-mix 的浏览器就用这一套（默认蓝的 rgba）。
     ⚠️ 不能在同一个块里"先写兜底、再写 color-mix"—— 同一块内后者永远赢，兜底等于没写；
     而且自定义属性存的是 token 流，不支持时每个 var() 都会在**计算期**失效、底色直接消失。
     所以真兜底必须用 @supports 分开（见下面那个块）。2026-10-08 审查修正。 */
  --accent-soft: rgba(76, 141, 255, .12);
  --accent-border: rgba(76, 141, 255, .36);
  --accent-ring: rgba(76, 141, 255, .4);
  --accent-side-soft: var(--accent-soft);
  --accent-side-border: var(--accent-border);
  /* 自定义背景只存四个"数据"，拼装在这里（同理：内联脚本只管填值） */
  --bg-solid: #0b1220;
  --bg-from: #1b2a4a;
  --bg-to: #2e2440;
  --bg-angle: 135deg;
  /* 控制件的"轮廓色"：亮色下 --border (#e0e7f2) 贴白卡只有 1.24:1，开关的槽、分段控件的
     轨道、未选中的单选圈都靠它表达 —— 实测 1.11~1.16，等于"看不见控件"。掺主文字色加深一档。 */
  --ctl-line: var(--border);   /* 兜底：见下面 @supports（不支持 color-mix 时退回浅一档描边） */
  --switch-w: 44px;
  --switch-h: 24px;
  --switch-knob: 18px;
  --switch-travel: 20px;
}

/* ── 0b. color-mix 派生色（@supports 分支）──
   支持 color-mix 时，强调色的柔和底/描边/焦点环与控件轮廓色都从 --accent / --text "现算"——
   换强调色只需要重算一个值（首屏内联脚本也只抄一个值）。不支持的老浏览器留在上面 :root 的
   rgba 兜底上（默认蓝那一套）。**别把这些写回上面那个块**：同块内后写的那条永远赢，
   兜底就永远不生效（2026-10-08 审查发现这个坑）。 */
@supports (color: color-mix(in srgb, red, blue)) {
  :root {
    --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);
    --accent-border: color-mix(in srgb, var(--accent) 36%, transparent);
    --accent-ring: color-mix(in srgb, var(--accent) 40%, transparent);
    --accent-side-soft: color-mix(in srgb, var(--accent-side) 12%, transparent);
    --accent-side-border: color-mix(in srgb, var(--accent-side) 36%, transparent);
    /* 22% 是"白卡背景"下的最小值；液态玻璃档正文直接压在壁纸上，亮部（壁纸右上角的
       白色泛光）会把 1px 描边吃掉，输入框只剩半截边（2026-10-19 实测「异常」页数字框）。 */
    --ctl-line: color-mix(in srgb, var(--text) 30%, var(--border));
  }
}

/* ── 1. 界面字体 ──
   字体栈写在 CSS 而不是 JS：首屏内联脚本只要抄一个 data-font 值，不必连字体栈一起复制。
   「默认」就是上面 body 那条原始栈，所以它没有对应规则 —— 不设 data-font 即为默认。 */
html[data-font='rounded'] body {
  font-family: 'Segoe UI Variable Display', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', system-ui, sans-serif;
}
html[data-font='serif'] body {
  font-family: 'Iowan Old Style', Georgia, 'Songti SC', 'Noto Serif SC', 'SimSun', serif;
}

/* ── 2. 自定义背景：纯色 / 渐变时整页换底；=无 时保留原来的装饰性径向渐变 ──
   ⚠️ 2026-10-09 换掉「一条两色线性渐变」。原因有两个，都不是审美口味问题：
     ① 两色线性渐变是现在最典型的"AI 生成感"——所有生成出来的页面都是那一张蓝紫斜渐变；
     ② 更实际的：**玻璃背后必须有结构才成立**。一整片平的颜色渐变，玻璃盖上去只能得到
        "浅一点的灰矩形"，无论参数怎么调。苹果那些 demo 好看，一半功劳在背后那张有层次的壁纸。
   还是原来那三个输入（起色 / 止色 / 角度），只是把它们铺成一组合成：
     两块错位的径向光斑（上中 + 右下，制造明暗错落）+ 线性底 + 四角压暗的暗角。
   两个色只当"光"用（掺进 --bg 里，低饱和），所以哪怕用户挑了两个很艳的色，
   出来也是夜景感而不是塑料感。 */
html[data-background='gradient'] body {
  background-image:
    radial-gradient(130% 100% at 50% -10%,
      color-mix(in srgb, var(--bg-from) 70%, transparent), transparent 68%),
    radial-gradient(115% 95% at 88% 108%,
      color-mix(in srgb, var(--bg-to) 58%, transparent), transparent 66%),
    linear-gradient(var(--bg-angle),
      color-mix(in srgb, var(--bg-from) 40%, var(--bg)),
      color-mix(in srgb, var(--bg-to) 34%, var(--bg))),
    linear-gradient(var(--bg), var(--bg));
  background-attachment: fixed;
  background-repeat: no-repeat;
  background-size: cover;
}
/* 液态玻璃的衬底（2026-10-19 对照参考实现返工）：APK 里那套玻璃之所以好看，是它整屏
   浮在一张壁纸上 —— 边缘折射、饱和度、模糊都需要"有东西可透"。控制台默认是纯色深底，
   面板底下什么都没有，所以之前怎么调都像实心卡片。
   只在「材质=液态玻璃」且「背景=无」时铺这张图；用户显式选了纯色/渐变就尊重他的选择。
   body 上压一层半透明纯色（不是渐变）：暗色下保证文字可读，浅色下是白纱。 */
html[data-glass='liquid'][data-background='none'] {
  background-color: #0b1220;
  background-image: url('/assets/liquid-wallpaper.png');
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* 固定住：滚动的是内容，不是壁纸；玻璃下面的东西才会相对移动、折射才看得出来 */
  background-attachment: fixed;
}
html[data-glass='liquid'][data-background='none'] body {
  /* 深色主题下要压得比浅色更狠：白字直接压在这张亮蓝/青的壁纸上，靠 60% 出头的
     纯色纱保证正文对比度；面板自己还有 40% 表面色，两层叠起来才是参考实现的暗玻璃。 */
  background-color: rgba(4, 8, 15, .70);
  background-image: none;
}
[data-theme='light'][data-glass='liquid'][data-background='none'] body {
  background-color: rgba(250, 252, 255, .58);
}

/* 暗角：单独一层压在最后，把四角收掉 —— 平铺到底的渐变会让页面"发散的松" */
html[data-background='gradient'] body::after,
html[data-background='solid'] body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(140% 120% at 50% 45%,
    transparent 42%, color-mix(in srgb, var(--bg) 78%, transparent) 100%);
}
html[data-background='solid'] body {
  background-image:
    radial-gradient(120% 80% at 50% 0%,
      color-mix(in srgb, var(--bg-solid) 72%, #ffffff 10%), transparent 70%),
    linear-gradient(var(--bg-solid), var(--bg-solid));
}

/* ── 3. 深色强度：只压三档背景，色板的描边/文字照旧（纯黑不该把配色一起抹平） ── */
[data-theme='dark'][data-dark-intensity='oled'] {
  --bg: #000000;
  --bg-2: #07080b;
  --bg-3: #101318;
}
[data-theme='dark'][data-dark-intensity='oled'] body { background-image: none; }

/* ── 4. 表面色板（只给 底/卡/面板/边框/文字，强调色独立） ──
   2026-10-08 重做：原来这五套是"在默认底色上挪几个 RGB 单位"凑出来的，实测两两之间
   暗色 ΔL* 只有 0.2~2.8（中位 1.2，肉眼门槛约 3）、**亮色 ΔL* 0.0~0.1（完全同色）** ——
   用户反馈"配色方案的效果不明显"就是这个原因。现在按"暗色走亮度阶梯、亮色走色相与彩度"
   重排（亮色下背景都在 L*92~97，亮度分不开，只能靠冷暖与彩度区分）：
     默认 L*5.5 中性蓝灰 → 石板 10.7 中性冷灰 → 玫瑰 13.5 紫调 → 青林 15.5 墨绿 → 极地 21.6 北欧蓝
   亮色：默认 冷蓝白 / 石板 纯中性灰 / 极地 蓝灰 / 青林 米黄 / 玫瑰 粉白
   判据写进了 test/appearance-schemes.test.mjs（两两必须可分辨），改值前先跑它。 */
[data-scheme='slate'][data-theme='dark'] {
  --bg: #191d24; --bg-2: #20252e; --bg-3: #282f39;
  --border: #39424e; --text: #e9edf2; --muted: #9ea8b5;
}
[data-scheme='slate'][data-theme='light'] {
  --bg: #ededed; --bg-2: #f8f8f8; --bg-3: #e3e3e3;
  --border: #d5d5d5; --text: #202020; --muted: #5f5f5f;
}
[data-scheme='nord'][data-theme='dark'] {
  --bg: #2e3440; --bg-2: #3b4252; --bg-3: #434c5e;
  --border: #4c566a; --text: #eceff4; --muted: #aeb8c9;
}
[data-scheme='nord'][data-theme='light'] {
  --bg: #e5e9f0; --bg-2: #eceff4; --bg-3: #dde3ec;
  --border: #cbd2de; --text: #2e3440; --muted: #556070;
}
[data-scheme='forest'][data-theme='dark'] {
  --bg: #1b2a23; --bg-2: #22332b; --bg-3: #2a3d34;
  --border: #3a4f44; --text: #e4ede7; --muted: #9db3a8;
}
[data-scheme='forest'][data-theme='light'] {
  --bg: #f6f1e3; --bg-2: #fdf6e3; --bg-3: #ece5d2;
  --border: #dbd3bf; --text: #3d4740; --muted: #5f6a59;
}
[data-scheme='rose'][data-theme='dark'] {
  --bg: #241f38; --bg-2: #2b2645; --bg-3: #352f52;
  --border: #453d63; --text: #e6e1f5; --muted: #aca5c8;
}
[data-scheme='rose'][data-theme='light'] {
  --bg: #fceef4; --bg-2: #fff4f8; --bg-3: #f4e3ea;
  --border: #e6d2da; --text: #463039; --muted: #7a5f6b;
}

/* ── 5. 侧栏样式 + 侧栏强调色（应用范围选"仅侧栏"时，按钮保持默认蓝，只有侧栏跟着变） ── */
html[data-sidebar-style='panel'] #sidebar { background: var(--bg-3); }
html[data-sidebar-style='accent'] #sidebar {
  background: color-mix(in srgb, var(--accent-side) 10%, var(--bg-2));
  border-right-color: color-mix(in srgb, var(--accent-side) 22%, var(--border));
}
#sidebar .tab.active {
  /* 底色/左缘条都由 .side-pill 给（它会滑动，见「一级导航」区块）——这里只留文字颜色 */
  background: transparent;
  color: var(--accent-side);
  box-shadow: none;
}
#sidebar .side-head .brand-mark { box-shadow: 0 0 0 1px var(--accent-side-border); }

/* ── 6. 高对比模式：加粗边框、提亮次要文字（放在色板之后，同权重靠顺序取胜） ── */
/* color-mix 放进 @supports：不支持它的浏览器不能拿到一个非法自定义属性值 ——
   那会让用到 var() 的整条声明失效（边框消失、文字掉回默认色），与高对比的目标相反。
   不支持时沿用主题原有的 --border/--muted/--faint。 */
@supports (color: color-mix(in srgb, red, blue)) {
  :root[data-contrast='high'] {
    --border: color-mix(in srgb, var(--text) 30%, var(--bg));
    --muted: color-mix(in srgb, var(--text) 80%, var(--bg));
    --faint: color-mix(in srgb, var(--text) 62%, var(--bg));
  }
}
:root[data-contrast='high'] .panel,
:root[data-contrast='high'] .kpi,
:root[data-contrast='high'] .opt-card,
:root[data-contrast='high'] .plat-card,
:root[data-contrast='high'] .usage-card,
:root[data-contrast='high'] .tool-card { border-color: var(--border); }
:root[data-contrast='high'] .hint { color: var(--muted); }

/* ── 7. 动效开关：减弱（把时长压到看不见）/ 关闭（直接没有动画）。
      两者都尊重系统偏好；浏览器里 prefers-reduced-motion 优先于我们的设置。 ── */
html[data-motion='reduced'] *,
html[data-motion='reduced'] *::before,
html[data-motion='reduced'] *::after {
  transition-duration: .01ms !important;
  animation-duration: .01ms !important;
  animation-iteration-count: 1 !important;
  scroll-behavior: auto !important;
}
html[data-motion='reduced'] .dot.dot-on { animation: none !important; }
html[data-motion='off'] *,
html[data-motion='off'] *::before,
html[data-motion='off'] *::after {
  transition-duration: .01ms !important;
  animation: none !important;
  scroll-behavior: auto !important;
}
/* 转圈是"正在进行"的唯一提示，全关时留它（参照实现也留着 animate-spin） */
html[data-motion='off'] .spin,
html[data-motion='off'] .spinner,
html[data-motion='off'] .loading-spinner { animation: spin 1s linear infinite !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .dot.dot-on { animation: none !important; }
}

/* ── 8. 主题切换的圆形波纹（View Transitions API） ── */
::view-transition-old(root) { z-index: 0; animation: none; mix-blend-mode: normal; }
::view-transition-new(root) { z-index: 1; animation: vt-reveal .5s cubic-bezier(.16, 1, .3, 1); }
@keyframes vt-reveal {
  from { clip-path: circle(0px at var(--vt-x, 50%) var(--vt-y, 50%)); }
  to { clip-path: circle(var(--vt-r, 150vmax) at var(--vt-x, 50%) var(--vt-y, 50%)); }
}
html[data-motion='reduced']::view-transition-old(root),
html[data-motion='reduced']::view-transition-new(root),
html[data-motion='off']::view-transition-old(root),
html[data-motion='off']::view-transition-new(root) { animation: none !important; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }
}
/* 页面整体不参与波纹：只有 root 一层的快照做遮挡，避免卡片各自闪一下 */
::view-transition-group(root) { animation-duration: .5s; }

/* ── 9. 开关（checkbox → 胶囊开关）。
      只改"开关语义"的位置：设置页 .checkbox-row、平台能力页 .plat-row、外观页 .opt-row-ctrl。
      列表里那种多选（.ma-check / .bl-chk / .group-check / 白名单分群）保持方框，
      它们表达的是"选中若干项"，不是"开/关"，长成开关反而会读错。 ── */
.checkbox-row > input[type='checkbox'],
.plat-row > input[type='checkbox'],
.opt-row-ctrl > input[type='checkbox'],
input.sw[type='checkbox'] {
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: var(--switch-w);
  height: var(--switch-h);
  margin: 0;
  border: none;
  border-radius: var(--r-pill);
  background: var(--bg-3);
  box-shadow: inset 0 0 0 1px var(--ctl-line);
  cursor: pointer;
  transition:
    background-color var(--dur-interaction) var(--ease-in-out),
    box-shadow var(--dur-interaction) var(--ease-in-out);
}
.checkbox-row > input[type='checkbox']::after,
.plat-row > input[type='checkbox']::after,
.opt-row-ctrl > input[type='checkbox']::after,
input.sw[type='checkbox']::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: var(--switch-knob);
  height: var(--switch-knob);
  border-radius: 50%;
  background: #fff;
  /* 圆点的描边从 6% 黑提到 14%：参照实现是 ring-black/10 + 一层阴影 —— 亮色下圆点贴浅底，
     只靠 6% 的话"看不出这里有个圆点"（实测白点 vs 轨道 1.11:1）。 */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .14), 0 1px 2px rgba(0, 0, 0, .22);
  transition: transform 160ms var(--ease-spring);
}
.checkbox-row > input[type='checkbox']:checked,
.plat-row > input[type='checkbox']:checked,
.opt-row-ctrl > input[type='checkbox']:checked,
input.sw[type='checkbox']:checked {
  background: var(--accent);
  box-shadow: inset 0 0 0 1px transparent;
  animation: none;   /* 旧的 checkPop 是给方框用的，开关上不需要 */
}
.checkbox-row > input[type='checkbox']:checked::after,
.plat-row > input[type='checkbox']:checked::after,
.opt-row-ctrl > input[type='checkbox']:checked::after,
input.sw[type='checkbox']:checked::after { transform: translateX(var(--switch-travel)); }
.checkbox-row > input[type='checkbox']:disabled,
.plat-row > input[type='checkbox']:disabled,
.opt-row-ctrl > input[type='checkbox']:disabled,
input.sw[type='checkbox']:disabled { opacity: .45; cursor: not-allowed; }
.checkbox-row > input[type='checkbox']:disabled::after,
.opt-row-ctrl > input[type='checkbox']:disabled::after { background: var(--muted); }
.checkbox-row > input[type='checkbox']:focus-visible,
.plat-row > input[type='checkbox']:focus-visible,
.opt-row-ctrl > input[type='checkbox']:focus-visible,
input.sw[type='checkbox']:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-pill);
}
/* 开关行：开关在前、文字在后，行高按标度给足（原来是 6px 挤在一起） */
.checkbox-row { align-items: flex-start; gap: var(--sp-3); padding: var(--sp-2) 0; }
.checkbox-row > label { line-height: var(--lh-base); }
@media (prefers-reduced-motion: reduce) {
  .checkbox-row > input[type='checkbox']::after,
  .plat-row > input[type='checkbox']::after,
  .opt-row-ctrl > input[type='checkbox']::after { transition: none; }
}

/* ── 10. 分段控件 v2：轨道 + 平移滑块 ── */
.seg {
  position: relative;
  display: flex;          /* 这两条原来散落在老规则里：思考档位要按 100% 撑满它的容器 */
  width: 100%;
  box-sizing: border-box;
  gap: 0;
  /* 用 inset 描边代替 1px 边框：border 的宽度会被浏览器按 DPR 取成 0.667px 这类小数，
     而 offsetLeft/clientLeft 只给整数 —— 滑块要按几何算位置，那个取整误差会让它对不齐
     （实测差 1px）。去掉 border 之后 padding box 就是 border box，算术全是整数。
     视觉与 1px 边框等价（描边画在背景之内，滑块是子元素、画在它上面，不会被盖住）。 */
  padding: 4px;
  background: var(--bg-3);
  border: none;
  border-radius: calc(9px * var(--r-scale));
  box-shadow: inset 0 0 0 1px var(--ctl-line), inset 0 1px 2px rgba(0, 0, 0, .07);
}
.seg-pill {
  position: absolute;
  top: 4px;
  bottom: 4px;
  /* left 与轨道内边距同源：由 segment.js 按实测几何写进 --pill-left，
     CSS 里不再写死一个和 padding 重复的 4px */
  left: var(--pill-left, 4px);
  width: var(--pill-w, 0px);
  border-radius: calc(6px * var(--r-scale));
  background: var(--accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .28);
  transform: translateX(var(--pill-x, 0px));
  opacity: 0;
  pointer-events: none;
  transition:
    transform var(--dur-interaction) var(--ease-out-quart),
    width var(--dur-interaction) var(--ease-out-quart),
    opacity 120ms linear;
}
.seg-item {
  position: relative;
  z-index: 1;
  flex: 1 1 0;            /* 等宽等分（原来在老规则里） */
  min-width: 0;
  white-space: nowrap;
  background: transparent;
  border: none;
  color: var(--muted);
  font-weight: 500;
  font-size: var(--fs-sm);
  padding: 6px 10px;
  border-radius: calc(6px * var(--r-scale));
  cursor: pointer;
  transition: color var(--dur-interaction) var(--ease-in-out);
}
@media (hover: hover) and (pointer: fine) { .seg-item:hover { color: var(--text); background: transparent; } }
.seg-item.selected {
  background: transparent;
  color: var(--accent-fg);
  font-weight: 600;
  box-shadow: none;
}
.seg.dim { opacity: .45; }

/* ── 11. 外观面板组件 ── */
.opt-card {
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--bg-2);
  box-shadow: var(--shadow-card);
  padding: var(--sp-4);
  margin: 0 0 var(--sp-4);
}
.opt-head { display: flex; align-items: center; gap: 8px; }
.opt-head .ico { display: inline-flex; color: var(--accent); }
.opt-head h4 { margin: 0; font-size: var(--fs-md); font-weight: 600; letter-spacing: -.1px; }
.opt-desc { color: var(--muted); font-size: var(--fs-sm); margin: 4px 0 var(--sp-3); line-height: var(--lh-base); }
.opt-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); padding: var(--sp-3) 0;
  border-top: 1px solid var(--border);
}
.opt-grid > .opt-row:first-child, .opt-card > .opt-row:first-child { border-top: none; padding-top: 0; }
.opt-row-main { min-width: 0; }
.opt-row-title { font-size: var(--fs-md); }
.opt-row-sub { color: var(--muted); font-size: var(--fs-sm); margin-top: 3px; line-height: var(--lh-base); }
.opt-row-ctrl { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; }
.opt-row-ctrl .seg { width: auto; max-width: 100%; }   /* 老的 .seg 是 width:100%（思考档位要撑满），
   在外观面板里它只是行尾的一个小控件，必须回到"按内容宽度" */

/* 强调色色板：圆形、选中有勾与外环（参照实现：hover 放大 1.05，我们 1.08 更明显） */
.swatch-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.swatch {
  position: relative;
  width: 36px; height: 36px; padding: 0;
  display: inline-grid; place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: #fff;
  cursor: pointer;
  transition:
    transform var(--dur-interaction) var(--ease-in-out),
    box-shadow var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out);
}
@media (hover: hover) and (pointer: fine) { .swatch:hover { transform: scale(1.08); } }
.swatch:active { transform: scale(.96); }
.swatch svg { width: 16px; height: 16px; stroke-width: 3; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .45)); }
.swatch.on { border-color: transparent; box-shadow: 0 0 0 2px var(--bg-2), 0 0 0 4px var(--accent); }
.swatch-custom { overflow: hidden; }
.swatch-custom input[type='color'] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; border: none; padding: 0;
}

/* 色板预览卡：每格自带"底色 + 卡片色 + 强调色点"，跟参照实现一样先看再挑 */
.scheme-row { display: flex; flex-wrap: wrap; gap: 10px; }
.scheme-card {
  width: 88px; padding: 6px;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: transparent;
  cursor: pointer;
  transition: border-color var(--dur-interaction) var(--ease-in-out), box-shadow var(--dur-interaction) var(--ease-in-out);
}
.scheme-card .sc-prev {
  width: 100%; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: calc(8px * var(--r-scale));
}
.scheme-card .sc-dots {
  display: flex; gap: 4px; padding: 4px 6px;
  border-radius: calc(6px * var(--r-scale));
  box-shadow: 0 1px 2px rgba(0, 0, 0, .14);
}
.scheme-card .sc-dots i { width: 6px; height: 6px; border-radius: 50%; display: block; }
.scheme-card .sc-label { font-size: var(--fs-sm); font-weight: 500; }
.scheme-card:hover { border-color: var(--accent-border); }
.scheme-card:active { transform: scale(.98); }
.scheme-card.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-ring); }

/* 主题微调：颜色格 + 变量名 + 当前值 + 重置 */
.tweak-list { display: flex; flex-direction: column; }
.tweak-row {
  display: flex; align-items: center; gap: 10px;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.tweak-row:first-child { border-top: none; }
.tweak-name { flex: 1 1 auto; min-width: 0; font-size: var(--fs-md); }
.tweak-var { font-family: var(--mono); font-size: var(--fs-xs); color: var(--faint); }
.tweak-hex { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); min-width: 66px; text-align: right; }
.tweak-swatch {
  position: relative;
  width: 30px; height: 26px; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: calc(7px * var(--r-scale));
  overflow: hidden; cursor: pointer;
}
.tweak-swatch input[type='color'] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; border: none; padding: 0; }
.tweak-reset {
  flex: 0 0 auto;
  background: none; border: 1px solid var(--border); color: var(--muted);
  border-radius: var(--r-sm); padding: 3px 8px; font-size: var(--fs-xs); cursor: pointer;
  transition: color var(--dur-interaction) var(--ease-in-out), border-color var(--dur-interaction) var(--ease-in-out);
}
.tweak-reset:hover:not(:disabled) { color: var(--text); border-color: var(--accent-border); }
.tweak-reset:disabled { opacity: .4; cursor: default; }

/* 滑条行：控件 + 数值（等宽数字，拖动时不左右跳） */
.range-row { display: flex; align-items: center; gap: 10px; }
.range-row input[type='range'] { width: 150px; }
.range-value {
  font-size: var(--fs-sm); color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-width: 48px; text-align: right;
}

/* ── 12. 交互反馈 v3：按下回弹 + 焦点环 + 滚动条（各自只留一条权威规则） ── */

/* 紧凑控件按下缩到 0.96；整宽行（.tab / .settings-menu-item / .plat-row）只变色不缩放 ——
   整宽行缩放会让文字横向抽动，参照实现也是这个口径。 */
.btn, .icon-btn, .seg-item, .swatch, .scheme-card, .opt-row, .tier-btn,
.tweak-reset, .plat-card-head button {
  transition-property: color, background-color, border-color, box-shadow, transform, opacity;
  transition-duration: var(--dur-interaction);
  transition-timing-function: var(--ease-in-out);
}
.btn:active:not(:disabled), .icon-btn:active:not(:disabled), .seg-item:active,
.tier-btn:active:not(:disabled), .tweak-reset:active:not(:disabled) {
  transform: scale(.96);
}
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.btn-primary:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); opacity: .92; }
@media (prefers-reduced-motion: reduce) {
  .btn:active:not(:disabled), .icon-btn:active:not(:disabled), .seg-item:active,
  .tier-btn:active:not(:disabled), .swatch:active, .scheme-card:active { transform: none; }
}

/* 焦点环：统一到一条。原来是两条（一条 2px 强调色、一条 36% 透明描边）互相打架，
   后写的赢、但读代码的人会以为另一条也生效。 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* 滚动条：轨道透明、thumb 用 padding-box 裁出 2px 透明边（参照实现同款）。
   原来写成 `border: 2px solid var(--bg)`，等于给 thumb 描了一圈背景色的边 —— 深色下
   每条滚动条都像贴了条灰胶带。 */
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--text) 22%, var(--border)) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-corner { background: transparent; }
*::-webkit-scrollbar-thumb {
  /* 滑块原来就是 --border：深色下与底色 1.31:1，等于没有滚动条。掺 22% 主文字色后约 2.5:1
     （参照实现用的是半透明白，约 2.02）—— 看得见，但不至于抢注意力。 */
  background-color: color-mix(in srgb, var(--text) 22%, var(--border));
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: var(--r-pill);
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--faint); background-clip: padding-box; }

/* 顶栏可隐藏项（设置 → 外观 → 顶栏） */
html[data-hide-badges='1'] .top-status { display: none; }
html[data-hide-theme-btn='1'] #theme-btn { display: none; }

/* ══════════════════════════════════════════════════════════════════════════════
   侧栏 v2 / 表单控件 v2（2026-10-08 第二轮，逐项对着参照控制台实测）
   ──────────────────────────────────────────────────────────────────────────────
   参照实现（SnowLuma WebUI，实测计算值）：
     一级导航 · 一项 = 图标 16px（在 48px 的图标列里居中）+ 主名 14px/500 + 一行说明 12px/低对比
               · 项高 ≈53px、半径 12px、项间 4px、导航内边距 8px
               · 选中态 = 整行淡强调底 + 左缘 4px 强调条 + 图标染强调色（不是"边框 + 内阴影"双重描边）
               · 没有分组小标题，组与组之间只留空白
     表单控件 · 高 40px、内边距 4~12px、半径 10px、字号 14px、**背景透明 + 一像素描边**
               · 聚焦 = 描边转强调色 + 3px 半透明强调环
               · 字段是"固定列数的网格 + 输入框填满所在列"，不是一个字段一条横幅
   本区块是这两件事的唯一权威定义（上面那些历史规则已改成注释指针）。
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. 一级导航 ── */
/* 「悬停收成图标条」那套的尺寸与曲线 token。数值来自参照实现（SnowLuma WebUI 打包产物，
   2026-10-08 实测）：aside 宽 248 / 收起 64，宽度过渡 260ms cubic-bezier(.4,0,.1,1)，
   文字淡入淡出 200ms。滑块的曲线是 Framer spring(stiffness 380, damping 32) 的贝塞尔近似
   （ζ≈0.82 → 约 4% 过冲、~0.42s 落定）——比 --ease-spring（1.56 的大回弹）收敛得多，
   给"高亮滑过去"用正合适。 */
:root {
  /* ⚠️ 三个值必须自洽：内层内容盒宽 = 外框宽 − 2×左右内边距。
     曾经把内层 min-width 直接写成 --side-w-full（=外框宽 248）→ 内层比内容盒宽 20px，
     展开时底部那一排会顶出侧栏右缘，最右那个图标被裁掉一半（用户报的"左下角图标快超出侧栏"）。
     以后改侧栏宽度只改这三个，别在别处再写死像素。 */
  --side-w-full: 248px;
  --side-w-rail: 64px;
  --side-pad-x: 10px;
  --side-w-inner: calc(var(--side-w-full) - 2 * var(--side-pad-x));
  --side-dur-w: 260ms;
  --side-ease-w: cubic-bezier(.4, 0, .1, 1);
  --side-dur-fade: 200ms;
  --side-dur-slide: 420ms;
  --ease-side-spring: cubic-bezier(.3, 1.18, .4, 1);
}

#sidebar { width: var(--side-w-full); padding: 12px var(--side-pad-x) 10px; gap: 6px; }
.side-head { padding: 2px 8px 10px; gap: 10px; }
.side-head .brand-mark { width: 28px; height: 28px; border-radius: var(--r-sm); }
.side-title strong { font-size: var(--fs-md); font-weight: 600; letter-spacing: 0; }
.brand-sub { font-size: var(--fs-xs); }

/* #tabs 是滑块的 offsetParent（滑块按绝对定位放在它里面），所以它必须是定位元素 */
#tabs { position: relative; display: flex; flex-direction: column; gap: 2px; padding: 0 2px; min-height: 0; overflow-y: auto; }

/* 会滑动的选中块：一个元素同时承载"底块"与"左缘强调条"（参照实现里是两个 layoutId 元素，
   换项时各自 spring 滑过去；我们合成一个，位移/高度一起补间，观感一致而 DOM 少一层）。
   left/right 2px 与 #tabs 的 padding 同源 —— 正好盖住 .tab 那一行的范围。 */
.side-pill {
  position: absolute;
  /* 左右内缩按容器给：主导航的项本身内缩 2px，设置页的分区菜单项是满宽（--pill-inset: 0） */
  left: var(--pill-inset, 2px);
  right: var(--pill-inset, 2px);
  top: 0;
  height: var(--side-pill-h, 0px);
  transform: translateY(var(--side-pill-y, 0px));
  border-radius: var(--r-card);
  background: var(--accent-side-soft);
  pointer-events: none;
  opacity: 0;
  transition:
    transform var(--side-dur-slide) var(--ease-side-spring),
    height var(--side-dur-slide) var(--ease-side-spring),
    width var(--side-dur-w) var(--side-ease-w),
    opacity 150ms linear;
}
.side-pill-bar {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 16px;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--accent-side);
}

.tab {
  position: relative;
  display: flex;
  align-items: center;
  /* 以下两条原来在文件前段那份 .tab 定义里（已并入此处）：按钮要继承界面字体；
     两行标签（主名 + 说明）不许换行，宽度不够时靠省略号 */
  font-family: inherit;
  white-space: nowrap;
  gap: 12px;
  width: 100%;
  padding: 8px 10px 8px 12px;
  border: 0;
  border-radius: var(--r-card);
  background: none;
  text-align: left;
  color: var(--muted);
  font-size: var(--fs-md);
  font-weight: 500;
  cursor: pointer;
  transition: color var(--dur-interaction) var(--ease-in-out),
              background-color var(--dur-interaction) var(--ease-in-out);
}
.tab .ico { flex: 0 0 auto; width: 18px; height: 18px; opacity: .85; }
.tab-label { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tab-label b { font-size: var(--fs-md); font-weight: 500; line-height: 1.35; color: inherit; }
.tab-label i {
  font-style: normal;
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (hover: hover) and (pointer: fine) {
  /* 只有非选中项才有 hover 底：选中项底下是滑块，再叠一层 hover 会把它压暗成"两层色" */
  .tab:not(.active):hover { background: var(--hover); color: var(--text); }
  .tab:not(.active):hover .ico { opacity: 1; }
  .tab:not(.active):hover .tab-label i { color: var(--muted); }
}
/* 选中：底块与左缘条由 .side-pill 给（会滑动），这里只负责文字与图标的颜色，
   背景必须是透明的 —— 否则滑块滑过来时旧的那一格还留着自己的底色。
   原来这里还挂了 inset 描边，与滑块的圆角底块叠在一起边界发毛，去掉。 */
.tab.active {
  background: transparent;
  color: var(--accent-side);
  /* 底色与左缘条归 .side-pill；这里不再写 border-color —— .tab 的 border 已经是 0（死声明） */
  box-shadow: none;
}
/* 左缘强调条已并进 .side-pill（会跟着选中项滑动），这里不再逐项画一根 */
.tab.active .ico { opacity: 1; color: var(--accent-side); }
.tab.active .tab-label b { font-weight: 600; }
.tab.active .tab-label i { color: color-mix(in srgb, var(--accent-side) 65%, var(--muted)); }
/* 组与组之间只留空白（分组标题已取消） */
.tab[data-group-start] { margin-top: 10px; }

.side-foot { margin-top: auto; padding-top: 10px; border-top: 1px solid var(--border); }

/* ── 1b. 悬停展开的图标条（对齐参照实现；只在宽屏用，≤900px 的横向布局不参与） ──
   手法与参照实现一致：aside 只改宽度并被裁切，**内层内容始终 248 宽** ——
   标签因此不是"重排"而是"露出来"，再叠一条 200ms 的 opacity 淡入淡出，
   否则收起的一瞬间能看到半截字被切掉。悬停/聚焦状态由 ui/core/side-nav.js 写在
   #sidebar 的 data-side-open 上；是否启用这一套由外观轴 data-side-rail 决定。 */
@media (min-width: 901px) {
  #sidebar { overflow: hidden; transition: width var(--side-dur-w) var(--side-ease-w); }
  /* 内层固定成**内容盒**宽：收起时靠 aside 裁切露出内容（不重排），展开时严丝合缝不越界 */
  #sidebar > .side-head, #sidebar > #tabs, #sidebar > .side-foot { min-width: var(--side-w-inner); }
  .tab-label, .side-title { transition: opacity var(--side-dur-fade) linear; }
  #sidebar[data-side-open] { width: var(--side-w-full); }
  html[data-side-rail='1'] #sidebar:not([data-side-open]) { width: var(--side-w-rail); }
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .tab-label,
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-title { opacity: 0; }
  /* 收起时滑块从"撑满一行"变成图标下面的方块：可见区宽 = 64 − 10(侧栏左内边距) = 54，
     每一项从 x=12 起（侧栏 10 + #tabs 的 2），所以方块宽 42 正好顶到可见区右缘。 */
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-pill { right: auto; width: 42px; }
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-pill-bar { display: none; }
  /* 收起时底栏只留图标按钮：下拉框与"暂停"没有图标形态，与其露出半截不如收起来 ——
     鼠标一移上去就展开，所有控件都在。 */
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-foot {
    flex-direction: column; align-items: flex-start; min-width: 0; gap: 8px;
  }
  /* 收起时只留图标按钮，且要落在可见的 44px 里（44 − 34 = 10，两边各留 5px 视觉居中） */
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-foot { padding-left: 5px; }
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-foot .runtime-control,
  html[data-side-rail='1'] #sidebar:not([data-side-open]) .side-foot #pause-btn { display: none; }
}
/* 关掉动效：宽度与滑块都直接落位（与参照实现的 duration:0 同一口径） */
html[data-motion='off'] #sidebar, html[data-motion='reduced'] #sidebar,
html[data-motion='off'] .side-pill, html[data-motion='reduced'] .side-pill,
html[data-motion='off'] .tab-label, html[data-motion='reduced'] .tab-label,
html[data-motion='off'] .side-title, html[data-motion='reduced'] .side-title { transition: none; }

/* ── 2. 分段控件：文字不许溢出轨道 ──
   实测：`.seg-item { flex: 1 1 0 }` 会把每一项压成等宽，而 `min-width: 0` 允许它小于自身文字 ——
   "默认（系统）"需要 82px 却只分到 60px，字就顶出圆角轨道（用户看到的"字体超出边框"）。
   改成"内容宽度是下限、多余空间再均分"，并在行上允许换行：宁可控件换到下一行，也不把字挤出去。 */
.seg-item { flex: 1 1 auto; min-width: max-content; }
.opt-row { flex-wrap: wrap; row-gap: 10px; }
.opt-row-ctrl { flex-wrap: wrap; justify-content: flex-end; }

/* ── 3. 表单控件 v2 ── */
:root {
  /* 下拉箭头：写死两份主题色（= --muted 的两套取值）。data URI 里用不了 currentColor，
     而只为一个箭头去包一层 span 不值当；换配色时它不跟着变，但它只是个指示符。 */
  --select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2396a5bf' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E");
}
[data-theme='light'] {
  --select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b6b83' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E");
}

input:not([type]),
input[type='text'], input[type='password'], input[type='number'], input[type='search'],
input[type='url'], input[type='email'], input[type='tel'], input[type='date'], input[type='time'],
input[type='datetime-local'], select, textarea {
  height: 40px;
  padding: 0 12px;
  /* 边框走 --ctl-line：纯 --border 在亮色下是 #e0e7f2 贴白卡，肉眼几乎看不到框，
     "透明底 + 看不见的边"就等于没有输入框。 */
  border: 1px solid var(--ctl-line);
  border-radius: var(--r-input);
  background: transparent;
  color: var(--text);
  font-size: var(--fs-md);
  font-family: inherit;
  line-height: 1.4;
  box-shadow: none;
  transition: border-color var(--dur-interaction) var(--ease-in-out),
              background-color var(--dur-interaction) var(--ease-in-out),
              box-shadow var(--dur-interaction) var(--ease-in-out);
}
textarea { height: auto; min-height: 92px; padding: 10px 12px; resize: vertical; }
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: var(--select-caret);
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 14px 14px;
  padding-right: 32px;
  cursor: pointer;
}
/* 聚焦：描边转强调色 + 3px 半透明强调环 + 底色微微抬起（透明底在聚焦时"亮"一下，
   是"我在这里输入"最直接的提示，也省掉了常驻的灰底 —— 一页十几个框全是灰块会很重）。 */
input:not([type]):focus, input[type='text']:focus, input[type='password']:focus, input[type='number']:focus,
input[type='search']:focus, input[type='url']:focus, input[type='email']:focus, input[type='tel']:focus,
input[type='date']:focus, input[type='time']:focus, input[type='datetime-local']:focus,
select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  background-color: var(--bg-3);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }
input::placeholder, textarea::placeholder { color: var(--faint); }
input[type='number'] { font-variant-numeric: tabular-nums; }

/* ── 自定义下拉列表（ui/core/select-menu.js）──
   原生 <select> 的展开列表是系统控件，Windows 上是灰白方块、macOS 上是系统菜单，跟主题不搭。
   收起态仍是原生 select（值/焦点/disabled 都不动），展开时由 JS 弹这一层 listbox：
   位置按 select 的 rect 定，宽度至少与触发控件等宽，靠近视口底部时向上翻转。 */
.select-menu {
  position: fixed;
  z-index: 400;
  /* Popover 的 UA 默认样式会带 inset:0 / margin:auto；清掉，位置由 JS 的 left/top 定。 */
  inset: auto;
  margin: 0;
  box-sizing: border-box;
  max-width: min(360px, calc(100vw - 16px));
  max-height: 300px;
  overflow-y: auto;
  padding: 6px;
  background-color: var(--glass-modal-fill, var(--bg-2));
  background-image: var(--glass-grain, none);
  border: 1px solid var(--card-line, var(--border));
  border-radius: var(--r-card);
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  animation: selectMenuIn var(--dur-base) var(--ease-out-quart);
}
@keyframes selectMenuIn {
  from { opacity: 0; transform: translateY(-4px) scale(.985); }
  to { opacity: 1; transform: none; }
}
.select-option {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 7px 10px;
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: var(--fs-md);
  line-height: 1.45;
  cursor: pointer;
  user-select: none;
}
.select-option-label { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.select-option .select-check { margin-left: auto; color: var(--accent); opacity: 0; }
.select-option[aria-selected='true'] { color: var(--accent); background: var(--accent-soft); }
.select-option[aria-selected='true'] .select-check { opacity: 1; }
.select-option.is-active { background: var(--hover); }
.select-option[aria-selected='true'].is-active { background: var(--accent-soft); }
.select-option.is-disabled { opacity: .45; cursor: not-allowed; }
.select-group-label {
  padding: 8px 10px 4px;
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .2px;
}
html[data-motion='off'] .select-menu { animation: none; }

/* 单选/多选：自定义外观（原来直接用系统控件，深色下是系统蓝方块，和整页不是一套）。
   单选做成"实心圆点 + 内圈底色"，与开关的圆点同族。 */
.radio-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 12px;
  margin-bottom: 4px;
  border: 1px solid transparent;
  border-radius: var(--r-input);
  cursor: pointer;
  transition: background-color var(--dur-interaction) var(--ease-in-out),
              border-color var(--dur-interaction) var(--ease-in-out);
}
.radio-row input[type='radio'] {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 2px 0 0;
  border: 1.5px solid var(--ctl-line);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: border-color var(--dur-interaction) var(--ease-in-out),
              background-color var(--dur-interaction) var(--ease-in-out),
              box-shadow var(--dur-interaction) var(--ease-in-out);
}
.radio-row input[type='radio']:checked {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: inset 0 0 0 3px var(--bg-2);
}
.radio-row:has(input:checked) { background: var(--accent-soft); border-color: var(--accent-border); }
@media (hover: hover) and (pointer: fine) {
  .radio-row:hover:not(:has(input:checked)) { background: var(--hover); }
}
/* 句子里的内联数字（"我按渠道价：官方价 × [0.5]"）：跟着行高走，不撑成 40px 一行 */
.radio-row span input[type='number'] { height: 28px; padding: 0 8px; font-size: var(--fs-md); }

/* ══════════════════════════════════════════════════════════════════════════════
   丝滑度 v1（2026-10-08 第三轮）：覆层进出场 + 视图切换
   ──────────────────────────────────────────────────────────────────────────────
   实测参照实现（SnowLuma WebUI）后得到的结论：它的"丝滑"不来自更长的时长，恰恰相反 ——
   默认只有 **0.15s**、缓动是 `ease`/`ease-out`。它顺滑是因为**每一样覆层都有一套进出场**：

     @keyframes enter { from { opacity: var(--tw-enter-opacity,1);
                               transform: translate3d(…) scale3d(…);
                               filter: blur(var(--tw-enter-blur,0)); } }
     @keyframes exit  { to   { 同上，用 --tw-exit-* } }
     弹出层 = fade-in-0（0→1）+ zoom-in-95（.95→1）+ slide-in-from-*（位移）+ 200ms ease-out
     尺寸上：菜单/浮层 200ms、对话框 200ms、退场一律更快。

   反过来看我们这边缺什么（都是实测出来的）：
     · `<dialog>` 直接蹦出来，关掉是硬切 —— 全局三个对话框一点动画都没有
     · 切页（.view 的 display 切换）是硬切
     · 弹窗外壳用的是 260ms + `--ease-spring`（带过冲）—— 弹窗不该"弹"，那是玩具感
     · 没有一处用 blur（参照实现的进场带 2~3px 模糊，两个状态叠在一起时才像一个物体在变化）

   这一节按同一套语言补齐。时长刻意比原来的 token 短：丝滑感来自"每处都有"和"收尾干净"，
   不来自把动作放慢。
   ══════════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── 三级文字的最淡一档 ──
     由 --muted 与卡片底色按比例混合得到，而不是写死一个灰：色板切换、用户微调
     --muted/--bg-2 之后它会自动跟着走，不必十处一起改（原来五套色板 × 明暗各写一个值）。
     实测 WCAG 对比度（改了要重新量）：
       深色 88% → 卡片 5.62 / 面板 4.87 ；亮色 92% → 卡片 4.55 / 面板 4.09
     旧值是写死的 #6c7d99（深色 4.13）与 #8a99ad（亮色 2.90）—— 后者连 3:1 的图形门槛都不到，
     而这一档被用在 167 处（侧栏每项的说明行、· 分隔符、输入框占位符…）。 */
  --faint-mix: 88%;
  --faint: color-mix(in srgb, var(--muted) var(--faint-mix), var(--bg-2));
}
:root[data-theme='light'] { --faint-mix: 97%; }   /* 亮色下 muted 本身只有 5.4，留不出多少余量：
                                              取 97% 让占位符/说明行过 4.5，代价是与次级文字更接近 */

:root {
  /* 参照实现主题切换用的那条曲线（ease-out-expo）：起步快、收尾极慢，到位感最强 */
  --ease-out-expo: cubic-bezier(.16, 1, .3, 1);
  --dur-overlay: 200ms;        /* 进场 */
  --dur-overlay-out: 150ms;    /* 退场总是更快（人已经决定要走，别拖） */
  --shift-overlay: 6px;        /* 进场位移：参照实现是 slide-in 0.5rem ≈ 8px，取 6 更克制 */
  --blur-overlay: 3px;         /* 进场模糊：把"两层叠着"糊成一个物体在变 */
}

/* ── 1. <dialog>：进场 + 退场 ──
   这里**不用** `@starting-style` + `allow-discrete`：实测在 Chrome 里，对话框"打开"之后
   会一直停在起始态（opacity 0 / scale .97 / blur 3px），也就是弹窗永远不显示 —— 那套写法
   对 <dialog> 还不成熟。改用最可靠的两种手段：
     · 进场 = 挂在 `dialog[open]` 上的 keyframes。元素从 display:none 变成 block 时 CSS 动画
       会**重新开始**，正好是我们要的时机。
     · 退场 = closeDialog()（ui/core/dom-util.js）先挂 `.closing` 播一段 out 动画，
       动画结束再真正 close()。原生 close() 是瞬间 display:none，光靠 CSS 没有退场可言。 */
dialog[open] { animation: dialogIn var(--dur-overlay) var(--ease-out-expo); }
@keyframes dialogIn {
  from { opacity: 0; transform: translateY(calc(var(--shift-overlay) * -1)) scale(.97); filter: blur(var(--blur-overlay)); }
}
dialog[open]::backdrop { animation: backdropIn var(--dur-overlay) var(--ease-out-quart); }
@keyframes backdropIn { from { opacity: 0; } }
dialog.closing { animation: dialogOut var(--dur-overlay-out) var(--ease-out-quart) forwards; pointer-events: none; }
@keyframes dialogOut {
  to { opacity: 0; transform: translateY(4px) scale(.98); filter: blur(2px); }
}
dialog.closing::backdrop { animation: backdropOut var(--dur-overlay-out) var(--ease-out-quart) forwards; }
@keyframes backdropOut { to { opacity: 0; } }

/* ── 2. 视图切换（切页）：**不做动画** ──
   一开始我给 .view.active 加了一层 180ms 淡入（opacity 0→1）。实测（在会正常出帧的浏览器里
   逐帧采样）它是有害的：点击到动画真正开始之间有一段主线程同步工作（切视图 + 渲染 + 拉数据），
   而动画的 0% 是不透明 0 —— 于是那段时间用户看到的**是空白**。采样值：
     t=0/151/182/214ms 全为 opacity 0，t=248ms 才 0.98。
   也就是说它比"直接硬切"更糟：硬切至少立刻看到内容。
   参照实现这边也不给页面内容做入场动画（路由切换是即时的），所以这里保持硬切。

   2026-10-19 复核：用户要的是"切页有过渡"。照原来的测量结论，opacity 从 0 起会先白一帧 ——
   那就**只动 transform**：8px 上浮 + 0.4% 放大，320ms 收尾。内容从第一帧就是可见的，
   不会回到当年那个空白；位移也是 12px 以内，符合本文件开头的动效尺度。 */
.view.active { animation: viewIn var(--dur-slower) var(--ease-out-quart); }
@keyframes viewIn {
  from { transform: translateY(8px) scale(.996); }
  to { transform: none; }
}
html[data-motion='off'] .view.active { animation: none; }

/* ── 3. 弹窗外壳（.model-modal-overlay）──
   原来是 `fadeIn 180ms` + `popIn 260ms var(--ease-spring)`：进场带弹簧过冲（弹窗会"弹"一下），
   而且只有进场、没有退场。这里换成与参照实现同款的语言：进场 200ms ease-out-expo、退场 150ms、
   带 3px 模糊、去掉过冲。退场由 closeModelModal 加 `.closing`（见 ui/app.js）。 */
.model-modal-overlay { animation: overlayIn var(--dur-overlay) var(--ease-out-quart); }
@keyframes overlayIn { from { opacity: 0; } }
.model-modal { animation: modalIn var(--dur-overlay) var(--ease-out-expo); }
@keyframes modalIn {
  from { opacity: 0; transform: translateY(calc(var(--shift-overlay) * -1)) scale(.97); filter: blur(var(--blur-overlay)); }
}
.model-modal-overlay.closing { animation: overlayOut var(--dur-overlay-out) var(--ease-out-quart) forwards; pointer-events: none; }
@keyframes overlayOut { to { opacity: 0; } }
.model-modal-overlay.closing .model-modal { animation: modalOut var(--dur-overlay-out) var(--ease-out-quart) forwards; }
@keyframes modalOut {
  to { opacity: 0; transform: translateY(4px) scale(.98); filter: blur(2px); }
}

/* ── 4. 按 Esc / 点遮罩的退场也要走动画（同一套）──
   没有动画能力的环境（老浏览器 / 用户开了"关闭全部动效"）下，.closing 只是个透明层，
   所以 closeModelModal 里另有一条超时兜底，不会把节点卡在页面上。 */
html[data-motion='off'] .model-modal-overlay,
html[data-motion='off'] .model-modal,
html[data-motion='off'] dialog[open],
html[data-motion='off'] dialog.closing { animation: none; }
html[data-motion='off'] dialog.closing { display: none; }
html[data-motion='reduced'] .view.active { animation-duration: .01ms; }
@media (prefers-reduced-motion: reduce) {
  dialog[open], dialog.closing, .model-modal-overlay, .model-modal { animation-duration: .01ms; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   窄屏修正（2026-10-08）：把被"追加区块"架空的窄屏规则重新声明一遍
   ──────────────────────────────────────────────────────────────────────────────
   踩过的坑：侧栏 v2 那一块是**追加在文件末尾**的，而 `@media (max-width: 900px)`
   （L371）比它早三千多行 —— 同选择器、同权重，写在后面的赢，于是窄屏那一整块被架空。
   实测 820px 窗口：`#tabs` 仍是 `flex-direction: column`，侧栏变成 240×782 的竖列
   （比视口还高），正文被顶到 y=836 —— 打开控制台得先滚过一整屏导航才看得到内容。

   规则：给 `ui/style.css` 末尾追加区块时，凡是这批媒体查询里出现过的选择器
   （#sidebar / .side-head / #tabs / .tab / .side-foot …），要么写进
   `@media (min-width: 901px)`，要么在这里把窄屏取值再声明一次。改侧栏时这一节要同步。
   ══════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  #sidebar { width: 100%; padding: 10px 12px; gap: 6px; }
  /* 窄屏是"顶部一条可横滑的标签条"：滑块的位移只算 Y（宽屏那套），在这里会盖住整条导航 ——
     直接不用滑块，选中态回到"逐项底色 + 强调色文字"。 */
  .side-pill { display: none; }
  /* 选择器要与「侧栏样式」那一节的 `#sidebar .tab.active`（带 id）同权重，否则被它压回透明 */
  #sidebar .tab.active { background: var(--accent-side-soft); }
  .side-head { padding: 0 2px 4px; }
  /* 窄屏是"顶部一条可横滑的标签条"，不是竖列：不能继承上面的 column 与 width: 100% */
  #tabs {
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 6px;
    padding: 0 0 4px;
    overflow-x: auto;
    overflow-y: hidden;
  }
  .tab { width: auto; flex-shrink: 0; }
  .side-foot { margin-top: 0; padding-top: 4px; border-top: 0; }
  /* 窄屏这一条横着铺满整屏：不给下拉框限宽的话它会拉成几百像素宽（里面只有"观察/启用"两个字） */
  .side-foot .runtime-control { max-width: 200px; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   外观 v4（2026-10-09）：材质「液态玻璃」+ 折叠高度过渡 + 列表退场 + 触屏悬浮修正
   ──────────────────────────────────────────────────────────────────────────────
   这一批只做三件事，三条准则贯穿全节：
     · 不另起一套：能走 token 的一律走 token，绝不写死时长/曲线
       （console-motion.test.mjs 专门钉住“写死的兜底过渡会把整套 token 架空”这条）。
     · 尊重用户的选择：减弱/关闭动效、高对比、触屏，各自有对应降级，见各段。
     · 频率决定取舍：高频动作（切页签、删除行、键盘操作）不加动画；
       低频且“变化会让人眼睛丢掉位置”的（折叠展开、列表行消失）才给。
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. 材质轴：data-glass 不设（实心）/ frost（磨砂）/ liquid（液态玻璃）/ acrylic（亚克力）/ outline（描边） ──
   这一节被推翻重做过两轮，把结论写在最前面，免得再走回去：

   ▸ 第一版（半透明 + 模糊 + 顶部一大片白）：那是【毛玻璃 + Web 2.0 塑料高光】。
     塑料感的来源就是那条铺到 42%/14% 的顶部白渐变 —— 真玻璃的高光是**细、亮、贴边**的一条弧，
     不是一张盖在上半边的白纱。
   ▸ 第二版（把不透明度提上去、描边掺白）：卡片保住了层次，但变成"奶灰色卡片"——
     填充太实，玻璃就退化成"浅一点的矩形"，背景透不上来，等于白做。

   ▸ 现在这一版，三条同时成立才叫玻璃：
     ① 填充要**近乎通透**（liquid 只保留 48% 表面色）—— 背景必须真的看得见；
     ② 高光只在**边缘**，而且又细又亮（1.5px、白度 .5~.85）—— 这是"玻璃片"与"半透明色块"的分界；
     ③ 层次不靠填充色，靠**贴地阴影 + 环境阴影**把卡片从背景里抬起来。
     所以顶部的 sheen 层被整个删掉了（它只会带来塑料感），改由下面那组 inset 阴影承担高光。

   ▸ 五档靠"模糊度 + 通透度"两个维度区分，而不是都堆大模糊：
     磨砂＝真的糊（blur 22）；液态玻璃＝通透清晰、靠边（blur 1）；
     亚克力＝最糊（blur 56）+ 一层噪点；描边＝几乎不填色，只留边。

   ▸ 不透明度、描边强度、内辉光半径都是拿真控制台逐版截图比出来的（2026-10-09），
     不是在纸上推的。改这里的数值前请先看一眼真东西。 */

/* 每一档材质 = 一组 token；表面规则只写一次（见下），
   所以新增一档材质只需要加一个 token 块 + 在 appearance.js 的 GLASS 里加一项。 */
html[data-glass] {
  /* 模糊**很小** —— 这是照着社区那份实现（shuding/liquid-glass）改的：
     苹果那块玻璃是"透"的，不是"糊"的；糊起来立刻变成磨砂玻璃。
     真正营造玻璃感的是后面的 contrast/brightness/saturate 链与边缘。 */
  --glass-blur: 1px;
  --glass-sat: 130%;
  --glass-contrast: 1.18;
  --glass-bright: 1.05;
  --glass-tint: 74%;          /* 表面色保留多少（其余变透明）—— 越低越通透 */
  /* 填色本身也走 token：各档只改 --glass-tint，填色自动跟着算（custom property 的 var()
     在本元素上完成替换后再继承，所以这里写得一次就够）。侧栏那两种样式也只覆盖这一个 token。 */
  --glass-fill: color-mix(in srgb, var(--bg-2) var(--glass-tint), transparent);
  /* 模态框是**前景**，不是环境表面：它上面全是输入框与正文，背后透出页面文字会读成"没做完的
     半透明卡片"。单独一条更厚的填色，各材质只覆盖这一个 token。 */
  --glass-modal-fill: color-mix(in srgb, var(--bg-2) 92%, transparent);
  /* 描边色（颜色边）。必须是**单色**，不能是渐变 —— 见下面表面规则里那段说明：
     半透明填充下，background-clip: border-box 的渐变会把整张卡片铺白。 */
  --glass-edge: rgba(255, 255, 255, .20);
  /* 镜面高光（亮度边）：三条 inset 阴影拼出一圈"被上面的光打亮"的边。
     顶部最亮（光从上面来）、底部有回光（光绕着边绕过去）、左右很淡。
     这三条是这一版最关键的改动 —— 高级感几乎全在它们身上。 */
  --glass-spec-top: rgba(255, 255, 255, .50);
  --glass-spec-bot: rgba(255, 255, 255, .12);
  --glass-spec-side: rgba(255, 255, 255, .16);
  /* 壁厚：**底部一条暗 inset**（社区实现同款，`0 -10px 25px inset rgba(0,0,0,.15)`）。
     之前我用的是"一圈亮的辉光"，那是错的：玻璃的厚度读作"上亮下暗"——
     底边积着一层暗，才像一块有厚度的片材。 */
  --glass-inner: inset 0 -14px 30px -14px rgba(0, 0, 0, .50);
  --glass-grain: none;
  /* 阴影分两层：贴地一层（1px 的接触影）+ 环境一层（大而淡）。
     只有环境影会"飘"，只有贴地影会"贴死"，两层一起才既浮起又落在面上。 */
  --glass-shadow:
    0 1px 1px rgba(0, 0, 0, .28),
    0 14px 36px -10px rgba(0, 0, 0, .50);
}
/* 磨砂：真的"磨"—— 模糊最足、颜色收一点，像隔着磨砂玻璃看 */
html[data-glass='frost'] {
  --glass-sat: 115%;
  --glass-blur: 22px;          /* 磨砂这一档是**故意**糊的，它与液态玻璃的分工就是清晰度
                                  （2026-10-09 审查：上面原先还先声明过一次 26px，被这行覆盖成死代码，
                                  而测试恰好读的是那行死声明 —— 已删） */
  --glass-tint: 80%;
  --glass-bright: 1;
  --glass-spec-top: rgba(255, 255, 255, .40);
  --glass-inner: inset 0 -12px 24px -14px rgba(0, 0, 0, .42);
}
/* 液态玻璃：照参考实现（Kyant0/AndroidLiquidGlass 的 drawBackdrop 参数）来：
   · vibrancy() → saturate(150%)
   · blur(8dp) + lens(24dp, 24dp) → 8px 模糊 + 24px 折射带/位移
   · 表面色：暗色 #121212 @40%，浅色 #FAFAFA @40%（onDrawSurface drawRect(containerColor)）
   · Highlight 0.5dp（一条细亮线）、InnerShadow 24dp @15%、Shadow 24dp @10%
   它要透的是「壁纸」—— 见上面的 liquid 衬底规则；不是一层装饰渐变。 */
html[data-glass='liquid'] {
  --glass-blur: 12px;
  --glass-sat: 150%;
  --glass-contrast: 1;
  --glass-bright: 1;
  --glass-tint: 0%;
  --glass-fill: rgba(18, 18, 18, .42);
  --glass-modal-fill: rgba(18, 18, 18, .80);
  --glass-edge: transparent;
  --glass-spec-top: rgba(255, 255, 255, .90);
  --glass-spec-bot: rgba(255, 255, 255, .08);
  --glass-spec-side: rgba(255, 255, 255, .16);
  --glass-inner: inset 0 20px 24px -20px rgba(0, 0, 0, .28);
  --glass-grain: none;
  --glass-shadow:
    0 4px 24px rgba(0, 0, 0, .10),
    0 1px 2px rgba(0, 0, 0, .18);
}
/* 浮动玻璃壳：参考实现的底栏/控件都是"离边悬浮的胶囊"，不是贴着屏幕边缘的实心栏。
   只在液态玻璃档把左栏与顶栏从页面边缘浮起来；其余材质保持原来的贴边布局。 */
html[data-glass='liquid'] #shell { padding: 10px; gap: 10px; }
html[data-glass='liquid'] #sidebar { border-right: 0; border-radius: var(--r-float); }
/* 顶栏只装状态 chip，就让它缩成右上角的一条浮条 —— 拉满整行会是一大块空玻璃。 */
html[data-glass='liquid'] #topbar {
  border-bottom: 0;
  border-radius: var(--r-bar);
  margin-bottom: 8px;
  align-self: flex-end;
  width: max-content;
  max-width: 100%;
  padding: 6px 10px;
}
/* 设置页的正文是一整列表单：给它一层**不带 backdrop-filter** 的半透明纸面。
   不能把它加成玻璃表面 —— 里面的保存条自己就是玻璃，父子两层 backdrop-filter 会让
   子层采样到空的 backdrop root（2026-10-09 实测：整条发白、折射全丢）。 */
html[data-glass='liquid'] .settings-pane {
  background-color: color-mix(in srgb, var(--bg) 62%, transparent);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-sheet);
  box-shadow: var(--glass-shadow);
}
/* 大表面给更大的圆角：参考实现的卡片/弹层都不是小圆角（跟随用户的圆角档位缩放） */
html[data-glass='liquid'] .kpi-grid,
html[data-glass='liquid'] .panel,
html[data-glass='liquid'] .opt-card,
html[data-glass='liquid'] .list-pane,
html[data-glass='liquid'] .settings-sidebar { border-radius: var(--r-sheet); }
/* 参考实现的按钮是胶囊：液态玻璃档把按钮/图标按钮统一成 pill（输入框仍是圆角矩形 ——
   Coolapk 的输入框也不是胶囊，两者混用才像"控件"而不是"一堆药丸"）。 */
html[data-glass='liquid'] .btn,
html[data-glass='liquid'] .icon-btn { border-radius: var(--r-pill); }
/* 按压回弹：参考实现的按钮按下会缩到 ~0.94 再弹回（spring）。只给"真有鼠标"的设备。 */
@media (hover: hover) and (pointer: fine) {
  .btn:active:not(:disabled),
  .icon-btn:active:not(:disabled),
  .settings-menu-item:active,
  .seg button:active:not(:disabled) { transform: scale(.96); }
}
/* 亚克力：最糊 + 最实 + 一层噪点。灰阶中性，适合长时间盯着看。
   噪点的作用是打散大面积半透明的**色带**，不是装饰 —— 暗色底上色带很明显。 */
html[data-glass='acrylic'] {
  --glass-blur: 56px;
  --glass-sat: 100%;
  --glass-bright: 1;
  --glass-tint: 90%;
  --glass-edge: rgba(255, 255, 255, .14);
  --glass-spec-top: rgba(255, 255, 255, .34);
  --glass-spec-side: rgba(255, 255, 255, .12);
  --glass-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='.5'/%3E%3C/svg%3E");
  --glass-shadow:
    0 1px 1px rgba(0, 0, 0, .26),
    0 12px 32px -10px rgba(0, 0, 0, .44);
}
/* 描边玻璃：几乎不填色，靠一圈亮边把边界"划"出来。只在渐变/图片背景上成立，
   配纯色底会显得空 —— 所以照旧是选进来的，不是默认给的。 */
html[data-glass='outline'] {
  --glass-blur: 1px;
  --glass-sat: 150%;
  --glass-contrast: 1.14;
  --glass-tint: 22%;
  --glass-edge: rgba(255, 255, 255, .52);
  --glass-spec-top: rgba(255, 255, 255, .80);
  --glass-spec-side: rgba(255, 255, 255, .22);
  --glass-inner: inset 0 -12px 26px -16px rgba(0, 0, 0, .40);
}
/* 亮色：白表面上的"白色高光"看不见 —— 那里改用更亮的白边交代厚度；
   阴影也必须更轻，否则浅色下的卡片会显脏。
   2026-10-19：亮色壁纸的饱和色带比参考实现（照片壁纸）更宽、更艳，40% 白底会让同一组白卡
   在左边泛绿、右边泛蓝（插件页最明显：卡片底色一格格变）。亮色把填充提到 56% 并降一档
   vibrancy（150→125%），卡片回到中性白、插件图标自己的色相才读得准；暗色壁纸没有这个
   问题，保持在参考实现的 40% / 150%。 */
[data-theme='light'][data-glass='liquid'] {
  --glass-fill: rgba(250, 250, 250, .56);
  --glass-sat: 125%;
  /* 56% 的白玻璃在浅色壁纸上只够当卡片：做模态框时页面上的字会透进来（实测"添加人设"
     弹窗像一块没擦干净的毛玻璃）。前景提到 90%，保留一点透感但正文可读。 */
  --glass-modal-fill: rgba(250, 250, 250, .90);
}
/* 二级侧栏（会话/存档/记忆的列表栏、设置左栏）：它们的 sticky 表头是**不透明的**
   --bg-2 白条，56% 的玻璃底会把壁纸的绿色透上来 —— 上面一条白、下面一片绿，像两块拼的
   （2026-10-19 用户反馈"会话界面左侧边栏颜色违和"）。把这两块提到 84%，表头与列表底色
   只差几个色阶，整块读作一张白面板；壁纸仍留一点影子。主侧栏不跟 —— 它是导航轨，
   保持 56% 的透感。 */
html[data-theme='light'][data-glass='liquid'] .list-pane,
html[data-theme='light'][data-glass='liquid'] .settings-sidebar {
  --glass-fill: rgba(250, 250, 250, .84);
}
/* 上面两条把 --glass-fill 声明在元素自身上，会盖过 html 上的高对比规则（继承 vs 自身声明）；
   高对比要求这两块也回到不透明，所以用同一层选择器再声明一次。 */
html[data-theme='light'][data-contrast='high'][data-glass='liquid'] .list-pane,
html[data-theme='light'][data-contrast='high'][data-glass='liquid'] .settings-sidebar {
  --glass-fill: var(--bg-2);
}
[data-theme='light'][data-glass] {
  --glass-edge: rgba(255, 255, 255, .90);
  --glass-spec-top: rgba(255, 255, 255, 1);
  --glass-spec-bot: rgba(255, 255, 255, .55);
  --glass-spec-side: rgba(255, 255, 255, .70);
  --glass-inner: inset 0 0 12px -8px rgba(255, 255, 255, .85);
  --glass-shadow:
    0 1px 1px rgba(16, 24, 40, .06),
    0 12px 30px -10px rgba(16, 24, 40, .16);
  /* 卡片/表格的描边。--border (#e0e7f2) 贴白卡只有 1.24:1，亮壁纸上这些框几乎看不见
     （2026-10-19 用户反馈"浅色界面某些框的对比度不明显"）。与控件线同级（text 30%，
     约 2.3:1）；22% 实测只有 1.94:1，仍偏淡。只给"有边界、需要看出是个盒子"的表面；
     分节线仍用 --border。 */
  --card-line: var(--border);
}
@supports (color: color-mix(in srgb, red, blue)) {
  [data-theme='light'][data-glass] {
    --card-line: color-mix(in srgb, var(--text) 30%, var(--border));
  }
}
/* "有边界、要看出是个盒子"的表面统一走 --card-line；表格内部行线、面板分节线保持 --border。
   白玻璃边（--glass-edge）在这些盒子上贴白底看不见，所以在亮色玻璃下让位给这条描边。 */
[data-theme='light'][data-glass] .asset-summary-item,
[data-theme='light'][data-glass] .asset-table-wrap,
[data-theme='light'][data-glass] .context-call-table-wrap,
[data-theme='light'][data-glass] .control-service,
[data-theme='light'][data-glass] .control-key-list,
[data-theme='light'][data-glass] .usage-table,
[data-theme='light'][data-glass] .usage-card,
[data-theme='light'][data-glass] .usage-note,
[data-theme='light'][data-glass] .tool-card,
[data-theme='light'][data-glass] .kpi-grid,
[data-theme='light'][data-glass] .plugin-card,
[data-theme='light'][data-glass] .opt-card,
[data-theme='light'][data-glass] .plat-card,
[data-theme='light'][data-glass] .price-card,
[data-theme='light'][data-glass] .conversation-mode-option,
[data-theme='light'][data-glass] .persona-card,
[data-theme='light'][data-glass] .persona-detail,
[data-theme='light'][data-glass] .scheme-card,
[data-theme='light'][data-glass] .memory-summary,
[data-theme='light'][data-glass] .memory-advanced,
[data-theme='light'][data-glass] .memory-handoff,
[data-theme='light'][data-glass] .modal,
[data-theme='light'][data-glass] .model-modal,
[data-theme='light'][data-glass] .empty-hint,
[data-theme='light'][data-glass] .context-empty {
  border-color: var(--card-line);
}
/* 高对比模式：把不透明度拉满，可读性优先，材质让位 */
html[data-contrast='high'][data-glass] { --glass-tint: 97%; --glass-modal-fill: var(--bg-2); }
[data-theme='light'][data-contrast='high'][data-glass] { --glass-tint: 100%; }
/* 液态档的填色是写死的 rgba，不读 --glass-tint —— 上面两条对它无效（默认材质就是液态，
   等于高对比对默认用户失效）。这里直接把 --glass-fill 拉满。 */
html[data-contrast='high'][data-glass='liquid'] {
  /* 高对比要的就是不透明；直接给实色，不依赖 color-mix。 */
  --glass-fill: var(--bg-2);
  --glass-modal-fill: var(--bg-2);
}
[data-theme='light'][data-contrast='high'][data-glass='liquid'] { --glass-fill: var(--bg-2); --glass-modal-fill: var(--bg-2); }

/* 玻璃表面：一条规则覆盖全部"大表面"。所有档位差异都走上面的 token，
   所以新增一档材质不用动这里。

   ⚠️ 嵌套是这里最容易踩的坑：带 backdrop-filter 的元素会成为新的 backdrop root，
   它的子元素再上 backdrop-filter 时，采样到的是**空的**背景根 —— 表现出来就是
   "子层莫名其妙发白、文字对比度垮掉"。2026-10-09 在真控制台上实测到的就是这件事
   （.settings-sidebar 里又有 .settings-menu、.panel 里又有 .collapsible）。
   所以下面这份清单只收"最外层的表面"，绝不同时收父子两层。
   新增层前先问一句：它里面已经有别的玻璃层了吗？有就不该进来。

   ▸ 描边**不**能用「透明 border + background-clip: border-box 的渐变」那套常见技法：
     那个技法只在填充不透明时成立（靠上面不透明的 padding-box 层把中间盖住，只漏出 1px 环）。
     我们的填充是半透明的，于是那条渐变会**铺满整张卡片** —— 表现就是"顶部一大片白"，
     也就是这一节开头说的第一版塑料感。2026-10-09 实测踩到，记在这里免得有人再改回去。
     正确做法：实心 border（单色）+ 顶部一条 inset 亮弧。环形本来就是 inset 阴影的天然形状，
     而且"上亮下隐"由 --glass-spec-top / --glass-spec-bot 两条分别给，比渐变边更可控。 */
html[data-glass] #topbar,
html[data-glass] #sidebar,
html[data-glass] .panel,
html[data-glass] .kpi-grid,
html[data-glass] .opt-card,
html[data-glass] .usage-card,
html[data-glass] .plat-card,
html[data-glass] .tool-card,
html[data-glass] .settings-sidebar,
html[data-glass] .model-modal,
html[data-glass] .modal,
html[data-glass] dialog,
html[data-glass] .list-pane {
  border: 1px solid var(--glass-edge);
  background-color: var(--glass-fill);
  background-image: var(--glass-grain);
  background-clip: padding-box;
  /* var(--glass-refract, ) 的**空回退**是关键：没开折射时它替换成「什么都没有」，
     整条值仍然合法；开了则变成 url(#…) 打头。JS 只设这一个变量（见 ui/core/glass-refract.js），
     不必重写整条 backdrop-filter，也就不会把材质档的 token 冲掉。 */
  backdrop-filter: var(--glass-refract, ) blur(var(--glass-blur)) saturate(var(--glass-sat))
    contrast(var(--glass-contrast)) brightness(var(--glass-bright));
  -webkit-backdrop-filter: var(--glass-refract, ) blur(var(--glass-blur)) saturate(var(--glass-sat))
    contrast(var(--glass-contrast)) brightness(var(--glass-bright));
  /* 三条 inset 拼出高光圈，再加一圈内辉光当壁厚。顺序有讲究：
     先画外阴影，再自上而下压暗/提亮，最后才是整圈的内辉光。 */
  box-shadow:
    var(--glass-shadow),
    inset 0 1.5px 0 var(--glass-spec-top),
    inset 0 -1px 0 var(--glass-spec-bot),
    inset 1px 0 0 var(--glass-spec-side),
    inset -1px 0 0 var(--glass-spec-side),
    var(--glass-inner);
}
/* 前景表面（模态框、原生 <dialog>）：填色比环境面板更厚。上面的表面组先把它们拉进同一套
   边框/滤镜里，这里只换填色 —— 规则必须写在组之后，否则同特异性会被组里的 --glass-fill 盖掉。 */
html[data-glass] .model-modal,
html[data-glass] .modal,
html[data-glass] dialog {
  background-color: var(--glass-modal-fill);
}
/* ── 边缘折射带（只给液态玻璃）──
   苹果那套 Liquid Glass 最关键的、也是我前几版**完全没做**的一件事：折射。
   它把背后射过来的光在边缘弯折 —— 所以一条直线从玻璃下面穿过时，到边上会"拐一下"。

   试过用 feDisplacementMap 做整块位移（backdrop-filter: url(#滤镜)）。结论：
   ▸ 位移确实生效了，但**形状是错的** —— 它是"整块被抹开"，而苹果的折射**只发生在边缘那一圈**，
     中间是完全不动的；而且线性位移图是按元素宽高比拉伸的，宽扁的卡片上会明显不均匀。
   ▸ 所以那条路没走。现在换成一条真正只在边缘生效的折射带：
     一个铺满卡片、但用 mask 只留下外圈 ~11px 的伪元素，让它**自己**再对背景采样一次
     （更亮、更饱和、轻微再模糊）—— 那一圈看起来就像一块有厚度的玻璃边把背后的光吸过来。
   ▸ 关键收益：中间的内容区完全不受影响，所以文字永远不会被拉变形。

   ⚠️ 需要宿主是定位元素。下面这份清单只收确认过是 static 的；
      #sidebar / .settings-sidebar 可能已被别的区块设成 sticky，不动它们（宁缺勿破）。
      dialog **不在**清单里：它自己就是绝对定位元素（modal 时还在 top layer，作者样式设
      position 会被规范调整），::after 的包含块本来就有 —— 2026-10-09 审查实测过，
      加 position: relative 是纯冗余，还容易误导下一个人。
   ⚠️ 伪元素会盖在内容之上（pointer-events: none 不挡点击）。所以环带宽度必须小于
      卡片的内边距 —— 否则它会盖到正文上，看起来像文字外面蒙了一层灰。11px 是照
      .opt-card / .panel 的 14~16px 内边距留的余量。 */
html[data-glass='liquid'] {
  --glass-lens: 11px;
}
html[data-glass='liquid'] #topbar,
html[data-glass='liquid'] .kpi-grid,
html[data-glass='liquid'] .opt-card,
html[data-glass='liquid'] .usage-card,
html[data-glass='liquid'] .plat-card,
html[data-glass='liquid'] .tool-card,
html[data-glass='liquid'] .model-modal,
html[data-glass='liquid'] .list-pane {
  position: relative;
}
/* 2026-10-09 复核：表面清单早已改 .kpi-grid（玻璃加在整行），环清单当时漏跟 —— 结果是每格
   一个环、嵌在整行表面的 backdrop-filter 里（正是本节开头要防的嵌套）；现已同步。
   .panel 不再挂环：它是"细线分节"、横向内边距为 0（padding: 14px 0），11px 的环带会压在文字上。 */
html[data-glass='liquid'] #topbar::after,
html[data-glass='liquid'] .kpi-grid::after,
html[data-glass='liquid'] .opt-card::after,
html[data-glass='liquid'] .usage-card::after,
html[data-glass='liquid'] .plat-card::after,
html[data-glass='liquid'] .tool-card::after,
html[data-glass='liquid'] .model-modal::after,
html[data-glass='liquid'] dialog::after,
html[data-glass='liquid'] .list-pane::after {
  content: '';
  position: absolute;
  inset: 0;
  padding: var(--glass-lens);
  border-radius: inherit;
  pointer-events: none;
  backdrop-filter: blur(2px) saturate(220%) brightness(1.35) contrast(1.12);
  -webkit-backdrop-filter: blur(2px) saturate(220%) brightness(1.35) contrast(1.12);
  /* 两层同一块黑，靠 mask-composite: exclude 做差集 —— 只留 padding 那一圈 */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
/* 关闭全部动效时不动它（它是静态的，但省掉一层滤镜开销） */
html[data-motion='off'][data-glass='liquid'] #topbar::after,
html[data-motion='off'][data-glass='liquid'] .kpi-grid::after,
html[data-motion='off'][data-glass='liquid'] .opt-card::after,
html[data-motion='off'][data-glass='liquid'] .usage-card::after,
html[data-motion='off'][data-glass='liquid'] .plat-card::after,
html[data-motion='off'][data-glass='liquid'] .tool-card::after,
html[data-motion='off'][data-glass='liquid'] .model-modal::after,
html[data-motion='off'][data-glass='liquid'] dialog::after,
html[data-motion='off'][data-glass='liquid'] .list-pane::after {
  display: none;
}

/* 液态玻璃不再叠一圈"发亮的模糊环带"：苹果/社区的玻璃边缘是折射 + 一条镜面高光，
   不是一块发光磨砂环。边缘交给 SDF 折射与顶部 inset 高光，环带只会把它做成塑料边。 */
html[data-glass='liquid'] #topbar::after,
html[data-glass='liquid'] .kpi-grid::after,
html[data-glass='liquid'] .opt-card::after,
html[data-glass='liquid'] .usage-card::after,
html[data-glass='liquid'] .plat-card::after,
html[data-glass='liquid'] .tool-card::after,
html[data-glass='liquid'] .model-modal::after,
html[data-glass='liquid'] dialog::after,
html[data-glass='liquid'] .list-pane::after {
  display: none;
}

.opt-row[data-inert='1'] { opacity: .5; }
/* 折射开关在材质=实心时置灰（.opt-row[data-inert]），这里只负责它的观感：
   置灰靠 opacity + 原生 disabled 的 cursor；不加这一条也"能看懂"，但看着像坏掉的开关。 */
.opt-row[data-inert='1'] .opt-row-ctrl { cursor: not-allowed; }

/* ── 顶栏脱离文档流：所有页签的面板与主侧栏共用同一条顶线 ──
   顶栏只装一排状态 chip、右对齐，本来就是"浮在内容上的胶囊"；但它一直占着文档流的一行，
   于是所有正文都被压下去 64px。设置页最扎眼：主侧栏从 10px 起，设置左栏和正文从 74px 起，
   两条左缘错开一整行（2026-10-19 用户反馈"显得生硬"）。
   让它绝对定位，正文容器把顶部让出 88px（顶栏 54 + 上下留白）——**面板本身**从顶线开始，
   内容在顶栏下面。
   ⚠️ 两个边界（2026-10-19 审查）：
   ① 顶栏宽了会压住左侧的列表栏/设置左栏 —— 用 max-width 把它限制在右侧，
      给左边留 348px（列表 320 + 间距 / 设置左栏 220）；窗口 1280、侧栏展开时实测
      原本会压进列表 98px。chip 本身可收缩 + 省略，不会撑破。
   ② ≤900px 是"侧栏在上、内容全宽"的堆叠布局：列表栏整行、没有 88px 让位，顶栏必须回到
      文档流里，否则一定盖住列表头。整块只在 ≥901px 生效。 */
#workspace { position: relative; }
@media (min-width: 901px) {
  html[data-glass='liquid'] #topbar {
    position: absolute;
    top: 8px;
    right: 8px;
    margin: 0;
    z-index: 40;
    max-width: calc(100% - 348px);
  }
  html[data-glass='liquid'] .overview-pane,
  html[data-glass='liquid'] .control-pane,
  html[data-glass='liquid'] .asset-pane,
  html[data-glass='liquid'] .usage-pane,
  html[data-glass='liquid'] .plugins-pane,
  html[data-glass='liquid'] .detail-pane,
  html[data-glass='liquid'] #view-settings .settings-pane { padding-top: 88px; }
  /* 顶栏脱离流后，横幅会顶到 workspace 的最上沿、右半截被顶栏盖住 —— 让它从顶栏下面开始 */
  html[data-glass='liquid'] #banner:not(.hidden) { margin-top: 72px; }
}

/* 侧栏样式只换"填色"这一个 token，整块背景与描边不用重写 */
html[data-glass][data-sidebar-style='panel'] #sidebar {
  --glass-fill: color-mix(in srgb, var(--bg-3) var(--glass-tint), transparent);
}
html[data-glass][data-sidebar-style='accent'] #sidebar {
  --glass-fill: color-mix(in srgb, var(--accent-side) 12%,
    color-mix(in srgb, var(--bg-2) var(--glass-tint), transparent));
}
/* 液态玻璃的 --glass-tint 是 0（填色由写死的 --glass-fill 直接给），上面两条会算出
   "100% 透明"——默认材质改成液态玻璃后，"浅色面板"会变成一块没有底的侧栏。
   这里给液态档单独一份保留比例：浅色同 56%、暗色同 42%（与该档的 --glass-fill 对齐）。 */
html[data-glass='liquid'][data-sidebar-style='panel'] #sidebar {
  --glass-fill: color-mix(in srgb, var(--bg-3) 56%, transparent);
}
[data-theme='dark'][data-glass='liquid'][data-sidebar-style='panel'] #sidebar {
  --glass-fill: color-mix(in srgb, var(--bg-3) 42%, transparent);
}
html[data-glass='liquid'][data-sidebar-style='accent'] #sidebar {
  --glass-fill: color-mix(in srgb, var(--accent-side) 14%,
    color-mix(in srgb, var(--bg-2) 56%, transparent));
}
[data-theme='dark'][data-glass='liquid'][data-sidebar-style='accent'] #sidebar {
  --glass-fill: color-mix(in srgb, var(--accent-side) 14%,
    color-mix(in srgb, var(--bg-2) 42%, transparent));
}
/* 不支持 backdrop-filter（老浏览器 / 部分 Linux 桌面浏览器）：退回实心表面。
   不退回的话，半透明底会直接糊在背景渐变上，文字对比度跟着掉 —— 宁可不玻璃。 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass] #topbar,
  html[data-glass] #sidebar,
  html[data-glass] .panel,
  html[data-glass] .kpi-grid,
  html[data-glass] .opt-card,
  html[data-glass] .usage-card,
  html[data-glass] .plat-card,
  html[data-glass] .tool-card,
  html[data-glass] .settings-sidebar,
  html[data-glass] .model-modal,
  html[data-glass] .modal,
  html[data-glass] dialog,
  html[data-glass] .list-pane {
    border-color: var(--border);
    background-image: none;
    background-color: var(--bg-2);
    box-shadow: var(--shadow-card, none);
  }
}

/* ── 2. 折叠面板（<details class="collapsible">）：展开与收起都走高度过渡 ──
   原状：只给 [open] 的子元素一条淡入 —— 展开有动效、收起“啪”地消失，
   而且高度完全不参与过渡，所以下面一整块内容是硬顶上来的。
   现在给内容盒（::details-content，Chrome 131+ / Safari 18.4+）做 block-size 0 ↔ auto。
   interpolate-size: allow-keywords 是让“auto”可以参与过渡的开关（默认 auto 不可插值）。
   不支持这两个特性的浏览器保持“直切” —— 也就是原本的行为，不算回归。
   content-visibility 必须跟 allow-discrete 一起写：收起时内容要留到过渡结束才隐藏。 */
:root { interpolate-size: allow-keywords; }
.collapsible::details-content {
  block-size: 0;
  overflow: hidden;
  opacity: 0;
  transition:
    block-size var(--dur-slow) var(--ease-out-quart),
    opacity var(--dur-base) var(--ease-in-out),
    content-visibility var(--dur-slow) allow-discrete;
}
.collapsible[open]::details-content {
  block-size: auto;
  opacity: 1;
}

/* ── 3. 列表行的退场：先塔缩再移除 ──
   行被删掉时下面的内容会瞬间上跳 —— 一眼就能看出“这一块在重排”。
   高度由 JS 量好写进 --leave-h（这里不能写 height: auto，插值不了），
   padding/margin/border 一并归零，否则高度到 0 了还留着一圈空隙。
   时长与 ui/core/dom-util.js 的 LEAVE_MS 必须一致：那里等的就是这条动画。
   注：这里确实在动 height（布局属性），而不是一个便宜的 transform ——
   但“塔缩”不给高度就没有意义，而且它只在删行时跑一次、列表很短，代价可以接受。 */
.session-item.leaving,
.chat-item.leaving {
  overflow: hidden;
  height: var(--leave-h, auto);
  pointer-events: none;
  animation: rowOut var(--dur-slow) var(--ease-out-quart) forwards;
}
@keyframes rowOut {
  to {
    height: 0;
    opacity: 0;
    transform: translateX(-6px);
    padding-top: 0;
    padding-bottom: 0;
    margin-top: 0;
    margin-bottom: 0;
    border-top-width: 0;
    border-bottom-width: 0;
  }
}
/* 减弱/关闭动效时不塔缩（也不延时删除：JS 那边看到 data-motion 就直接移除） */
html[data-motion='reduced'] .session-item.leaving,
html[data-motion='reduced'] .chat-item.leaving,
html[data-motion='off'] .session-item.leaving,
html[data-motion='off'] .chat-item.leaving { animation: none; }

/* ── 4. 悬浮位移只在“真有鼠标”的设备上生效 ──
   触屏上 tap 会先触发一次 hover，列表行会横着抽一下、卡片会跳一下。
   （.kpi 那一条原本就是这个口径，这几条是同一类漏网 —— 收口到一起。） */
@media (hover: hover) and (pointer: fine) {
  .persona-card:hover { transform: translateY(-1px); }
  .usage-card.accent:hover { transform: translateY(-2px); }
  .session-item:hover,
  .chat-item:hover { transform: translateX(2px); }
  .tier-slider::-webkit-slider-thumb:hover { transform: scale(1.15); }
}
/* 触屏上把这些位移显式压回 0：有些移动浏览器支持 hover 又同时是触屏，光靠上面那条遮不住 */
@media (hover: none) {
  .persona-card:hover,
  .usage-card.accent:hover,
  .session-item:hover,
  .chat-item:hover { transform: none; }
}

/* ── 5. KPI 数字滚动：等宽数字 ──
   计数动画（ui/pages/overview.js）每帧改文本，不等宽的话数字宽度会抖、整块跟着微移。
   比例字体下的 tabular-nums 只影响数字字形，不影响中文，所以全站 KPI 都开着。 */
.kpi-value { font-variant-numeric: tabular-nums; }
/* ══════════════════════════════════════════════════════════════
   插件页（第五版，2026-10-16 夜）
   ══════════════════════════════════════════════════════════════
   前四版一直在"卡片长什么样"上打转，版式本身还是设置页：已装台账压在目录上面、标签挤成横向一行、
   没有分面栏。这一版按参照（Obsidian 社区插件目录 / VS Code 市场 / Raycast 商店）**量出来**的
   尺寸重排：

     ① **市场就是这一页**：目录 / 已装 / 手动安装 三个面用页签切换（HACS 的 Installed/Available
        就是这一手），目录是默认面 —— 管理动作不再压在看货之前；
     ② **左侧分面栏**（Obsidian 版式的核心）：范围（全部/已装/有更新）、分类、标签、其他，
        **每项带数量** —— 数量回答"点进去有没有东西"，也把标签从横排 chip 墙变成一竖栏；
     ③ **主区一条工具栏**：搜索 + 列表/卡片切换 + 排序（Obsidian 也有这两档视图）；
     ④ **卡片压扁**：图标 + 名字 + 两行说明，名字 15px、说明 13.5px，行高 1.6（参照是 16/16、
        行高 24px，但我们是在一个 13-14px 的 console 里，等比上抬而不是照抄）。

   保留的是这一页自己的东西：状态轨（活着没有）、两条风险标记、相对时间、详情两栏与相关插件。
*/
.plugins-pane { flex: 1; min-width: 0; overflow-y: auto; padding: 22px 26px 40px; }
/* 整页内容限宽并居中：1920 屏上原来页头/卡片都贴着左边，右边空出 750px ——
   用户 2026-10-17 的「内容全堆在左侧」就是这个。与「用量」页的 .usage-wrap 同一套口径。 */
.plugins-pane > * { max-width: 1200px; margin-inline: auto; }
[data-density='compact'] .plugins-pane { padding: 15px 17px 22px; }
[data-density='roomy'] .plugins-pane { padding: 26px 30px 34px; }

/* 滚动条宽 10px（见文件末尾的 ::-webkit-scrollbar 口径）。内容够长的页面会因此比不够长的
   页面少 10px 可用宽度 —— 同一列卡片在切页时右缘横跳。stable 让每个主滚动容器都**始终**
   预留同一条沟：有没有滚动条，内容的左右缘都一样。（列表/详情/设置正文早就是这条口径。） */
.overview-pane,
.control-pane,
.asset-pane,
.usage-pane,
.plugins-pane,
#view-settings { scrollbar-gutter: stable; }

/* ── 页头：标题 + 信任声明在左，总开关与隔离说明收在右（admin 不占市场的位置）── */
.plugin-page-head { display: flex; align-items: flex-start; gap: 24px; margin-bottom: 18px; }
.plugin-page-head > div:first-child { min-width: 0; flex: 1; }
.plugins-pane .pane-head h2 { font-size: var(--fs-2xl); }
.plugin-claim { margin-top: 8px; max-width: 68ch; font-size: var(--fs-sm); color: var(--muted); line-height: 1.75; }
.plugin-claim b { color: var(--text); font-weight: 600; }
.plugin-master { flex: 0 0 auto; max-width: 320px; margin-top: 4px; }
.plugin-master .checkbox-row { margin-bottom: 2px; }
.plugin-master .hint { font-size: var(--fs-2xs); line-height: 1.6; }
/* 索引状态那行：比隔离说明再淡一点 —— 它说的是「数据从哪来」，不是安全提示 */
.plugin-source-chip { margin-top: 7px !important; color: var(--faint); }

/* ── 页签：目录 / 已装 / 手动安装（HACS 的 Installed-Available 那一手）──
   下边框一条线，选中项用强调色下划线 —— 与主导航的滑块不同，这里是"页内二级页签"。 */
.plugin-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 18px; }
/* 窄屏（320px）时四个页签 + 「发布插件」放不进一行：允许**整块换行**，而不是把词拆开
   或让整页横向滚动（`white-space: nowrap` 之后必须配 wrap，否则 flex 不会收缩）。 */
.plugin-tab {
  position: relative; background: none; border: 0; cursor: pointer; font: inherit; white-space: nowrap;
  font-size: var(--fs-md); color: var(--muted); padding: 8px 14px 10px;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--dur-interaction) var(--ease-in-out), border-color var(--dur-interaction) var(--ease-in-out);
}
.plugin-tab:hover { color: var(--text); }
.plugin-tab.is-on { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }
.plugin-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
.plugin-tab-n { margin-left: 7px; font-family: var(--mono); font-size: var(--fs-2xs); color: var(--faint); }
.plugin-tab.is-on .plugin-tab-n { color: var(--accent); }
/* 「发布插件」：作者入口做成**右侧的强调按钮**（不是第四个页签）。
   第一版把它写成同款页签、只靠一条发丝线分开，用户反馈"不够显眼" —— 页签的语义是
   "切换视图"，读者会把它当成第四个筛选面。所以这里换一套形状：胶囊 + 描边 + 强调色，
   一眼分得清"左边是看什么、右边是去做什么"。 */
.plugin-publish-cta {
  margin-left: auto; align-self: center; margin-bottom: 6px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: var(--r-pill); cursor: pointer; font: inherit;
  font-size: var(--fs-sm); font-weight: 600; white-space: nowrap;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid var(--accent-border);
  transition: background var(--dur-interaction) var(--ease-in-out), border-color var(--dur-interaction) var(--ease-in-out);
}
.plugin-publish-cta svg { flex: 0 0 auto; }
.plugin-publish-cta.is-on { color: #fff; background: var(--accent); border-color: var(--accent); }
.plugin-publish-cta:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .plugin-publish-cta:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); border-color: var(--accent); }
  .plugin-publish-cta.is-on:hover { background: var(--accent); opacity: .92; }
}
.plugin-pane-head { margin-bottom: 12px; }

/* ── 两栏：左分面栏 + 右主区（Obsidian 的版式）── */
.plugin-layout { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 26px; align-items: start; }
/* 详情占满主区时切成单列：分面栏会被隐藏，而"网格项消失"会让主区落到第一列被挤成 190px 宽
   （2026-10-16 用户实测"详情页全挤一块"就是这个）。所以隐藏分面栏的同时必须改列定义。 */
.plugin-layout.is-detail { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 900px) { .plugin-layout { grid-template-columns: 1fr; gap: 14px; } }
/* 窄屏的筛选：分面栏**默认收起**，用工具栏上的「筛选」按钮展开。
   不收的话整条筛选（范围/分类/标签/其他）会堆在卡片上方，把插件列表推出屏幕 ——
   这正是 2026-10-17 用户说的"标签放在插件上面对吗"。参照（VS Code 市场窄屏）也是收进按钮。 */
.plugin-filter-toggle { display: none; }
@media (max-width: 900px) {
  .plugin-filter-toggle { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; }
  .plugin-filter-toggle.is-on { border-color: var(--accent-border); color: var(--accent); }
  /* ⚠️ 选择器要带 `.plugin-layout`：基础规则 `.plugin-facets { display: flex }` 写在本块**后面**，
     同权重下后出现的赢 —— 只用 `.plugin-facets` 会被它顶回来，窄屏照样堆在卡片上方
     （2026-10-17 实测：railHidden=false、gridTop=988）。 */
  .plugin-layout .plugin-facets { display: none; }
  .plugin-layout.is-filters-open .plugin-facets {
    display: flex; position: static; margin-bottom: 4px; padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
  }
}
.plugin-facets { position: sticky; top: 0; display: flex; flex-direction: column; gap: 16px; }
.plugin-facet-group { display: flex; flex-direction: column; gap: 1px; }
/* 组标题与参照同款：小字、弱色、留出左边距（Obsidian 就是 TYPE / CATEGORIES / PAYMENTS 那种） */
.plugin-facet-label { font-size: var(--fs-2xs); color: var(--faint); padding: 0 8px 5px; letter-spacing: .02em; }
.plugin-facet {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: none; border: 0; cursor: pointer; font: inherit; font-size: var(--fs-sm); color: var(--muted);
  padding: 6px 12px; border-radius: var(--r-sm); text-align: left; min-height: 28px;
  transition: background-color var(--dur-interaction) var(--ease-in-out), color var(--dur-interaction) var(--ease-in-out);
}
.plugin-facet:hover { background: var(--bg-3); color: var(--text); }
.plugin-facet.is-on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.plugin-facet:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.plugin-facet-n { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--faint); }
.plugin-facet.is-on .plugin-facet-n { color: var(--accent); }
.plugin-facet-clear { justify-content: flex-start; color: var(--accent); }

/* ── 主区工具栏：搜索（左）+ 视图切换 + 排序（右）── */
.plugin-main { min-width: 0; }
.plugin-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.plugin-toolbar #plugins-search { flex: 1; min-width: 140px; font-size: var(--fs-sm); }
.plugin-viewtoggle { display: inline-flex; border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.plugin-view {
  display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 28px;
  background: none; border: 0; cursor: pointer; color: var(--faint);
  transition: background-color var(--dur-interaction) var(--ease-in-out), color var(--dur-interaction) var(--ease-in-out);
}
.plugin-view + .plugin-view { border-left: 1px solid var(--border); }
.plugin-view:hover { background: var(--bg-3); color: var(--text); }
.plugin-view.is-on { background: var(--bg-3); color: var(--accent); }
.plugin-sort { max-width: 150px; }
/* 索引的两块（原来是压在卡片上方的灰色提示行，用户反馈"在上面很难受"）：
   · 顶部只留"这份目录不可信"的告警（拉取失败、正在用缓存）—— 它必须第一眼看见；
   · 来源与条数挪到列表**尾部**做落款，字号更小、颜色更淡，像页脚而不像提示。 */
.plugin-index-warn {
  margin-bottom: 12px; padding: 9px 12px;
  border: 1px solid rgba(247, 185, 85, .35); border-radius: var(--r-md);
  background: color-mix(in srgb, var(--orange) 10%, transparent);
  font-size: var(--fs-sm); line-height: 1.65;
}
.plugin-index-foot {
  margin-top: 16px; padding-top: 10px; border-top: 1px solid var(--border);
  font-size: var(--fs-2xs); color: var(--faint); line-height: 1.7;
}
.plugin-index-foot code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--muted); }

/* ── 图标块：按 id 生成的色相（这一版没变）── */
.plugin-mono {
  flex: 0 0 auto; width: 38px; height: 38px; display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: var(--fs-lg); font-weight: 650; line-height: 1;
  border-radius: var(--r-md);
  background: linear-gradient(155deg, hsl(var(--tile-h, 220) 44% 30%), hsl(var(--tile-h, 220) 52% 20%));
  border: 1px solid hsl(var(--tile-h, 220) 40% 40% / .55);
  color: hsl(var(--tile-h, 220) 78% 84%);
  box-shadow: inset 0 1px 0 hsl(var(--tile-h, 220) 60% 70% / .18);
}
[data-theme='light'] .plugin-mono {
  background: linear-gradient(155deg, hsl(var(--tile-h, 220) 72% 92%), hsl(var(--tile-h, 220) 66% 84%));
  border-color: hsl(var(--tile-h, 220) 50% 78%);
  color: hsl(var(--tile-h, 220) 45% 34%);
}
.plugin-mono.lg { width: 52px; height: 52px; font-size: var(--fs-2xl); border-radius: var(--r-card); }
.plugin-mono img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; display: block; }
.plugin-mono.is-off { filter: saturate(.35) brightness(.75); }
[data-theme='light'] .plugin-mono.is-off { filter: saturate(.4); }

.plugin-ver { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }
.plugin-fact { font-family: var(--mono); font-size: var(--fs-xs); color: var(--faint); }
.plugin-name { font-size: var(--fs-md); font-weight: 600; color: var(--text); }
/* 卡面上的小标签（分类 / 已弃用 / 风险）——比 chip 轻，只描边不上底 */
.plugin-tag {
  font-size: var(--fs-2xs); color: var(--muted); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 0 5px; white-space: nowrap;
}
.plugin-tag.is-warn { color: var(--orange); border-color: rgba(247, 185, 85, .4); }
.plugin-tag.is-danger { color: var(--red); border-color: rgba(255, 107, 107, .4); }
.plugin-avatar {
  width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 600;
  background: linear-gradient(155deg, hsl(var(--tile-h, 220) 40% 32%), hsl(var(--tile-h, 220) 48% 22%));
  color: hsl(var(--tile-h, 220) 70% 82%);
  border: 1px solid var(--border);
}

/* ── 已装：台账 ── */
.plugin-ledger { border-top: 1px solid var(--border); }
.plugin-row { position: relative; display: flex; gap: 14px; padding: 14px 14px 14px 18px; border-bottom: 1px solid var(--border); }
.plugin-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.plugin-row-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.plugin-row[role='button'] { cursor: pointer; }
@media (hover: hover) and (pointer: fine) { .plugin-row[role='button']:hover { background: var(--bg-2); } }
.plugin-row[role='button']:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--r-sm); }
.plugin-row-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.plugin-act { background: none; border: 0; padding: 2px 0; font: inherit; font-size: var(--fs-sm); color: var(--muted); cursor: pointer; }
.plugin-act:hover { color: var(--text); text-decoration: underline; }
.plugin-act.is-danger:hover { color: var(--red); }
.plugin-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
.plugin-act:disabled { opacity: .5; cursor: not-allowed; }

/* 状态轨：3px 竖条，只表达"活着没有" */
.plugin-row::before, .plugin-card::before {
  content: ''; position: absolute; left: 0; top: 14px; bottom: 14px; width: 3px;
  border-radius: var(--r-pill); background: transparent;
  transition: background-color var(--dur-interaction) var(--ease-in-out);
}
.plugin-state-running::before { background: var(--green); }
.plugin-state-idle::before { background: var(--accent); }
.plugin-state-off::before { background: var(--border); }
.plugin-state-failed::before { background: var(--red); }
.plugin-state-deprecated::before { background: var(--orange); }

.plugin-state-text { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-sm); color: var(--muted); }
.plugin-state-text i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.plugin-state-text.running { color: var(--green); }
.plugin-state-text.idle { color: var(--accent); }
.plugin-state-text.failed { color: var(--red); }
.plugin-state-text.off { color: var(--faint); }
.plugin-caps-inline { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: var(--fs-xs); color: var(--muted); }
.plugin-caps-inline .ico-svg { display: inline-block; vertical-align: -2px; color: var(--faint); }
.plugin-caps-inline .lead { color: var(--faint); }
.plugin-note { font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; max-width: 82ch; }
.plugin-warn { font-size: var(--fs-xs); color: var(--orange); line-height: 1.6; max-width: 82ch; }
.plugin-warn.bad { color: var(--red); }

/* ── 目录：卡片网格（压扁版）+ 列表视图 ── */
.plugin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.plugin-card {
  position: relative; display: flex; flex-direction: column; gap: 7px;
  padding: 13px 14px 12px 17px; text-align: left; color: inherit; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-2);
  transition: background-color var(--dur-interaction) var(--ease-in-out),
    border-color var(--dur-interaction) var(--ease-in-out);
}
.plugin-card-head { display: flex; align-items: center; gap: 11px; min-width: 0; width: 100%; }
.plugin-card-title { min-width: 0; flex: 1; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.plugin-card-head .plugin-card-action { flex: 0 0 auto; }
.plugin-card-desc {
  font-size: var(--fs-sm); color: var(--muted); line-height: 1.65;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.plugin-card-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: var(--fs-2xs); color: var(--faint); }
.plugin-card-foot .ico-svg { color: var(--faint); }
.plugin-card-foot .pb-item { display: inline-flex; align-items: center; gap: 4px; }

/* 列表视图（Obsidian 的 List 那一档）：一行一条，说明挤到名字右边一行截断 ——
   同一份 DOM，只换 grid 列数与内部朝向，切换不重排数据 */
.plugin-grid.is-list { display: flex; flex-direction: column; gap: 0; border-top: 1px solid var(--border); }
.plugin-grid.is-list .plugin-card { flex-direction: row; align-items: center; gap: 12px; border: 0; border-bottom: 1px solid var(--border); border-radius: 0; padding: 11px 14px 11px 17px; background: transparent; }
.plugin-grid.is-list .plugin-card-head { width: auto; flex: 0 0 auto; min-width: 0; }
.plugin-grid.is-list .plugin-card-title { flex-direction: column; align-items: flex-start; gap: 2px; }
.plugin-grid.is-list .plugin-card-desc { flex: 1; min-width: 0; -webkit-line-clamp: 1; margin-left: 4px; }
.plugin-grid.is-list .plugin-card-foot { flex: 0 0 auto; }
.plugin-grid.is-list .plugin-warn { flex: 0 0 auto; }

/* ── 详情：面包屑 + 色带 Hero + 正文/侧栏两栏 ──
   版式照参照量着做（VS Code 市场详情页：面包屑 → 整条彩色 Hero 带大图标/大名字/byline/主按钮
   → 标签页 → 正文 + 右侧元信息栏；Obsidian 详情：元信息侧栏 + 相关插件）。
   两处刻意的取舍：
   · Hero 的色相跟着插件自己走（--tile-h 与图标块同源）—— 一屏里每个插件一条不同色的带子，
     这是"我在看哪一条"最省事的信号；
   · sha256 从正文最显眼的位置挪进右栏小字：它是给怀疑的人核对用的，不是给所有人读的。 */
.plugin-detail {
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg-2);
  overflow: hidden;           /* Hero 与面包屑各有自己的底线，圆角要裁干净 */
}
.plugin-crumbs {
  display: flex; align-items: center; gap: 7px; padding: 10px 18px;
  border-bottom: 1px solid var(--border); font-size: var(--fs-sm); color: var(--muted);
}
.plugin-crumb-back {
  display: inline-flex; align-items: center; padding: 0; background: none; border: 0;
  font: inherit; font-size: var(--fs-sm); color: var(--accent); cursor: pointer;
}
.plugin-crumb-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm); }
.plugin-crumb-sep { color: var(--faint); }
.plugin-crumb.is-cur { color: var(--text); font-weight: 600; }
@media (hover: hover) and (pointer: fine) { .plugin-crumb-back:hover { text-decoration: underline; } }

.plugin-hero {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 18px;
  padding: 20px 18px;
  border-bottom: 1px solid var(--border);
  background: linear-gradient(100deg, hsl(var(--tile-h, 220) 46% 18% / .75), hsl(var(--tile-h, 220) 40% 12% / .25) 58%, transparent);
}
[data-theme='light'] .plugin-hero {
  background: linear-gradient(100deg, hsl(var(--tile-h, 220) 74% 94%), hsl(var(--tile-h, 220) 66% 97%) 58%, transparent);
}
.plugin-hero .plugin-mono { width: 60px; height: 60px; font-size: var(--fs-2xl); border-radius: var(--r-xl); }
.plugin-hero-main { min-width: 0; }
.plugin-hero-title { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.plugin-detail-name { margin: 0; font-size: var(--fs-2xl); font-weight: 650; letter-spacing: .01em; }
/* byline 一行：作者 · 下载 · 体积 · 更新 · 兼容 · 来源。值本身不强调，靠 mono 与间隔分组 */
.plugin-hero-stats { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 7px; font-size: var(--fs-sm); color: var(--muted); }
.plugin-hero-stats .pb-item { display: inline-flex; align-items: center; gap: 5px; }
/* 头像位（.plugin-avatar）在下面卡片那一段已经定义过 —— 这里**不再重复一份**：
   同一个类两份声明，同权重下后写的赢，改前一份会"改了没反应"（本仓踩过两次的死声明）。
   Hero 要的只是比卡片里小一点，所以只覆盖尺寸，其余继承。 */
.plugin-hero .plugin-avatar { width: 17px; height: 17px; }
/* 右栏的标签 chip：与卡片上的 .plugin-tag 同一个外观，但这里是 <button>（点了按标签筛目录），
   所以要补按钮复位 —— 少了 background/border 复位就会出现浏览器默认按钮底与边框。 */
.plugin-tag.is-btn {
  background: none; font: inherit; font-size: var(--fs-2xs); cursor: pointer; padding: 1px 6px;
}
.plugin-tag.is-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) { .plugin-tag.is-btn:hover { color: var(--text); border-color: var(--accent-border); } }
.plugin-hero-desc { margin: 9px 0 0; font-size: var(--fs-md); color: var(--text); line-height: 1.7; max-width: 76ch; }
.plugin-detail-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.plugin-hero-flags { padding: 10px 18px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 6px; }
.plugin-warn.bad { color: var(--red); }

.plugin-detail-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 264px); gap: 30px; padding: 20px 18px 22px; }
@media (max-width: 980px) { .plugin-detail-grid { grid-template-columns: 1fr; gap: 22px; } }
/* 手机宽度（≤640）：Hero 的三列（图标/正文/动作）挤不下 —— 实测 320px 时正文只剩 68px，
   名字会一个字一行。改成两列 + 动作换到下一行。 */
@media (max-width: 640px) {
  .plugin-hero { grid-template-columns: auto minmax(0, 1fr); }
  .plugin-detail-actions { grid-column: 1 / -1; justify-content: flex-start; }
}
.plugin-detail-main { min-width: 0; }
.pd-block + .pd-block { margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--border); }
/* 分节标题要比 README 自己的标题（h3.md-h = --fs-lg）高一级，否则"它能做什么"（分节）
   与 README 里同名的 h4 看起来一样重，读者分不清哪层是宿主写的、哪层是作者写的 */
.pd-block-title { margin: 0 0 12px; font-size: var(--fs-xl); font-weight: 600; letter-spacing: .01em; }
/* README：真标题层级 + 真列表 + 真链接（渲染在 ui/core/format.js）。
   标题整体降两档（`#` → h3）：详情页已经用 hero 的名字当了 h1 的位置，
   README 自己的一级标题再顶一个大字号，页面就有两个"最粗的标题"了。 */
/* 正文不再按 78ch 卡宽：卡片本身已经限到 1080px，主列 ≈749px（约 89 字符，与 VS Code 市场
   的正文栏同量级）。再卡一次就又在栏内留出 180px 的空洞 —— 那正是「堆在左侧」的观感来源。 */
.pd-readme { font-size: var(--fs-md); color: var(--text); line-height: 1.85; overflow-wrap: anywhere; }
.pd-readme .md-h { margin: 18px 0 8px; font-weight: 650; letter-spacing: .01em; }
.pd-readme .md-h:first-child { margin-top: 0; }
.pd-readme h3.md-h { font-size: var(--fs-lg); }
.pd-readme h4.md-h { font-size: var(--fs-md); }
.pd-readme h5.md-h, .pd-readme h6.md-h { font-size: var(--fs-sm); color: var(--muted); }
.pd-readme .md-ul, .pd-readme .md-ol { margin: 6px 0 10px; padding-left: 1.45em; }
.pd-readme .md-ul { list-style: disc; }
.pd-readme .md-ol { list-style: decimal; }
.pd-readme .md-ul li, .pd-readme .md-ol li { margin: 3px 0; }
/* 段落：空行分段（段内换行由作者自己敲的 <br> 保留）。首段不留上边距 —— 它紧跟在分节标题下 */
.pd-readme .md-p { margin: 0 0 10px; }
.pd-readme .md-p:last-child { margin-bottom: 0; }
.pd-readme a { color: var(--accent); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .pd-readme a:hover { text-decoration: underline; } }
.pd-readme strong { font-weight: 650; }
.pd-readme .md-pre {
  margin: 10px 0; padding: 11px 13px; background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-md); overflow-x: auto; line-height: 1.6;
}
.pd-readme code { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }
.pd-readme .md-pre code { color: var(--text); white-space: pre; }
.pd-caps { display: flex; flex-direction: column; gap: 12px; max-width: 70ch; }
.pd-cap { display: flex; gap: 10px; align-items: flex-start; }
.pd-cap-ico { display: inline-flex; color: var(--faint); margin-top: 2px; }
.pd-cap-name { font-size: var(--fs-sm); font-weight: 600; }
.pd-cap-desc { margin-top: 2px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.65; }
.pd-shots { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.pd-shots img { max-width: 200px; max-height: 126px; border-radius: var(--r-md); border: 1px solid var(--border); display: block; }

/* 右栏：标签 / 资源 / 作者 / 出处与指纹。参照里这一栏是"决定要不要装"的元信息堆放处 */
.plugin-detail-aside { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
.pd-side-label { font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--faint); margin-bottom: 8px; }
.plugin-detail-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.pd-side-links { display: flex; flex-direction: column; gap: 7px; font-size: var(--fs-sm); }
.pd-side-links a { color: var(--accent); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .pd-side-links a:hover { text-decoration: underline; } }
.plugin-fp code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--faint); overflow-wrap: anywhere; line-height: 1.5; }
.plugin-note { margin-top: 7px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; }
.plugin-author { display: flex; align-items: center; gap: 10px; }
.plugin-author-ico { width: 30px; height: 30px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-family: var(--mono); font-size: var(--fs-sm); font-weight: 600; background: linear-gradient(155deg, hsl(var(--tile-h, 220) 40% 32%), hsl(var(--tile-h, 220) 48% 22%)); color: hsl(var(--tile-h, 220) 70% 82%); border: 1px solid var(--border); }
[data-theme='light'] .plugin-author-ico { background: linear-gradient(155deg, hsl(var(--tile-h, 220) 70% 90%), hsl(var(--tile-h, 220) 64% 82%)); color: hsl(var(--tile-h, 220) 45% 34%); }
.plugin-author-name { font-size: var(--fs-sm); font-weight: 600; }
.plugin-author-sub { margin-top: 2px; font-size: var(--fs-2xs); color: var(--faint); }

/* 相关插件：详情底部 */
.plugin-related { padding: 18px 18px 20px; border-top: 1px solid var(--border); }
.plugin-related h5 { margin: 0 0 12px; font-size: var(--fs-lg); font-weight: 600; letter-spacing: .01em; }
.plugin-related-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; }
.plugin-related-item { display: flex; align-items: flex-start; gap: 10px; padding: 12px 13px; background: var(--bg-2); cursor: pointer; border: 1px solid var(--border); border-radius: var(--r-md); text-align: left; color: inherit; width: 100%; }
.plugin-related-name { font-size: var(--fs-sm); font-weight: 600; }
.plugin-related-desc { margin-top: 3px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* ── 发布插件（作者向）──
   一张卡片里的三段：抬头（图标 + 标题 + 一段话）、三步命令、三条分发渠道。
   刻意不做成表单：控制台跑在使用者自己的服务器上，它没有作者 GitHub 的写权限 ——
   这里的职责是把路说清楚，不是替他发布。
   第一版是"一段大文字 + 灰色命令行 + 三张白卡"直接铺在页面底上，没有边界也没有层次
   （用户："太难看了"）；这一版给它一个卡片壳、一个强调色抬头的图标块、编号徽标与渠道徽标。 */
.plugin-publish {
  padding: 22px 22px 20px;
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg-2);
}
.plugin-publish-hero { display: flex; gap: 14px; align-items: flex-start; padding-bottom: 18px; margin-bottom: 20px; border-bottom: 1px solid var(--border); }
.plugin-publish-ico {
  flex: 0 0 auto; width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-md); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid var(--accent-border);
}
.plugin-publish-hero-txt { min-width: 0; }
.plugin-publish-hero-txt h3 { margin: 0 0 6px; font-size: var(--fs-xl); font-weight: 650; letter-spacing: .01em; }
.plugin-publish-hero-txt p { margin: 0; font-size: var(--fs-sm); line-height: 1.8; color: var(--muted); max-width: 74ch; }
.plugin-publish-hero-txt a, .plugin-channel a, .plugin-publish-foot a { color: var(--accent); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  .plugin-publish-hero-txt a:hover, .plugin-channel a:hover, .plugin-publish-foot a:hover { text-decoration: underline; }
}
/* 左三步、右"你会得到什么"：宽屏把右侧那片空白用起来（原来三段命令孤零零占满整张卡，
   右边一大片空 —— 看着像没做完）。窄屏收成一列，侧栏落到命令下面。 */
.plugin-publish-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(190px, 215px); gap: 22px; align-items: start; }
@media (max-width: 1000px) { .plugin-publish-grid { grid-template-columns: 1fr; gap: 20px; } }
.plugin-publish-side {
  display: flex; flex-direction: column; gap: 9px; padding: 14px 15px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-md);
}
.plugin-publish-side-label { font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--faint); }
.plugin-publish-side-label:not(:first-child) { margin-top: 6px; }
.plugin-tree { margin: 0; padding: 10px 12px; background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--r-sm); overflow-x: auto; }
.plugin-tree code { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text); line-height: 1.7; white-space: pre; }
.plugin-publish-side-list { margin: 0; padding-left: 1.1em; font-size: var(--fs-xs); line-height: 1.7; color: var(--muted); }
.plugin-publish-side-list li { margin: 2px 0; }
.plugin-publish-side-list code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--text); }
.plugin-publish-side-link { margin-top: 2px; font-size: var(--fs-xs); color: var(--accent); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .plugin-publish-side-link:hover { text-decoration: underline; } }
.plugin-steps { list-style: none; margin: 0 0 26px; padding: 0; display: flex; flex-direction: column; gap: 18px; }
.plugin-step { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 12px; }
.plugin-step-n {
  width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 600;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid var(--accent-border);
}
.plugin-step-body { min-width: 0; }
.plugin-step-t { font-size: var(--fs-md); font-weight: 600; margin: 2px 0 7px; }
/* 命令块：等宽、可整块选中。不做"复制"按钮 —— 控制台常在 http 隧道后面，
   非安全上下文里 navigator.clipboard 根本不存在，按钮会变成假的。 */
.plugin-cmd {
  margin: 0; padding: 9px 12px; background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-md); overflow-x: auto; line-height: 1.6;
}
/* 长命令**只在空格处折行**（pre-wrap 不含 overflow-wrap:anywhere —— 那个会在 token 中间断，
   把 URL 劈成两半）。最长的 pack 命令在 577px 宽度下折成两行，每行都是完整的参数。 */
.plugin-cmd code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--text); white-space: pre; }
.plugin-step-hint { margin-top: 7px; font-size: var(--fs-xs); line-height: 1.75; color: var(--muted); }
.plugin-step-hint code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--text); }
.plugin-publish-sec { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.plugin-publish-sec h4 { margin: 0; font-size: var(--fs-lg); font-weight: 600; letter-spacing: .01em; }
.plugin-publish-sec span { font-size: var(--fs-xs); color: var(--faint); }
.plugin-channels { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.plugin-channel {
  display: flex; flex-direction: column; gap: 8px; padding: 14px 15px 13px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-md);
}
.plugin-channel-head { display: flex; align-items: center; gap: 7px; }
.plugin-channel-ico { display: inline-flex; color: var(--accent); }
.plugin-channel-head b { font-size: var(--fs-sm); font-weight: 600; }
.plugin-channel-badge {
  margin-left: auto; padding: 1px 7px; border-radius: var(--r-pill);
  border: 1px solid var(--border); font-size: var(--fs-2xs); color: var(--muted); white-space: nowrap;
}
.plugin-channel p { margin: 0; font-size: var(--fs-xs); line-height: 1.75; color: var(--muted); }
.plugin-channel code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--text); }
.plugin-channel-link { margin-top: auto; padding-top: 2px; font-size: var(--fs-xs); }
.plugin-publish-foot {
  margin: 20px 0 0; padding: 11px 14px; font-size: var(--fs-xs); line-height: 1.8; color: var(--muted);
  background: var(--bg); border-left: 2px solid var(--accent-border); border-radius: 0 var(--r-md) var(--r-md) 0;
}
.plugin-publish-foot b { color: var(--text); }
.plugin-publish-foot code { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--text); }
/* 「手动安装」页脚的一句桥接：作者做出包之后最自然的下一步就是看怎么上架 */
.plugin-publish-bridge { margin-top: 16px; padding-top: 12px; border-top: 1px dashed var(--border); font-size: var(--fs-sm); color: var(--muted); }
.plugin-linkbtn { background: none; border: 0; padding: 0; font: inherit; font-size: var(--fs-sm); color: var(--accent); cursor: pointer; }
@media (hover: hover) and (pointer: fine) { .plugin-linkbtn:hover { text-decoration: underline; } }
.plugin-linkbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }

/* 动作反馈条：贴底 */
.plugins-status-bar { position: sticky; bottom: 0; z-index: 2; margin-top: 22px; padding: 10px 0 2px; background: var(--bg); border-top: 1px solid var(--border); font-size: var(--fs-sm); }
.plugins-status-bar:empty { display: none; }
.plugin-empty { padding: 16px 0; font-size: var(--fs-sm); color: var(--muted); }

@media (max-width: 760px) {
  .plugin-page-head { flex-direction: column; gap: 12px; }
  .plugin-master { max-width: none; }
  .plugin-row { flex-wrap: wrap; }
  .plugin-row-actions { justify-content: flex-start; }
  .plugin-grid { grid-template-columns: 1fr; }
  .plugin-grid.is-list .plugin-card-desc { display: none; }
  /* 列表档在窄屏把 head/foot 压成一整行会溢出（图标+标题+动作+作者+下载+时间算下来远宽于 320px）。
     允许折行并放开收缩，否则卡片会画到面板外面、出现横向滚动条。 */
  .plugin-grid.is-list .plugin-card { flex-wrap: wrap; }
  .plugin-grid.is-list .plugin-card-head,
  .plugin-grid.is-list .plugin-card-foot { flex: 0 1 auto; min-width: 0; }
  .plugin-grid.is-list .plugin-card-title { min-width: 0; }
  .plugin-grid.is-list .plugin-name { overflow-wrap: anywhere; }
}

html[data-glass] .plugin-card,
html[data-glass] .plugin-detail,
html[data-glass] .plugin-related-item {
  border-color: var(--glass-edge); background-color: var(--glass-fill); background-image: var(--glass-grain); background-clip: padding-box;
}

@media (hover: hover) and (pointer: fine) {
  .plugin-row:hover { background: var(--bg-2); }
  .plugin-card:hover { background: var(--bg-3); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
  .plugin-grid.is-list .plugin-card:hover { background: var(--bg-2); border-color: transparent; border-bottom-color: var(--border); }
  .plugin-related-item:hover { background: var(--bg-3); border-color: var(--accent-border); }
}
