/* ══════════════════════════════════════════════════════════════
   设计 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-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: #d9a441;              /* 黄铜。只给"机器人说的"，别挪作他用 */
  --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: #8a5a06;              /* 亮色下的黄铜：压暗才压得住白底（在卡片上 5.9:1）。
                                    底色与上面那六项一律保持原值，见顶部那段说明。 */
  --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; }
.tab:hover .ico { opacity: 1; }
.tab.active .ico { opacity: 1; color: var(--accent); }
.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; }
.view.active { display: flex; }

.list-pane {
  width: 320px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  overflow-y: auto;
  background: var(--bg-2);
}
/* 列表头：字号从 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;
}

.detail-pane { flex: 1; min-width: 0; overflow-x: hidden; overflow-y: auto; padding: 20px 24px; }
/* 空状态：用 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); }
.mode-lifecycle { --mode-color: var(--orange); --mode-soft: rgba(255, 180, 84, .12); }
.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);
}
.session-mode-name { color: var(--mode-color); 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; }
}
.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; }
.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); }

.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%;
}
/* 选中项的底色交给滑块 —— 否则滑块滑过来时旧格子还留着自己那一层底色 */
.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;
  padding: 22px 26px 40px;
}
.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); }
.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; }
.model-rows-table th:first-child, .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, .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;
}
.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) 32px; align-items: end; gap: 10px; margin-bottom: 10px; }
.moment-window-row .field { min-width: 0; margin: 0; }
.moment-window-row input { min-width: 0; width: 100%; height: 34px; box-sizing: border-box; }
.moment-window-remove { width: 32px; height: 32px; padding: 0; margin-bottom: 2px; }
.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 28px; }
  .moment-window-row input { padding: 6px 3px; }
  .moment-window-remove { width: 28px; }
}
.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; }
}
/* 保存条：渲染在内容末尾 + 贴底悬浮，长页面里从顶部就能看到、一直悬在视口底部，
   滚到底时正好落在内容末尾。全站只有一个保存按钮（分区里不再各放一个）。 */
.save-bar {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--bg); padding: 12px 0; border-top: 1px solid var(--border);
  display: flex; gap: 10px; align-items: center;
  box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, .45);
}

/* ── 动效 ── */
/* 入场：新增条目（会话/存档列表的新行）。参照实现的 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);
  border-radius: var(--r-sm);
  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-sm);
  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-xs); }
.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);
}
.update-deploy-summary > div { min-width: 0; padding: 10px 12px; }
.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-sm);
  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);
}
.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;
  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);
}
.asset-sticker-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.asset-sticker-body { padding: 8px; }
.asset-sticker-body strong,
.asset-sticker-body > span,
.asset-sticker-body small {
  display: block;
  overflow-wrap: anywhere;
}
.asset-sticker-body strong { font-size: var(--fs-sm); }
.asset-sticker-body > span { margin-top: 3px; color: var(--muted); font-size: var(--fs-2xs); }
.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-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-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: collapse; font-size: var(--fs-sm);
  background: var(--bg-2);
  border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden;
}
.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);
  border-radius: var(--r-input);
}
.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);
}
.chat-item, .session-item, .pick-item, .settings-menu-item, .tool-card, .chat-item:hover, .session-item:hover, .pick-item:hover, .settings-menu-item:hover {
  background: var(--hover); box-shadow: var(--shadow-sm);
}
.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); }
.asset-table-wrap, .context-call-table-wrap { border-radius: var(--r-card); border: 1px solid var(--border); overflow: hidden; }

/* ── 对话框 / 登录卡 / 加载页 ── */
/* 弹窗类才需要压暗背景；启动提示是浮层，不压暗（否则进控制台会整屏闪一下） */
.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：助手文字在控件下方、始终可见），它们的观感问题本来就不是"空框"。
   ══════════════════════════════════════════════════════════════════════════════ */
.field:not(:has(.hint)):not(:has(> label.checkbox-row)) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
}
/* 标签固定列宽：所有行的控件都从同一条竖线开始（Ant Design 的 labelCol 思路）。
   150px 是按最长标签量的（"旧模式最低累计消息" 9 字 ≈ 113px，留出余量）。 */
.field:not(:has(.hint)):not(:has(> label.checkbox-row)) > label {
  flex: 0 0 150px;
  margin: 0;
  align-self: center;
}
.field:not(:has(.hint)):not(:has(> label.checkbox-row)) > :not(label) {
  flex: 1 1 auto;
  min-width: 0;
}
/* 数字：按内容定宽（8ch ≈ 7 位数字 + 一点余量）。不是"所有数字都这么窄" —— 它只影响
   参数行里的数字；句子里那种行内小数字有自己的规则。 */
.field:not(:has(.hint)):not(:has(> label.checkbox-row)) > input[type='number'] {
  flex: 0 0 auto;
  width: calc(8ch + 30px);
}
/* 下拉：按内容定宽（fit 最长选项），不拉满整行 —— 拉满就会出现"右边一大截空白" */
.field:not(:has(.hint)):not(:has(> label.checkbox-row)) > select {
  flex: 0 1 auto;
  width: auto;
  max-width: 100%;
}

/* ── 旧式内联字段（<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; }

/* 其余长文本字段（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）不只总览页用，后续页面统一往这套上靠。
*/
.overview-pane { flex: 1; min-width: 0; overflow-y: auto; padding: 20px 22px 28px; }

.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 0 15px; margin-bottom: 0; }
.grid-2 > .panel { margin-bottom: 0; }
.panel-head { display: flex; align-items: flex-start; gap: 9px; margin-bottom: 11px; }
.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 0 11px; }
[data-density='compact'] .tab { padding: 6px 10px; }
[data-density='compact'] .overview-pane { padding: 14px 16px 20px; }
[data-density='roomy'] .kpi { padding: 16px 18px 17px; }
[data-density='roomy'] .panel { padding: 17px 0 18px; }
[data-density='roomy'] .tab { padding: 10px 12px; }
[data-density='roomy'] .overview-pane { padding: 24px 28px 32px; }
[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 变（它就是"两端全圆"），所以刻意不进 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) 0; }
.kpi { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.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);
    --ctl-line: color-mix(in srgb, var(--text) 22%, 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;
}
/* 暗角：单独一层压在最后，把四角收掉 —— 平铺到底的渐变会让页面"发散的松" */
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. 高对比模式：加粗边框、提亮次要文字（放在色板之后，同权重靠顺序取胜） ── */
: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; }

/* 单选/多选：自定义外观（原来直接用系统控件，深色下是系统蓝方块，和整页不是一套）。
   单选做成"实心圆点 + 内圈底色"，与开关的圆点同族。 */
.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。
   也就是说它比"直接硬切"更糟：硬切至少立刻看到内容。
   参照实现这边也不给页面内容做入场动画（路由切换是即时的），所以这里保持硬切。 */
.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);
  /* 描边色（颜色边）。必须是**单色**，不能是渐变 —— 见下面表面规则里那段说明：
     半透明填充下，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);
}
/* 液态玻璃：通透清晰 + 最亮的边。低模糊是刻意的 —— 液体玻璃要"看得透"，
   糊起来就变成磨砂了。强烈建议配「背景 → 渐变」，它要透的就是那层渐变。 */
html[data-glass='liquid'] {
  --glass-blur: 1px;
  --glass-sat: 175%;
  --glass-contrast: 1.22;
  --glass-bright: 1.06;
  --glass-tint: 58%;
  --glass-edge: rgba(255, 255, 255, .34);
  --glass-spec-top: rgba(255, 255, 255, .85);
  --glass-spec-bot: rgba(255, 255, 255, .22);
  --glass-spec-side: rgba(255, 255, 255, .26);
  --glass-inner: inset 0 -16px 32px -14px rgba(0, 0, 0, .58);
  --glass-shadow:
    0 4px 8px rgba(0, 0, 0, .26),
    0 20px 48px -14px rgba(0, 0, 0, .60);
}
/* 亚克力：最糊 + 最实 + 一层噪点。灰阶中性，适合长时间盯着看。
   噪点的作用是打散大面积半透明的**色带**，不是装饰 —— 暗色底上色带很明显。 */
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);
}
/* 亮色：白表面上的"白色高光"看不见 —— 那里改用更亮的白边交代厚度；
   阴影也必须更轻，否则浅色下的卡片会显脏。 */
[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);
}
/* 高对比模式：把不透明度拉满，可读性优先，材质让位 */
html[data-contrast='high'][data-glass] { --glass-tint: 97%; }
[data-theme='light'][data-contrast='high'][data-glass] { --glass-tint: 100%; }

/* 玻璃表面：一条规则覆盖全部"大表面"。所有档位差异都走上面的 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] 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);
}
/* ── 边缘折射带（只给液态玻璃）──
   苹果那套 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(1.5px) saturate(210%) brightness(1.22);
  -webkit-backdrop-filter: blur(1.5px) saturate(210%) brightness(1.22);
  /* 两层同一块黑，靠 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;
}

.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; }

/* 侧栏样式只换"填色"这一个 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));
}
/* 不支持 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] 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; }
