/* =============================================================================
 * llmconsole_app 前端样式
 *
 * 配色参考 Hugging Face 的 light theme：近白底 + 细边框 + 克制阴影，
 * 文字用暖黑（#1f2937 系）而不是纯黑，强调色用 HF 的黄色，#0f0f0f 只作主按钮/标题。
 * 所有颜色都收敛到下面的 token 里，组件规则**不写字面色**——换主题只改这一段。
 *
 * 布局：左侧固定侧边栏 + 右侧内容区（flex，不是 margin-left 硬顶）。
 * 内容区用 12 栏网格，仪表类视图（总览/模型）按语义跨列，宽屏才铺开、窄屏自动单列。
 * ========================================================================== */

:root {
  /* 基础面 */
  --bg: #fafafa;
  --bg-soft: #f3f4f6;
  --panel: #ffffff;
  --panel-2: #f9fafb;
  --panel-3: #f3f4f6;
  --border: #e5e7eb;
  --border-strong: #d6dae0;

  /* 2.1 灰卡两档（管理者裁决 2026-11-09）：② 灰=有实物但显存放不下，③ 深灰=查无实物。
     必须与 [data-theme="night"] 成对（tests：token 集合一致，见本文件主题大注释）。 */
  --card-mute-bg: #e5e9ef;
  --card-mute-edge: #c9d2dc;
  --card-mute-ink: #4b5563;
  --card-deep-bg: #d0d7de;
  --card-deep-edge: #b8c1cc;
  --card-deep-ink: #57606a;

  /* 文字 */
  --text: #1f2937;
  --text-strong: #0f0f0f;
  --muted: #6b7280;
  --muted-2: #9ca3af;

  /* 强调（HF 黄）+ 语义色 */
  --accent: #ffd21e;
  --accent-hover: #ffcc00;
  --accent-ink: #3d3000;          /* 黄底上的字色 */
  --accent-soft: #fff8d6;
  --blue: #2563eb;
  --blue-soft: #eff6ff;
  --green: #15803d;
  --green-soft: #ecfdf5;
  --red: #dc2626;
  --red-soft: #fef2f2;
  --amber: #b45309;
  --amber-soft: #fffbeb;
  --purple: #6d28d9;
  --purple-soft: #f5f3ff;

  /* 曲线/柱状图配色：浅底上要够深才看得清，故不用霓虹色 */
  --chart-1: #0d9488;   /* Prefill 吞吐 */
  --chart-2: #db2777;   /* 输出速度 */
  --chart-3: #2563eb;   /* TTFT */
  --chart-grid: #e5e7eb;
  --chart-axis: #cfd5dd;
  --chart-ref: #9ca3af;

  /* 尺寸 */
  --nav-w: 224px;
  --content-max: 1280px;
  /* 身份条的高度 = 二级页签的 sticky top（.tabs 用它）。
     真值由 app.js 的 syncMachBarHeight() 量完写进 html 的内联样式（内联优先于这里）。
     这一行是**兜底**，不是为了好看：var() 引用的变量若一处都没定义，取到的是空值，
     sticky 的 top 会退化成 auto —— 页签就完全不吸顶了，而且不报任何错。
     （uitest 的「组件规则里 var() 引用的变量都在某处定义过」正是钉这个。）
     数值取桌面端实测的 49px 附近；真到用上它的那天，说明 JS 已经没在跑，
     这时候页签差几个像素是第二位的，别贴到视口顶才是第一位的。 */
  --size-mach-bar: 49px;
  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 14px;
  --shadow-xs: 0 1px 2px rgba(16, 24, 40, .05);
  --shadow-sm: 0 1px 3px rgba(16, 24, 40, .07), 0 1px 2px rgba(16, 24, 40, .04);
  --shadow-md: 0 4px 12px rgba(16, 24, 40, .08);
  /* 1.4b 实时时间带（见文件末尾的 .pb-* / .perfband 一节）。
     ★ 本前端的夜间主题选择器是 [data-theme="night"]（不是 "dark"）——
     上面第 95 行那句话说的就是这个；新增颜色 token 必须同时在那一块里声明。 */
  --pb-line: var(--border);
  --pb-grid: rgba(128, 128, 128, .22);
  /* 时间带与仪表盘用的**序列色**。
     从前这几个值以字面量写在 app.js 的 pbDraw/pbLegend 里，暗色主题下不会变——
     同一个色板在两种底色上不可能都对。挪进 token 后两处各有一套。
     虚线用的半透明由 canvas 的 globalAlpha 现算，所以这里只放实色。 */
  --pb-power: #e0821a;      /* 功耗占比 */
  --pb-util: #2f7fd0;       /* SM 算力占用 */
  --pb-memutil: #7c4dbb;    /* 显存**带宽**占用（与 SM 占用是两张表） */
  --pb-decode: #3fa36b;     /* 生成速率 */
  --pb-kv: #0d9488;         /* KV 缓存占用 */
  --pb-prefill: #d18f28;    /* 注入上下文（阶段色带） */
  --pb-busy: #8a8aa0;       /* 忙但分不出阶段 */
  --pb-waiting: #d4623a;    /* 排队等待 */
  /* 仪表盘的底槽。用 --border 而不是新的灰：底槽本来就该"几乎看不见"。 */
  --pg-track: var(--border);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* 交互件的字面色（也要 token 化：夜间主题下要整体反相） */
  --btn-primary-bg: #0f0f0f;      /* 浅色主题：黑底白字 */
  --btn-primary-ink: #ffffff;
  --btn-primary-bg-hover: #2b2b2b;
  --check-ink: #0f0f0f;           /* checkbox 的 accent-color */
  --chip-hi: rgba(255, 255, 255, .22);
  --chip-hi-ink: #ffffff;
  --bar-grad-2: #ffb454;          /* 切换进度条渐变的第二色 */

  /* 语义色边框 / 软底悬停（原先写死在组件规则里，夜间主题会露馅） */
  --accent-border: #f2e2a8;
  --blue-border: #bfdbfe;
  --green-border: #bbf7d0;
  --red-border: #fecaca;
  --red-soft-hover: #fde8e8;
  --amber-border: #fde68a;
  --purple-border: #ddd6fe;

  /* “仪器感”用色：蓝图网格 / 卡片微渐变 / 辉光 / 焦点环 / 滚动条。
     都是**极淡**的量——目标是"有质感"，不是"看得见装饰"。
     新增颜色 token 必须同时在 [data-theme="night"] 里声明，否则暗色下会静默继承浅色值
     （tests/test_theme.py 会拦）。 */
  --bg-grid: rgba(15, 23, 42, .035);
  --panel-grad: linear-gradient(180deg, #ffffff, #fcfcfd);
  --glow: rgba(255, 210, 30, .38);
  --ring: rgba(37, 99, 235, .40);
  --track: #eef0f3;
  --thumb: #c8cfd8;

  /* 让原生控件（滚动条 / 下拉 / 日期选择器）跟着主题走 */
  color-scheme: light;
}

/* =============================================================================
 * 夜间主题（night）
 *
 * 取色依据 Hugging Face 前端的 GitHub-Primer 暗色变量（其公开的 DESIGN.md 记录了
 * `--d2h-dark-bg-color` / `--d2h-dark-border-color` 等正取自这套调色板）：
 *   画布 #0b0f19 · 抬升面 #161b22 / #21262d · 描边 #30363d · 正文 #e6edf3 ·
 *   次级文字 #8b949e · 语义 blue #388bfd / green #3fb950 / amber #d29922 / red #f85149。
 * 品牌 HF 黄（#ffd21e）保持不变——它本来就压得住暗底。
 *
 * 两条规则：
 *   ① **只在这里覆盖颜色 token**，组件规则一个字都不改（换主题只动这一块）；
 *   ② 语义色必须**成对**换：前景换成亮色（暗底上要看得清），软底换成暗色调（否则一片惨白）。
 *      `tests/test_theme.py` 会断言这里与 :root 的 token 集合完全一致——漏一个就会
 *      静默继承浅色值，典型症状是"暗底上的深色字，看不见"。
 * ========================================================================== */
[data-theme="night"] {
  color-scheme: dark;

  /* 基础面 */
  --bg: #0b0f19;
  --bg-soft: #11161f;
  --panel: #161b22;
  --panel-2: #1a2029;
  --panel-3: #21262d;
  --border: #30363d;
  --border-strong: #3d444d;

  /* 灰卡两档的夜色（与 :root 成对）：夜底「更深灰」= 真的往下沉——
     普通卡面 #161b22 → ② #10151b → ③ #0d1117 三档递暗（设计表里的 #1c2128
     反而比卡面亮，画不出「灰」，落地时改掉）。 */
  --card-mute-bg: #10151b;
  --card-mute-edge: #2a313a;
  --card-mute-ink: #8b949e;
  --card-deep-bg: #0d1117;
  --card-deep-edge: #21262d;
  --card-deep-ink: #7d8590;

  /* 文字 */
  --text: #e6edf3;
  --text-strong: #ffffff;
  --muted: #8b949e;
  --muted-2: #6e7681;

  /* 强调：HF 黄在暗底上同样成立，只把浅黄软底换成暗色 */
  --accent: #ffd21e;
  --accent-hover: #ffcc00;
  --accent-ink: #3d3000;
  --accent-soft: #322905;
  --accent-border: #7a6410;

  /* 语义色：前景提亮 + 软底压暗 */
  --blue: #58a6ff;
  --blue-soft: #0d2a4d;
  --blue-border: #1f4270;
  --green: #3fb950;
  --green-soft: #0d2a17;
  --green-border: #1f5c2e;
  --red: #f85149;
  --red-soft: #3a1417;
  --red-soft-hover: #4a1a1e;
  --red-border: #6e2b2b;
  --amber: #d29922;
  --amber-soft: #33270a;
  --amber-border: #6b5320;
  --purple: #a371f7;
  --purple-soft: #241a3d;
  --purple-border: #4c3a75;

  /* 图表：暗底上要用亮色，否则曲线糊进背景 */
  --chart-1: #2dd4bf;
  --chart-2: #f472b6;
  --chart-3: #58a6ff;
  --chart-grid: #30363d;
  --chart-axis: #484f58;
  --chart-ref: #6e7681;

  /* 交互件反相：暗底上的主按钮用亮色（HF 的 primary-on-dark 就是这个做法） */
  --btn-primary-bg: #e6edf3;
  --btn-primary-ink: #0b0f19;
  --btn-primary-bg-hover: #ffffff;
  --check-ink: #e6edf3;
  --chip-hi: rgba(0, 0, 0, .25);
  --chip-hi-ink: #0b0f19;
  --bar-grad-2: #ffb454;

  /* 暗色下阴影要用更深的黑：浅色主题的柔和阴影在暗底上根本看不见 */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, .5), 0 1px 2px rgba(0, 0, 0, .35);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, .55);

  /* 仪器感用色（暗色版）：网格线改用亮色、卡片微渐变从 --panel-2 收到 --panel，
     辉光在暗底上要收敛一点（同样的 alpha 在暗底上会显得更亮）。 */
  --bg-grid: rgba(230, 237, 243, .045);
  --panel-grad: linear-gradient(180deg, #1a2029, #161b22);
  --glow: rgba(255, 210, 30, .28);
  --ring: rgba(88, 166, 255, .50);
  --track: #12171e;
  --thumb: #39414d;
  /* 1.4b 实时时间带的辅助色：网格线在暗底上要更淡而不是更亮，
     否则整条带被网格切成碎块。语义色的对比由下面成对覆盖的 token 提供。 */
  --pb-line: #30363d;
  --pb-grid: rgba(200, 200, 210, .18);
  /* 序列色在暗底上要**提亮**：同样的 #e0821a 在 #0b0f19 上会显得发闷、
     与相邻的蓝绿分不开。这一组是各自的暗色对应值，不是简单加透明。 */
  --pb-power: #f0a038;
  --pb-util: #58a6ff;
  --pb-memutil: #a371f7;
  --pb-decode: #3fb950;
  --pb-kv: #2dd4bf;
  --pb-prefill: #e3a33c;
  --pb-busy: #8b949e;
  --pb-waiting: #f0883e;
  --pg-track: #21262d;
}

* { box-sizing: border-box; scrollbar-width: thin; scrollbar-color: var(--thumb) var(--track); }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background-color: var(--bg);
  /* 蓝图网格：只在卡片之间的缝隙里透出来，给整页一点纵深。
     刻意压到几乎看不见（light 3.5% / night 4.5% 透明度）——它是"质感"，不是"花纹"。 */
  background-image:
    linear-gradient(var(--bg-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
  background-size: 26px 26px, 26px 26px;
  background-attachment: fixed;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC",
               "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  font-size: 14px;
  line-height: 1.6;
  display: flex;
  align-items: stretch;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ============================ 侧边导航 ============================ */
.topnav {
  flex: 0 0 var(--nav-w);
  width: var(--nav-w);
  position: sticky; top: 0;
  align-self: flex-start;
  height: 100vh;
  display: flex; flex-direction: column;
  gap: 3px;
  padding: 18px 14px 24px;
  background: var(--panel);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  z-index: 10;
}
.topnav .brand {
  display: flex; align-items: flex-start; gap: 6px;
  margin: 0 4px 18px;
  font-size: 15px; font-weight: 700; color: var(--text-strong);
  letter-spacing: -.01em;
  /* 品牌块是四行（厂商·产品 / 系统名 / 版本 / 英文名），每行靠 small 的
     flex-basis:100% 独占一行。行距收得很紧（row-gap:1px）：四行之间若按常规
     行距排，整块会把下面的第一个导航条目顶下去，侧栏第一屏就少一条。 */
  flex-wrap: wrap; row-gap: 1px;
}
/* 产品名比厂商名重：厂商是限定语，模衡才是这块牌子的主角 */
.topnav .brand .brand-vendor {
  font-size: 12px; font-weight: 600; color: var(--muted);
  letter-spacing: .02em;
}
.topnav .brand .brand-sep { color: var(--muted-2); font-weight: 400; }
.topnav .brand .brand-name { letter-spacing: .01em; }
/* 品牌标：HF 风的黄色方块 + 深色字，整个侧栏唯一的高饱和色块。
   品牌块改成多行后 logo 要**顶对齐**（原先的 center 会让它漂在四行的正中间，
   看着像没挂住）；再压一点上边距，与 15px 的产品名对到同一条基线附近。 */
.topnav .brand::before {
  content: "⬢";
  display: grid; place-items: center;
  width: 26px; height: 26px;
  flex: 0 0 auto;
  margin-top: 1px;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 8px; font-size: 15px; line-height: 1;
  /* 一点点外发光：让品牌标像"通电"的指示灯，而不是贴上去的色块 */
  box-shadow: 0 0 0 1px var(--accent-hover), 0 0 10px var(--glow);
}
.topnav .brand small {
  flex-basis: 100%;          /* 强制换行 → 每行副标题各占一行 */
  font-size: 11px; font-weight: 500; color: var(--muted-2);
  letter-spacing: 0; margin-top: 1px;
}
/* 系统名是这块牌子的解释，比版本/英文名这两个标签高一档 */
.topnav .brand .brand-sub { color: var(--muted); }
/* 端别行（本页是「哨兵 · 节点机」/ 对面是「舰队总控台」）：与厂商名同一个 accent 家族，
   一眼能分出是哪一个端 */
.topnav .brand .brand-ed {
  margin-top: 3px;
  font-weight: 600; color: var(--accent);
}
.topnav .brand .brand-en { letter-spacing: .04em; text-transform: none; }
/* 分区标题（.nav-sep）2026-10-11 **已删除**：侧栏原先是「品牌块 + 6 条分区线 + 11 个条目
   + 状态脚」，六条分区线各占 14px 上边距 + 10px 内边距 + 1px border（合计约 150px），
   而它们买到的分辨力是零——分区名（"评测"）与条目名（"③ 运行比较"）说的是同一件事。
   删掉后侧栏是一列平铺按钮，⑩⑪ 也进了第一屏。窄屏翻转里那条隐藏规则一并删了
   （没有 .nav-sep 可藏，留着会让人以为还存在这么一个东西）。 */
.nav-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--text);
  cursor: pointer; font-size: 13.5px; font-family: inherit; text-align: left;
  transition: background .12s, color .12s, border-color .12s;
}
.nav-item:hover { background: var(--panel-3); }
.nav-item.active {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--text-strong);
  font-weight: 600;
  box-shadow: 0 0 0 1px var(--glow), var(--shadow-xs);
}
.nav-item.active::before {
  content: ""; width: 3px; height: 16px; margin-left: -10px; margin-right: 4px;
  background: var(--accent-hover); border-radius: 2px;
}
.nav-foot {
  margin-top: auto; padding: 12px 6px 0;
  border-top: 1px solid var(--border);
  font-size: 11px; color: var(--muted-2); line-height: 1.5;
}
/* 导航“总览”载入徽标 */
/* 导航项末尾的 §N：与圈码是**同一个序**（①=§1 … ⑪=§11）；它把正文里
   11.1 / 7.2 这类标题的编号显式写出来。改序时徽标与正文必须一起改。 */
.nav-item i {
  font-style: normal; margin-left: auto; padding-left: 8px;
  font-size: 11px; color: var(--muted-2); font-variant-numeric: tabular-nums;
}
.nav-item.active i { color: var(--muted); }

/* 只给读屏用的文本（视图切换播报）。不能用 display:none——那样读屏也读不到。 */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.nav-badge {
  display: inline-block; margin-left: auto; padding: 1px 8px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-size: 11px; font-weight: 700;
  animation: pulse 1.2s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }

/* ============================ 内容区 ============================ */
.wrap {
  flex: 1 1 auto; min-width: 0;
  max-width: var(--content-max);
  padding: 26px 28px 72px;
}
.view { display: none; }
.view.active { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 16px; }
.view.active > * { grid-column: 1 / -1; min-width: 0; }

/* ---------- 分节页签（⑦ 硬件与能力） ----------
   7.0~7.4 原来纵向堆叠，找 7.4 互联拓扑要滚过四张整卡；改成页签后一屏只有当前那节。
   切页签**只改 class**（.tabpanel 的显隐 + .tab 的选中态）：卡片的 DOM、表格、
   <pre> 原文一个字节都不动。这一条是必需的，不是洁癖——排序靠一个扫整篇 document 的
   MutationObserver（startSortObserver）在渲染后把顺序摆回来，卡片一旦重绘就得让
   观察器再跑一轮；而「表在隐藏面板里」本身是安全的：CSS 只改 display，
   querySelectorAll('table[data-sort-key]') 照取得到，排序记忆照旧生效。 */
.tabs {
  /* 页签条自己也要占满 12 栏（.view.active > * 里已经写了 grid-column: 1/-1），
     所以这里只管横向排布与间距。 */
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: 2px; padding-bottom: 10px;
  border-bottom: 1px solid var(--border);

  /* 二级导航吸在**身份条正下方**（2026-10-11）：切页签是这一页里最常做的动作，
     原先滚下去它就被卡片顶走，想换页签得先滚回顶部。
     top 用 --size-mach-bar，那个变量由 app.js 实测后写进 :root —— 条的高度不是常量
     （窄屏字段换行会长高、换字体差几个像素），写死数字就会露出一条缝，
     卡片从那道缝里滚上来。变量取不到时退回 42px（桌面端的实测值）。
     z-index 15：压住卡片与卡内表头（那两处是 1~2），但低于身份条(20)与齿轮(60)，
     所以往下滚时它是**滑进身份条底下**，不是盖住身份条。 */
  position: sticky;
  top: var(--size-mach-bar, 42px);
  z-index: 15;
  /* 吸住时必须是不透明的，否则卡片会透过它显出来；用 --bg 与页面底色一致
     （身份条是 --panel 白，页签用 --bg 读起来正好是它下面的一层）。 */
  background: var(--bg);
  /* 遮挡只到**这条下沿的那条分割线为止**，不要往下再铺一块色带。
     第一版我铺了 16px（想盖住网格 gap），结果卡片的正文被切在分割线**下方 16px**，
     于是分割线与半行字之间浮着一条碎字——比不吸顶还难看。切在分割线上、
     紧接着就是卡片正文，才是吸顶表头该有的样子（下面 .card 自带 18px 内边距，
     所以正常滚动速度下先出现的是卡片的空白内边距，不是字）。 */
  box-shadow: 0 1px 0 0 var(--bg);
}
.tab {
  padding: 7px 14px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--text);
  cursor: pointer; font-size: 13.5px; font-family: inherit;
  transition: background .12s, color .12s, border-color .12s;
}
.tab:hover { background: var(--panel-3); }
.tab.active {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--text-strong);
  font-weight: 600;
  box-shadow: 0 0 0 1px var(--glow), var(--shadow-xs);
}
/* 页签是 tablist，方向键换页后焦点要看得见（不然键盘操作像是没反应）。 */
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* 非选中面板真正不显示；只做显隐，不重绘内容。 */
.tabpanel { display: none; }
.tabpanel.active { display: block; }

/* 卡片 */
.card[data-span="5"] { --span: 5; }
.card[data-span="7"] { --span: 7; }
.card[data-span="12"] { --span: 12; }
.card {
  background: var(--panel-grad);      /* 极淡的上下渐变，替代纯色块，卡片有"面板"厚度 */
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  box-shadow: var(--shadow-xs);
}
.card > h2 {
  margin: 0 0 14px;
  /* 右侧留出齿轮的宽度：否则长 hint（如「1.2 API 接入信息 base_url / model / key / curl」）
     会钻到右上角那个 fixed 齿轮底下 */
  padding-right: 40px;
  padding-bottom: 10px;
  /* 标题下沿用"渐隐数据线"替掉等宽实线：从左侧强到右侧消失，像仪表的分区刻度。
     用 border 做不到渐变，改用 1px 的背景条。 */
  border-bottom: 0;
  background-image: linear-gradient(90deg, var(--border-strong), var(--border) 34%, transparent 97%);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 1px;
  font-size: 15px; font-weight: 650; color: var(--text-strong);
  letter-spacing: -.01em;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
}
/* 标题前的强调块：3×13px 的 HF 黄竖条，给每张卡一个"仪器铭牌"的起点。
   align-self 覆盖父级的 baseline，让它垂直居中于标题文字。 */
.card > h2::before {
  content: ""; flex: 0 0 auto; align-self: center;
  width: 3px; height: 13px; border-radius: 2px;
  background: linear-gradient(180deg, var(--accent), var(--accent-hover));
  box-shadow: 0 0 6px var(--glow);
}
.card h3 { font-size: 13.5px; font-weight: 650; color: var(--text-strong); }
.hint { color: var(--muted); font-weight: 400; font-size: 12px; }
.hint.down { color: var(--red); }
header h1 { margin: 0 0 2px; font-size: 22px; }
header .sub { color: var(--muted); font-size: 13px; }

/* 总览：按信息密度跨列，宽屏才铺开。
   隐藏的卡片（切换横幅/日志卡）不参与排版，dense 让后面的卡自动补位。 */
/* 跨列宽度写在 HTML 的 data-span 上（不用 :has()：它在老浏览器里直接失效，
   卡片会静默全部变成半宽，属于「不报错但排版塌了」的那类问题）。
   用 dense 让条件隐藏的卡片（切换横幅）不留下空档。 */
#view-overview.active { grid-auto-flow: row dense; }
#view-overview > .card { grid-column: span var(--span, 12); }
/* 1.4 实时性能 与 1.5 累计用量：内容本身很宽（多指标 + 四层价格 + 报价表），
   各占一半会把指标挤成窄条、说明文字压到 10px。改成整行纵向排列。 */
#view-overview > .card:has(#perf), #view-overview > .card:has(#usage) { grid-column: 1 / -1; }

/* 模型页：筛选/卡片整行；硬件信息整行；切换日志整行 */
#view-models > .card { grid-column: 1 / -1; }

/* 基准测试页每张图都独占整行，是唯一能真正吃到「更宽=更好读」的页面：
   宽屏下把内容区上限放宽，横轴标签与图下注脚才不至于挤。 */
@media (min-width: 1600px) {
  .wrap:has(#view-benchsuite.active) { --content-max: 1560px; }
}

/* 基准测试：两张图并排比纵向堆叠更好对比 */
#view-benchsuite > .card { grid-column: 1 / -1; }
@media (min-width: 1500px) {
  .wrap { --content-max: 1440px; }
}

/* 窄屏一律单列：两列里塞 GPU/用量那些宽内容会挤成竖条 */
@media (max-width: 1280px) {
  #view-overview > .card { grid-column: 1 / -1; }
}
@media (max-width: 1024px) {
  body { flex-direction: column; }
  .topnav {
    position: static; height: auto; width: 100%; flex: none;
    flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px;
    padding: 10px 12px; border-right: none; border-bottom: 1px solid var(--border);
  }
  .topnav .brand { margin: 0 12px 0 0; }
  .nav-foot { display: none; }
  .nav-item { width: auto; }
  .wrap { padding: 18px 14px 60px; }
}

/* ============================ 表单 ============================ */
.bench-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px 18px;
  align-items: center;
}
.bench-form > label {
  display: flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: 13px;
  min-width: 0;
}
.bench-form > label > b { color: var(--text-strong); font-weight: 600; }
/* Prefill 档位芯片编辑器。
   档位是一组离散值（每个档 = 一次单独测量），用胶囊逐个显示：
   一眼看得出几个档、单个可删、加档有校验。 */
.size-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
.size-chips:empty::after {
  content: "还没有档位 —— 至少加一个（如 500）"; color: var(--muted-2); font-size: 12px;
}
.size-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--accent-soft); border: 1px solid var(--accent-border);
  border-radius: 999px; padding: 3px 6px 3px 12px;
  font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--text-strong);
}
.size-chip .sz-v { font-weight: 600; }
.size-chip .sz-real { color: var(--muted); font-size: 11px; }
.size-chip .sz-del {
  border: none; background: none; cursor: pointer; color: var(--muted);
  font-size: 15px; line-height: 1; padding: 0 4px; border-radius: 999px;
}
.size-chip .sz-del:hover { color: var(--red); background: var(--red-soft); }
.size-chip.bad { background: var(--red-soft); border-color: var(--red-border); color: var(--red); }
.size-add { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.size-add input { flex: 0 0 130px; }
.sz-preset {
  font-family: inherit; font-size: 11.5px; cursor: pointer;
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 2px 8px; color: var(--text);
}
.sz-preset:hover { border-color: var(--text-strong); }
.size-err { color: var(--red); font-size: 12px; }

/* 本机身份条（内容区顶部吸顶，全视图常驻）+ ② 权重路径卡（.mach-rows）
   两处共用 .mach- 前缀，但**版式已经分开**（2026-10-11）：
     · 身份条走 .mach-bar + 一行 flex 的 .mach-f（本机是哪台，一行看完）；
     · 权重卡走 .mach-rows 的两列网格（标签 + 长路径，列宽要一致才不被挤成两截）。
   混用过一次：身份条原先也用 .mach-rows，于是 主机名/其它地址/控制台 竖成三行——
   改一边就会连累另一边，所以宁可两套版式。共用的是 .mach-k/.mach-v 这两个原子的配色。 */
.mach-bar {
  /* 吸顶：内容区第一个孩子，滚动时钉在视口顶。
     负外边距把 .wrap 的上/左右 padding 折算掉，padding 再补回来——
     于是它自己左右与卡片同一条边（视觉上像一张通栏的卡），但**透明底之外的部分
     必须是不透明的**：它下面会有卡片滚上来，透明底会把两段文字叠在一起。
     z-index 20 要高于卡片、低于 .topnav(10 在窄屏会变 static，故不冲突) 与 .gear-btn(60)。 */
  position: sticky; top: 0; z-index: 20;
  margin: -26px -28px 16px;
  padding: 9px 28px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 5px 22px;
  font-size: 12px;
}
/* 一行放不下就换行，不溢出、不横滚（窄屏 1024px 以下还有一条专属的负外边距修正） */
.mach-bar .mach-head { font-size: 16px; flex-wrap: nowrap; }
.mach-bar .mach-head code { font-size: 13px; }
.mach-bar .mach-f { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
/* 值是"照着敲"或"要认出来"的串（主机名、地址列表），给个上限它就自己换行，
   不会把整条撑到内容区宽度之外。 */
.mach-bar .mach-v { max-width: 52ch; }
/* 窄屏：负外边距必须跟着 .wrap 的专属 padding（18/14px）重算，否则这一条会被
   桌面端那条 -26/-28px 顶出屏幕左右各 14px（实测 barLeft=-14 / barRight=914，
   在 900px 的视口上直接多出一条横向滚动条）。
   ⚠️ 这条**必须**排在 .mach-bar 基规则之后：本文件早期的 @media (max-width:1024px)
   在第 452 行附近，比 .mach-bar 更靠前；选择器同为单类、特指度相同，
   靠前的那条会被后面的基规则整体盖掉——我第一版就写在那儿，于是窄屏毫无反应。
   所以窄屏这一份挂在这里，而不是回上面那个断点块里。 */
@media (max-width: 1024px) {
  .mach-bar { margin: -18px -14px 14px; padding: 8px 14px; }
}
.mach-id { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 20px; width: 100%; }
.mach-head {
  font-size: 19px; font-weight: 600; color: var(--text-strong);
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
/* 地址用等宽字体、字号略小：它是"照着敲"的东西，不要和标题抢视觉重量。 */
.mach-head code { font-size: 15px; font-weight: 500; }
.mach-sep { color: var(--muted-2); }
.mach-unknown { color: var(--muted-2); font-size: 12px; }
.mach-note { font-size: 11.5px; color: var(--muted); }
/* ② 权重路径卡的两列网格：标签列 max-content，长路径才不会被挤成两截。
   （身份条曾经也用它，2026-10-11 分成一行后不再用，见上面 .mach-bar） */
.mach-rows {
  display: grid; grid-template-columns: max-content 1fr;
  gap: 3px 10px; font-size: 12px; align-items: baseline;
  margin-top: 6px;
}
.mach-k { color: var(--muted); white-space: nowrap; }
.mach-v { color: var(--text); }
.mach-v code, .mach-rows code { word-break: break-all; }
/* 其它地址收在折叠块里：面上只留一条 summary，展开才看到全部（含容器网桥）。 */
.mach-more summary { cursor: pointer; color: var(--muted); font-size: 12px; }
.mach-more[open] summary { color: var(--muted-2); }
.mach-more code { margin-right: 6px; }

/* ② 权重路径栏 */
.models-paths { display: flex; flex-direction: column; gap: 10px; margin: 2px 0 12px; }
.models-paths:empty { display: none; }
.path-sect {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 10px; background: var(--bg-soft);
}
.path-sect-h { font-size: 12px; font-weight: 600; color: var(--text-strong); margin-bottom: 4px; }
.path-remote { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.path-remote li { font-size: 12px; color: var(--text); }
.path-remote-path code { word-break: break-all; }
.path-remote .chip.acc {
  font-size: 11px; color: var(--accent-ink); background: var(--accent-soft);
  border: 1px solid var(--accent-border); border-radius: var(--radius-sm); padding: 0 5px;
}

/* 卡片上那一行「本机路径」：与 .mach-rows 同款两列，但更小、更淡——
   它是补充信息（要查的时候查得到），不该把卡片上的名称与状态压下去。 */
.m-path {
  display: grid; grid-template-columns: max-content 1fr;
  gap: 2px 10px; font-size: 11.5px; margin-top: 7px;
  padding-top: 6px; border-top: 1px dashed var(--border);
  align-items: baseline;
}
.m-path code { word-break: break-all; color: var(--text); }

/* 档位建议块：**只建议、不阻拦**（不 disable 按钮、不自动改档位）。
   颜色全部走主题 token，浅色/夜间两套都跟得住。 */
.size-advice { display: flex; flex-direction: column; gap: 5px; margin: 2px 0 8px; }
.size-advice:empty { display: none; }
.adv-basis { font-size: 12px; color: var(--text); }
.adv-hint { font-size: 11.5px; color: var(--muted); }
.adv-dim { color: var(--muted-2); }
.adv-warn {
  font-size: 11.5px; color: var(--amber);
  background: var(--amber-soft); border: 1px solid var(--amber-border);
  border-radius: var(--radius-sm); padding: 5px 8px;
}
.adv-warn.adv-dim { color: var(--amber); opacity: .85; }
.adv-ok {
  font-size: 11.5px; color: var(--green);
  background: var(--green-soft); border: 1px solid var(--green-border);
  border-radius: var(--radius-sm); padding: 5px 8px;
}
.adv-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 11.5px; }
.adv-actions .sz-preset:disabled { cursor: default; opacity: .55; }

/* 表单的「动作行」：勾选项 + 主/次按钮 + 报告链接排成一行、独占整行。
   不归组的话 grid 会把它们当作普通格子，勾选框可能落到按钮行下方半格的位置，
   看起来像挤在两颗按钮中间。 */
.bench-form > .form-actions {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
}
.bench-form > .form-actions > label {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: 13px;
}

/* 3.1 主参数行：只留 max_tokens + 开始按钮，其余收进「高级参数」。
   按钮用主按钮样式——它是这一页唯一的动作，不该和旁边的输入框同等重量。 */
.bench-form-main { margin-top: 4px; }
.bench-form-main > button { justify-self: start; }

/* 高级参数折叠块：收起时只占一行，展开后内部仍是一个自适应网格 */
.bench-adv {
  margin-top: 14px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--panel-2); padding: 0 14px;
}
.bench-adv > summary {
  cursor: pointer; padding: 10px 0; font-size: 13px; color: var(--text);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  list-style: none;                     /* 去掉默认三角，改用自绘箭头 */
}
.bench-adv > summary::-webkit-details-marker { display: none; }
.bench-adv > summary::before {
  content: "▸"; color: var(--muted); font-size: 11px;
  transition: transform .15s;
}
.bench-adv[open] > summary::before { transform: rotate(90deg); }
.bench-adv > summary:hover { color: var(--text-strong); }
.bench-adv > .bench-form { padding: 4px 0 14px; }
/* 「N 项已改」徽标：收起时也能看见，避免拿非默认配置当默认口径比 */
.bench-adv .adv-badge {
  font-style: normal; font-size: 11px; font-weight: 600;
  color: var(--amber); background: var(--amber-soft);
  border: 1px solid var(--amber-border); border-radius: 999px; padding: 1px 8px;
}

/* 内容本身就是一行的项（当前模型、用例勾选）：让它独占整行。
   塞进 230px 的列里会把模型名挤成三行、勾选组被压成一列。 */
.bench-form > label.form-wide { grid-column: 1 / -1; align-items: flex-start; }
.bench-form > label.form-wide .case-checks { flex: 1 1 auto; }
/* 输入框按内容给宽：数字/下拉固定窄宽，文本类才吃剩余空间。
   否则 grid 会把每个控件都拉满一整列（230px），一屏数字被摊得极散。 */
.bench-form input[type=number], .bench-form select { flex: 0 0 auto; }
/* 提示文字比输入框还宽时会被截断（top_k 的「留空=不传」就是这样：
   88px 的框留 48px 给内容，提示要 60px）。
   这里按 placeholder 的字符数给个下界，让带长提示的输入框自己变宽——
   比逐个调宽度、或者把提示删短更不容易再犯。1 个 CJK 字符 ≈ 1em，拉丁字符更窄，
   所以用 em 估算偏保守；加减按钮的 20px 另算。 */
.bench-form input[placeholder] {
  min-width: calc(var(--ph, 0) * 1em + 44px);
}
.bench-form input[type=text] { flex: 1 1 140px; }
.bench-form > label:has(input[type=text]) { flex: 1 1 240px; }
/* 标签文字（max_tokens / temperature / 预热次数）不折行：折了会让同一行的控件
   基线错位，看着像排版坏了。控件本身仍可折到下一行。 */
.bench-form > label { white-space: nowrap; }
.bench-form > label > input, .bench-form > label > select, .bench-form > label > .case-checks { white-space: normal; }
/* 表单按钮不等宽拉满：按内容给宽即可 */
.bench-form > button, .bench-form > .btn { justify-self: start; }
input[type=number], input[type=text], select, textarea {
  font-family: inherit; font-size: 13px;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 6px 9px;
  transition: border-color .12s, box-shadow .12s;
}
input[type=number] { width: 88px; }
input[type=text] { width: 100%; min-width: 120px; }
select { min-width: 140px; }
input[type=number]:focus, input[type=text]:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent-hover);
  box-shadow: 0 0 0 3px rgba(255, 210, 30, .3);
}
input[type=checkbox] { accent-color: var(--check-ink); width: 15px; height: 15px; }
.mtp-row { margin: 0 0 14px; font-size: 13px; }
.mtp-row label { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--panel); color: var(--text);
  cursor: pointer; font-size: 13px; font-family: inherit; font-weight: 500;
  box-shadow: var(--shadow-xs);
  transition: background .12s, border-color .12s, color .12s;
  text-decoration: none;
}
.btn:hover { background: var(--panel-3); border-color: var(--muted-2); text-decoration: none; }
.btn:active:not(:disabled) { transform: translateY(1px); box-shadow: none; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
/* 主按钮用 HF 的深色实心，比黄色更压得住整页 */
.btn.primary {
  background: var(--btn-primary-bg); border-color: var(--btn-primary-bg); color: var(--btn-primary-ink);
  /* 顶部一道内高光：实心按钮不再是"一块平色"，有金属按键的暗示 */
  box-shadow: inset 0 1px 0 var(--glow), var(--shadow-xs);
}
.btn.primary:hover { background: var(--btn-primary-bg-hover); border-color: var(--btn-primary-bg-hover); }
.btn.danger { color: var(--red); border-color: var(--red-border); background: var(--red-soft); }
.btn.danger:hover { background: var(--red-soft-hover); border-color: var(--red); color: var(--red); }
.btn.mini { padding: 3px 10px; font-size: 12px; border-radius: var(--radius-sm); }
.btn.mini:disabled { opacity: .5; cursor: default; }
.stop-row { margin-top: 16px; display: flex; gap: 10px; flex-wrap: wrap; }

.case-checks { display: flex; flex-wrap: wrap; gap: 6px; }
.case-checks label {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 4px 10px; font-size: 12.5px; color: var(--text);
  cursor: pointer;
}
.case-checks label:hover { border-color: var(--border-strong); background: var(--panel-3); }

/* ============================ 提示/徽标 ============================ */
.tag {
  display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px;
  background: var(--bg-soft); color: var(--muted); border: 1px solid var(--border);
}
.tag.up { background: var(--green-soft); color: var(--green); border-color: var(--green-border); }
.tag.down { background: var(--red-soft); color: var(--red); border-color: var(--red-border); }
.tag.warn { background: var(--amber-soft); color: var(--amber); border-color: var(--amber-border); }
/* 「公开口径」：厂商**已下架**该页、只剩发布时口径的数字。刻意与 .warn（厂商现行口径）
   用同族的语义色系但**不同的色相**——蓝=出处更弱，别让它读起来像"厂商规格"。 */
.tag.ref { background: var(--blue-soft); color: var(--blue); border-color: var(--blue-border); }

/* ============================ 总览 ============================ */
.current-box { line-height: 1.9; }
.current-box .tag { margin-right: 6px; }
.curr-title { font-size: 17px; font-weight: 650; color: var(--text-strong); }

/* ---- 1.1 当前运行态：一类模型一张卡 ----
   为什么每张卡都要有边界（而不是像从前那样排成一长行）：
   卡片是「这一个位置（slot）」的作用域——同一门类同时只跑一个，卡里谁在跑、
   还能换成谁、换的时候会停掉谁，都写在同一张卡里。原先一条流水账配三个全局按钮，
   按钮的作用域（"这一类全停"）与它在页面上的位置（紧跟在某个模型的详情后面）对不上。 */
/* 一排一张卡：多一个门类就多占一排。
   为什么不是 auto-fill 的网格（原先是 minmax(380px,1fr)）：卡片内容**长短不齐**
   ——有的门类只有一条嵌入、几行就完，有的门类带着 25 条"本机起不来"的说明。
   并排时相邻两张卡按行高对齐，短的那张被拉出大片空白，长的那张把整行撑高；
   换一个门类数、换一个窗口宽度，错位的样子就变一次，看起来像排版坏了。
   一排一张之后，每一类占一条自己的横排，卡高只由自己决定，**加一类只是往下多一条**，
   与别的卡毫无关系。 */
.cur-grid {
  display: grid; width: 100%; min-width: 0;
  grid-template-columns: 1fr;
  gap: 12px;
}
.cur-card {
  display: flex; flex-direction: column; gap: 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 13px 15px; background: var(--panel); min-width: 0;
}
/* 在跑的那张给绿边框与淡绿底：一眼能数出「几类在跑」。停着的保持中性。 */
.cur-card.on { border-color: var(--green-border); background: var(--green-soft); }
/* 空卡（本机无对应模型）：虚线边框 + 不铺底色。
   为什么要与"停着的真卡"长得不一样：那是两种事——一个去启动、一个去装。
   实线边框的空卡会被当成为"有一个模型、只是没跑"。 */
.cur-card.empty { border-style: dashed; background: transparent; }
.cur-card .cc-empty { padding: 2px 0 2px 10px; }
.cur-card .cc-empty-why { color: var(--muted); }
/* 卡头：门类名 + 这一类的状态。门类名比条目名大一号——它是这张卡的身份，
   条目名是卡里的一条。两者字号反过来，人会以为卡是"某一个模型"的外框。 */
.cur-card .cc-head {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.cur-card .cc-cat {
  flex: 1 1 auto; min-width: 0; font-size: 14px; font-weight: 650; color: var(--text-strong);
}
/* 卡里的一条（这一类选定的那个模型）。左边一条竖线：读起来是
   "这一格里的那个"，与卡头分开，又不是"另一张卡"。 */
.cur-card .ce {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  padding-left: 10px; border-left: 2px solid transparent;
}
.cur-card .ce.on { border-left-color: var(--green-border); }
.cur-card .ce.off { border-left-color: var(--border); }
.cur-card .cc-note {
  font-size: 11.5px; line-height: 1.7; color: var(--muted);
  border-top: 1px dashed var(--border); padding-top: 8px;
}
.cur-card .cc-top { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
/* 卡头那一行里的标签（「缺省」）不参与压缩：它只有两个字，被挤成竖排就读不出来了。 */
.cur-card .cc-top > .tag { flex: none; }
.cur-card .cc-head .tag { flex: none; }
.cur-card .cc-name {
  flex: 1 1 auto; min-width: 0;
  font-weight: 600; font-size: 13.5px; line-height: 1.4; color: var(--text-strong);
  font-family: var(--mono);           /* 展示名就是权重文件名，等宽便于逐字符核对 */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; word-break: break-all;
}
.cur-card .cc-state { flex: none; font-size: 11.5px; white-space: nowrap; }
.cur-card .cc-state .tag { margin-left: 0; }
.cur-card .cc-kind { font-size: 11.5px; color: var(--muted); }
.cur-card .cc-kind .tag { margin-left: 6px; }
.cur-card .cc-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; }
.cur-card .chip {
  font-size: 11px; color: var(--muted);
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 3px 8px; white-space: nowrap;
}
.cur-card .chip b { font-weight: 600; color: var(--text); }
/* 按钮靠右、**保持自身宽度**：卡是一整排时 `flex:1 1 0` 会把「启动」「停止」拉成
   两条各占半屏的横条——读起来像两个页面级动作，而它们只是这一条模型的两个按钮。
   靠右与上方的「本类缺省」选择器同一条竖线，扫视时一眼能找回控制区。 */
.cur-card .cc-btns { display: flex; gap: 8px; margin-top: 2px; justify-content: flex-end; }
.cur-card .cc-btns .btn { flex: 0 0 auto; min-width: 88px; }
.cur-empty { padding: 16px 2px; }
/* 本机没有容器的通用工具那一句：**不是卡片**，只是说明它们为什么不在这张卡格里。 */
.cur-hidden { margin-top: 10px; }
.cur-hidden .hint { line-height: 1.7; }

/* 切换中横幅 */
.switch-banner { border-left: 3px solid var(--accent); background: var(--panel); }
.sw-model { margin: 4px 0; font-size: 13px; }
.sw-model b { color: var(--text-strong); }
.sw-bar {
  height: 8px; border-radius: 999px; background: var(--panel-3);
  border: 1px solid var(--border); overflow: hidden; margin: 10px 0 8px;
}
.sw-bar i {
  display: block; height: 100%; width: 0%;
  /* 两层背景：底层是进度渐变，上层是一道来回扫的高光。
     只有"模型切换中"的横幅可见时它才在跑——所以这不是装饰动画，
     而是"正在工作"的状态指示（配合 prefers-reduced-motion 会停）。 */
  background-image:
    linear-gradient(100deg, transparent 18%, var(--glow) 45%, transparent 72%),
    linear-gradient(90deg, var(--accent-hover), var(--bar-grad-2));
  background-size: 220% 100%, 100% 100%;
  background-repeat: no-repeat;
  transition: width .4s;
  animation: bar-scan 1.8s linear infinite;
}
.sw-meta { font-size: 12.5px; color: var(--muted); }
.sw-stages {
  font-size: 12px; color: var(--muted); line-height: 1.7; margin-top: 8px;
  max-height: 120px; overflow: auto;
}
/* 切换时间线的三段（app.js 里 `class="stage done|current|pending"`）。
   **这两条规则曾经缺失**：类名写了、style.css 里一条都没有，于是三个阶段长得一模一样，
   而 JS 那边算出的 done/current/pending 全白算——正是本仓库最防的那种静默失效
   （不报错，只是差异消失）。颜色一律走主题 token，深浅两套自动跟随。 */
.stage.done { color: var(--green); }
.stage.done::before { content: '✓ '; }
.stage.current { color: var(--text-strong); font-weight: 600; }
.stage.current::before { content: '▶ '; }
.stage.pending { color: var(--muted); opacity: .65; }
.stage.pending::before { content: '· '; }
/* 启动阶段横轴（1.1「当前运行态」里，app.js 的 loadAxisHtml）。
   一条横轴、分站推进：每站一个格子，走过 = 实心，正在做 = 呼吸高亮，没到 = 灰。
   为什么按"站"而不是按时间百分比：时间百分比在"卡在第 4 站 20 分钟"时会一路涨到
   90% 并长期停在那儿，看上去快好了，实际是卡住了；按站推进则一眼看到它停在哪儿。
   两处约束照抄本文件顶部那三条自我要求：颜色全走 token、有 reduced-motion 出口、
   动效必须表示状态（呼吸 = 正在做这一站）。 */
.load-axis {
  margin: 8px 0 6px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--panel-2);
}
.ax-track { display: flex; gap: 3px; align-items: stretch; }
/* 每站等宽：flex:1 让格子随容器伸缩，窄屏也能排得下（不会把页面撑出横向滚动）。 */
.ax-cell { flex: 1 1 0; min-width: 0; }
.ax-cell i {
  display: block; height: 10px; border-radius: 3px;
  /* 底层留一道"没走过的槽"，走过时被前景色盖住——所以灰格不是空白，
     而是"这里有一站、还没到"，与"根本没有这一站"区分得开。 */
  background: var(--panel-3); border: 1px solid var(--border);
  transition: background .3s, border-color .3s;
}
.ax-cell span {
  display: block; margin-top: 4px; font-size: 11px; line-height: 1.35; color: var(--muted);
  /* 中文标签最长 8 个字（"捕获 / 编译 CUDA 图"），窄屏下靠截断而不是换行：
     换行会把各站的高度撑得参差不齐，横轴就读不直了。title 里有全名兜底。 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ax-cell.done i { background: var(--green); border-color: var(--green); }
.ax-cell.done span { color: var(--green); }
/* 正在做的那一站：高亮 + 呼吸。呼吸是**状态**不是装饰——它区分
   "这一站正在做" 与 "这一站做完了、下一站还没开始"，两者都是一格已亮的条。 */
.ax-cell.current i {
  background: var(--amber); border-color: var(--amber);
  animation: ax-pulse 1.6s ease-in-out infinite;
}
.ax-cell.current span { color: var(--amber); font-weight: 600; }
.ax-cell.pending i { background: var(--panel-3); }
@keyframes ax-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.ax-note { margin-top: 7px; font-size: 12px; color: var(--muted); }
.ax-note b { color: var(--text-strong); }

.spinner {
  display: inline-block; width: 13px; height: 13px; vertical-align: -2px;
  border: 2px solid var(--border); border-top-color: var(--text-strong);
  border-radius: 50%; margin-right: 6px; animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* API 接入信息 */
.api-info { line-height: 1.75; }
.api-line { margin-top: 10px; }
.kv { margin: 3px 0; }
.kv b { color: var(--muted); font-weight: 600; width: 96px; display: inline-block; }
code {
  font-family: var(--mono); font-size: 12.5px;
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 2px 6px; color: var(--text-strong);
}
code.curl { display: block; width: 100%; margin-top: 6px; white-space: pre-wrap; word-break: break-all; }

/* GPU / 硬件 */
.gpu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.gpu {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px 14px; background: var(--panel-2);
}
.gpu .g-title { display: flex; justify-content: space-between; gap: 8px; color: var(--muted); font-size: 12px; }
.gpu .g-mem { font-size: 19px; font-weight: 650; margin: 6px 0; color: var(--text-strong); font-variant-numeric: tabular-nums; }
.gpu .bar { height: 6px; background: var(--border); border-radius: 999px; overflow: hidden; }
.gpu .bar i { display: block; height: 100%; background: var(--green); border-radius: 999px; }
.gpu .bar.warn i { background: var(--amber); }
.gpu .bar.hot i { background: var(--red); }
/* 按模型显存分段条（1.3）：总占用条下面，一条一段、一段一色（颜色由 JS 内联，
   这里只定形状）。高度写成 px 而不是新令牌：它不是一个会被主题改写的「颜色/尺寸语义」，
   引一个新令牌反而要欠夜间主题一份覆盖（本仓闸只认令牌，内联十六进制不受管）。 */
.gpu .g-seg { display: flex; gap: 2px; height: 10px; margin-top: 8px; border-radius: 999px; overflow: hidden; }
.gpu .g-seg i { display: block; height: 100%; border-radius: 2px; min-width: 3px; }
.gpu .g-lgrow { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 6px; font-size: 11px; color: var(--muted); }
.gpu .g-lg { display: inline-flex; align-items: center; gap: 5px; }
.gpu .g-lg i { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.gpu .g-lg b { color: var(--text-strong); font-weight: 600; font-variant-numeric: tabular-nums; }
.gpu .g-lg-n { color: var(--muted); opacity: .8; }
.gpu .g-meta { color: var(--muted); font-size: 12px; margin-top: 8px; }

.hw-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.hw.card { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; background: var(--panel-2); }
.hw-title { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
.hw-row { display: flex; align-items: baseline; gap: 6px; font-size: 13px; padding: 2px 0; }
.hw-row span { color: var(--muted); min-width: 34px; }
.hw-row b { color: var(--text-strong); font-size: 15px; font-variant-numeric: tabular-nums; }
.hw-sub { color: var(--muted-2); font-size: 11px; font-style: normal; margin-left: auto; }

/* 实时性能 / 用量 */
.perf { font-size: 22px; font-weight: 650; color: var(--text-strong); }
.perf .sub { color: var(--muted); font-size: 13px; font-weight: 400; margin-left: 8px; }
/* 指标格子。全宽卡片下用 grid 自适应列：格子之间自动对齐、不会像 flex 那样
   长短不一地参差；数值统一 tabular-nums，跨行竖直对齐。 */
.pur.stats {
  margin-top: 14px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px;
}
.pur .cell {
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px 14px; min-width: 0;
}
.pur .cell i { display: block; font-style: normal; color: var(--muted); font-size: 12px; margin-bottom: 4px; }
.pur .cell b { display: block; color: var(--text-strong); font-size: 18px; line-height: 1.35; font-variant-numeric: tabular-nums; }
/* 值后面挂一个限定词（如「实测平均」格里的「2 卡」）。
   限定词必须比数值小、且与数值同一行——它修饰的就是旁边那个数，
   换行或等大会让它看起来像另一个读数。
   作用域写到 b 之内，是为了不撞 .pur .seg .sub / .pur .meta .sub：
   那两处的 .sub 是段落里的说明，字号和颜色都不一样。 */
.pur .cell b .sub {
  display: inline; font-size: 11px; font-weight: 400;
  color: var(--muted); margin-left: 6px; font-variant-numeric: normal;
}
/* 「没有值」的指标格：必须与「值为 0」明显不同——0 是测到的结果，缺值是没这个能力/还没测。
  视觉上：虚线边框 + 弱化底色 + 灰色破折号 + 一个原因小标签；
    · 实线边框 = 有读数（哪怕是 0），虚线边框 = 没有读数，扫一眼就能分开。
    · 原因标签（未启用/空闲/无 draft/无数据）直接写在格子里，不必悬浮也知道为什么。 */
.pur .cell.na {
  border-style: dashed;
  background: transparent;
}
.pur .cell.na i { color: var(--muted-2); }
.pur .cell.na b {
  color: var(--muted-2); font-weight: 500;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.pur .cell.na .na-why {
  font-style: normal; font-size: 11px; font-weight: 600; line-height: 1.6;
  color: var(--muted); background: var(--panel-3);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px;
}
/* 1.4 实时性能：6 个指标一行排开（全宽才排得下） */
#perf .pur.stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.perf .cell.warn b { color: var(--amber); }
.pur { display: flex; gap: 18px; }
/* 注意：.pur.stats 是 grid（见上），.pur.head 仍是 flex —— 说明块 .pur.tip 必须显式 display:block */
.pur.head { font-size: 16px; flex-wrap: wrap; align-items: baseline; }
.pur.head b { color: var(--text-strong); }
.pur.head .sub { margin-left: auto; }
.pur.head .phase { font-weight: 700; }
.pur.head .phase.up { color: var(--green); }
.pur.head .phase.down { color: var(--blue); }
.pur.head .phase.warn { color: var(--amber); }
/* 抬头那一行也会出现「为什么这格是空的」（1.5 的总请求数：llama.cpp 没有请求计数器，
   而这块面板一进来看见的就是这个 `--`）。胶囊样式原先是 `.pur .cell.na .na-why` 专属，
   抬头不是 .cell，所以这里补一条同样的——**逐字照抄那套值**，不是另起一套：
   同一个标签在两处长得不一样，读的人会以为它们不是一回事。 */
.pur.head .na-why {
  font-style: normal; font-size: 11px; font-weight: 600; line-height: 1.6;
  color: var(--muted); background: var(--panel-3);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px;
}
/* 速率读数行：原来的走势图换成纯数字。
   这一行元素本身就是 `<div class="pur rate-row">`（.pur 与 .rate-row 在同一个元素上，
   没有 .pur 祖先），所以一律用复合类选择器，别写成后代选择器。 */
.pur.rate-row {
  margin-top: 14px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 28px;
  font-size: 12px; color: var(--muted);
}
.rate-stat {
  display: inline-flex; align-items: baseline; gap: 8px;
  padding-right: 24px; border-right: 1px solid var(--border);
}
.rate-stat:last-of-type { border-right: none; }
.rate-stat .rate-k { color: var(--muted); font-size: 12px; }
.rate-stat .rate-v {
  color: var(--text-strong); font-size: 20px; font-weight: 650; line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.rate-stat .rate-v em { font-style: normal; font-size: 12px; font-weight: 500; color: var(--muted); margin-left: 3px; }
.rate-stat .rate-peak {
  color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums;
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px;
}
/* 速率为 0：数字照样给（0 是真实读数），但用弱化色，和「正在跑」区分开 */
.rate-stat.rate-zero .rate-v { color: var(--muted-2); }
.rate-stat.na .rate-v { color: var(--muted-2); }
/* 「空闲」标签：和缺值格子的原因标签同一套视觉语言 */
.rate-idle {
  font-style: normal; font-size: 11px; font-weight: 600; line-height: 1.6;
  color: var(--muted); background: var(--panel-3);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px;
}
.rate-note { color: var(--muted-2); font-size: 11px; }
.pur.recent { margin-top: 12px; flex-direction: column; gap: 6px; font-size: 11px; color: var(--muted); }
.pur.turn {
  align-items: baseline; flex-wrap: wrap; gap: 14px;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 8px 12px; font-size: 12.5px; background: var(--panel-2);
}
.pur.turn .turn-id { font-weight: 650; color: var(--text-strong); white-space: nowrap; }
.pur.turn .turn-id i { font-style: normal; color: var(--muted); margin-left: 6px; font-weight: 400; }
.pur.turn .tk b { color: var(--text-strong); }
.pur.turn .tk.dim { color: var(--muted); }
/* 说明块。**`display: block` 必须显式写**：`.pur` 是 flex（见上面），早先这里没覆盖，
   于是说明里的每段文字、每个 <b>、每个 <code> 都成了独立的 flex item 横向排开。
   只有一行时看不出问题，一旦换行就碎成若干竖条——所以别把它改回继承 `.pur`。 */
.pur.tip {
  display: block; margin-top: 10px; font-size: 13px; line-height: 1.85;
  color: var(--muted);
}
/* 全宽卡片里一行能排 200+ 字符，读起来很累；两列排布把行宽压回正常范围。
   只有够宽时才分列，窄屏仍是一列。 */
@media (min-width: 1180px) {
  .pur.tip { columns: 2; column-gap: 36px; }
  .pur.tip.mk { padding-left: 26px; }
}
.pur.tip b { color: var(--text); }
.pur.tip.mk { position: relative; padding-left: 26px; }
.pur.tip .mk { position: absolute; left: 0; top: 0; color: var(--muted-2); font-weight: 700; }
.pur .seg {
  margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--border);
  font-size: 13px; font-weight: 650; color: var(--text-strong);
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.pur .seg .sub { font-size: 12px; font-weight: 400; color: var(--muted); }
/* 1.5 的小节容器。1.5 / 1.5.1 / 1.5.2 / 1.5.3 各自一个带边界的盒子——
   原先只用一条 border-bottom 分隔线把四段串成一条长带，扫读时找不到"这一节到哪结束"。
   嵌套深度只有**两级**（.card > .pur.box > .pur .cell），别再加层：
   最里层的 .cell 本来就有自己的边框和底色，是唯一不允许被外框盖住的一层，
   所以在盒子里必须让它浮在盒底色之上。
   盒子底色用 --panel-2（浅色 #f9fafb / 夜间 #161b22），比 .card 的 --panel-grad 低一档：
   浅色下是"凹陷的分区"，夜间下同样下沉。**不要**用 --panel-3：它在浅色下是 #f3f4f6，
   而 .cell 缺值态（.cell.na）是透明底，两者会撞成同一个灰，虚线边框就看不出来了。
   1.5.x 四个盒子都是同一个元素形态，改样式只改这一处。
   **`display: block` 必须显式写**，和下面 `.pur.tip` 是同一个坑：`.pur` 是 flex，
   盒子若继承它，标题和指标格会变成**同一行的两个 flex 项并排**——
   表现是标题占左边一条、`.pur.stats` 被压成 160px 单列竖着堆。
   不报错，只是排版静默坏掉（这一条是被实测的 getBoundingClientRect 抓出来的）。
   **`min-width: 0` 同样不能省**：弹性/网格项默认 `min-width: auto`，
   会被内容的 min-content 宽度撑住而不按轨道收缩。 */
.pur.box {
  display: block;
  margin-top: 12px;
  min-width: 0;
  background: var(--panel-2);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px;
}
/* 1.5 的 headline 盒子：它没有小节标题（那两个数不需要口径说明），
   所以盒内只有一行 .pur.head，别再套 .stats 的间距。 */
.pur.box > .pur.head { margin-top: 0; }
/* 小节标题。.seg 原本是 .pur 的**后代**（`<div class="pur"><div class="seg">`），
   现在两个类写在同一个元素上（且位于 .pur.box 内），所以必须用复合类 .pur.seg
   才压得住 `.pur .seg` 的 margin-top 与 border-top——两类同为 (0,2,0)，
   优先级相同，靠这条规则排在后面取胜。 */
.pur.seg {
  margin-top: 0; padding: 0 0 10px;
  border-bottom: 1px solid var(--border);
}
.pur.seg + .pur.stats { margin-top: 12px; }
/* 元信息行：讲的是「上面那组数字属于哪段时间、可信到什么程度」，不是又一个读数。
   和数值格必须长得不一样——它一旦看起来像 cell，读者就会把它当指标去比较。
   告警态用 .pur.warn（双类，同样是为了压过 .pur .seg 的 color）。 */
.pur.meta {
  margin-top: 12px; padding-top: 8px;
  border-top: 1px dashed var(--border);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  font-size: 12px; color: var(--muted);
}
.pur.meta > span:not(.mk-i) + span { margin-left: 14px; }
.pur.meta .mk-i {
  font-size: 11px; font-weight: 600; color: var(--muted);
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px;
}
.pur.meta b { color: var(--text-strong); font-variant-numeric: tabular-nums; }
.pur.meta .sub { color: var(--muted-2); font-size: 11px; }
.pur.warn { color: var(--amber); }
.pur.warn b { color: var(--amber); }
.pur.tip code { font-size: 10.5px; padding: 0 4px; }
/* 行内告警文字（原先是写死的十六进制色，浅色主题下必须换掉） */
.warn-ink { color: var(--amber); }
.down-ink { color: var(--red); }

details.market { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
details.market > summary { cursor: pointer; color: var(--text); font-size: 13px; font-weight: 500; }
details.market > summary:hover { color: var(--blue); }
details.market > summary .sub { margin-left: 8px; font-size: 11px; }
.mk-scroll { margin-top: 10px; overflow-x: auto; }
table.mk { width: 100%; min-width: 460px; border-collapse: collapse; font-size: 12px; }
table.mk th, table.mk td {
  padding: 9px 12px; border-bottom: 1px solid var(--border);
  text-align: left; vertical-align: middle; font-size: 12.5px;
}
table.mk th { color: var(--muted); font-weight: 600; white-space: nowrap; background: var(--panel-2); }
table.mk tbody tr:nth-child(even) td { background: var(--panel-2); }
table.mk tbody tr:hover td { background: var(--accent-soft); }
table.mk td.n { font-family: var(--mono); font-size: 12.5px; }
table.mk td { color: var(--text); }
table.mk td.n, table.mk th.n { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
table.mk td.n { color: var(--text-strong); }
table.mk .sub { font-size: 11px; color: var(--muted); }

/* ============================ 模型页 ============================ */
.model-filter {
  display: grid; gap: 10px; margin: 0 0 12px;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--panel-2);
}
.model-filter .mf-row { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.model-filter .mf-label {
  flex: none; min-width: 36px; padding-top: 5px;
  font-size: 12px; color: var(--muted); font-weight: 600;
}
.model-filter .mf-chips { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.mf-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text); cursor: pointer; font-family: inherit;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 999px; padding: 4px 11px;
  transition: border-color .12s, background .12s, color .12s;
}
.mf-chip:hover:not(:disabled) { border-color: var(--text-strong); }
/* 选中态：深色实心（HF 风格），不靠颜色细微差别区分 */
.mf-chip.on {
  background: var(--btn-primary-bg); border-color: var(--btn-primary-bg); color: var(--btn-primary-ink); font-weight: 600;
}
.mf-chip .mf-n {
  font-size: 11px; color: var(--muted);
  background: var(--panel-3); border-radius: 999px; padding: 1px 6px;
}
.mf-chip.on .mf-n { background: var(--chip-hi); color: var(--chip-hi-ink); }
.mf-chip.dead { opacity: .45; cursor: not-allowed; }
.model-filter .mf-tail { font-size: 12px; color: var(--muted); }
.model-filter .mf-tail b { color: var(--text-strong); }
.mf-clear {
  background: none; border: none; padding: 0; cursor: pointer; font-family: inherit;
  color: var(--blue); font-size: 12px; text-decoration: underline;
}
.model-sec-count { color: var(--muted); font-size: 12px; font-weight: 400; }

.model-groups { display: grid; grid-template-columns: 1fr; gap: 20px; width: 100%; }
.model-section { width: 100%; min-width: 0; }
.model-sec-title {
  margin: 0 0 10px; color: var(--text-strong); font-size: 13px; font-weight: 650;
  display: flex; align-items: center; gap: 8px;
}
.model-sec-title::before {
  content: ""; width: 3px; height: 14px; border-radius: 2px; background: var(--accent);
}
.model-section-llama .model-sec-title::before { background: var(--muted-2); }
.model-grid {
  display: grid; width: 100%; min-width: 0;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 12px;
}
.model {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 13px 15px; cursor: pointer; background: var(--panel);
  transition: border-color .15s, box-shadow .15s, transform .15s;
  min-width: 0; position: relative;
}
.model:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); transform: translateY(-1px); }
/* 本机起不来的卡片（data-no-start）：**没有点击这回事**。
   手势换成 not-allowed、不要 hover 抬起——抬起来就是在说"点我"，
   而这张卡点下去会被后端 409 拒掉（判据见 handlers.handleSwitch 的 CheckDeployable 一段）。
   只在「显示全部」时才看得到它们，所以这套样式不是常态。 */
.model[data-no-start="1"] { cursor: not-allowed; }
.model[data-no-start="1"]:hover {
  border-color: var(--border); box-shadow: none; transform: none;
}
/* —— 2.1 灰卡两档（管理者裁决 2026-11-09）——
   ② .mute 灰：权重实物在（本机或主库）但本机显存放不下；③ .deepmute 深灰：
   本机与主库都查无这份权重。实底换色**不是压透明度**——透明度把字和边框一起
   糊掉，截图里灰因反而最难读；灰卡是「往下沉」，字必须还是字。悬停不回暖、
   不抬起：灰卡不参与交互是设计意图，不是坏了。 */
.model.mute { background: var(--card-mute-bg); border-color: var(--card-mute-edge); }
.model.deepmute { background: var(--card-deep-bg); border-color: var(--card-deep-edge); }
.model.mute:hover, .model.deepmute:hover { box-shadow: none; transform: none; }
.model.mute .m-name { color: var(--card-mute-ink); }
.model.deepmute .m-name { color: var(--card-deep-ink); }
.model.mute .m-no, .model.mute .m-kind { color: var(--card-mute-ink); }
.model.deepmute .m-no, .model.deepmute .m-kind { color: var(--card-deep-ink); }
/* chip 在灰底上换一层 own 的白面（跟着 --panel 走两个主题都成立），
   否则 --panel-2 的 chip 底色和白灰卡面糊成一片。 */
.model.mute .chip, .model.deepmute .chip { background: var(--panel); }
.model.mute .chip b, .model.deepmute .chip b { color: var(--text); }
.model.mute[data-no-start="1"]:hover { border-color: var(--card-mute-edge); }
.model.deepmute[data-no-start="1"]:hover { border-color: var(--card-deep-edge); }
.model .m-top { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.model .m-no {
  flex: none; font-family: var(--mono);
  font-size: 11px; color: var(--muted);
  background: var(--panel-3); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 2px 6px;
}
.model .m-name {
  flex: 1 1 auto; min-width: 0;
  font-weight: 600; font-size: 13.5px; line-height: 1.4; color: var(--text-strong);
  font-family: var(--mono);          /* 展示名就是权重文件名，等宽更好逐字符核对 */
  /* 文件名最长 70+ 字符：两行夹紧，完整名字在 title 里 */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; word-break: break-all;
}
.model .m-state {
  flex: none; font-size: 11.5px; white-space: nowrap; color: var(--muted);
  display: inline-flex; align-items: center; gap: 5px;
}
.model .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--dot, var(--muted-2)); }
.model.off .dot { --dot: var(--muted-2); }
.model.on .dot { --dot: var(--green); }
.model .m-kind { font-size: 11.5px; color: var(--muted); }
.model .m-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; }
.model .chip {
  font-size: 11px; color: var(--muted);
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 3px 8px; white-space: nowrap;
}
.model .chip b { font-weight: 600; color: var(--text); }
.model .chip:first-child b { color: var(--amber); }   /* 量化高亮 */
.model .chip.vision { border-color: var(--purple-border); background: var(--purple-soft); color: var(--purple); }
.model .chip.vision b { color: var(--purple); }
/* 「本机不可用」与「未核对」必须与「可切换」看得出区别：看不出区别的状态等于没有状态。
   两者也不同色——false 是「确实不行」（红），null 是「没核对成」（灰），不是一回事。 */
.model .chip.down { border-color: var(--red-border); background: var(--red-soft); color: var(--red); }
.model .chip.down b { color: var(--red); font-weight: 600; }
.model .m-state.down { color: var(--red); }
.model .m-state.down .dot { --dot: var(--red); }   /* .dot 是 .m-state 的子节点 */
.model .m-state.unknown { color: var(--muted-2); }
.model.on { border-color: var(--green-border); background: var(--green-soft); }
.model.on .m-state { color: var(--green); font-weight: 600; }
/* 正在载入 / 刚就绪 */
.model.loading { border-color: var(--accent-hover); background: var(--accent-soft); animation: pulse 1.2s ease-in-out infinite; cursor: default; }
.model.loading .m-state { color: var(--amber); font-weight: 600; }
.model.loading .dot { --dot: var(--amber); }
.model.just-ready { border-color: var(--green-border); }
.model.just-ready .m-state { color: var(--green); }
.model.just-ready .dot { --dot: var(--green); }
.model-empty { padding: 20px 4px; }

/* ============================ 表格 ============================ */
.bench-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 10px; }
.bench-bar label { color: var(--muted); font-size: 13px; }
.bench-info { color: var(--muted); font-size: 13px; }
.bench-scroll { max-height: 520px; overflow: auto; border: 1px solid var(--border); border-radius: var(--radius); }
/* min-width 只保底：1360 是早先按列宽上限拍的，实测 1180 已够放全部列，
   少一点横向滚动。窄屏仍可左右滚（外面 .bench-scroll 负责）。 */
table.bench { width: 100%; min-width: 1180px; border-collapse: separate; border-spacing: 0; font-size: 13px; }
table.bench th, table.bench td {
  border-bottom: 1px solid var(--border); padding: 8px 10px; text-align: left; white-space: nowrap;
}
table.bench thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel-2); color: var(--muted); font-weight: 600;
  font-size: 12px; letter-spacing: .02em;
  box-shadow: inset 0 -1px 0 var(--border-strong);
}
table.bench tbody tr { transition: background .12s; }
table.bench tbody tr:hover td { background: var(--panel-2); }
/* 悬停时首列挂一条强调色：视线在长表里不会跟丢行（表格常有 16 列） */
table.bench tbody tr:hover td:first-child { box-shadow: inset 2px 0 0 var(--accent-hover); }
/* 执行失败的行：整行压成警示色（原先用行内 style 写死十六进制色） */
table.bench td { color: var(--text); font-variant-numeric: tabular-nums; }

#bs-history-table { min-width: 720px; width: 100%; }
#bs-history-table th:nth-child(1), #bs-history-table td:nth-child(1) {
  width: 170px; max-width: 170px; overflow: hidden; text-overflow: ellipsis;
}
#bs-history-table td:nth-of-type(1) { white-space: nowrap; }

/* ============================ 基准测试仪表盘 ============================ */
.bs-meta-grid, .bs-summary-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px;
}
.bs-meta-item, .bs-stat, .bs-panel, .bs-latest-wrap, .bs-raw {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
}
.bs-meta-item { padding: 10px 12px; background: var(--panel-2); }
.bs-meta-key { display: block; color: var(--muted); font-size: 11px; letter-spacing: .03em; margin-bottom: 5px; }
.bs-meta-val {
  color: var(--text); font-size: 12.5px; line-height: 1.55;
  /* 长字符串（一长串 vllm 启动参数、容器名）必须能断，否则它会顶宽整个网格 */
  overflow-wrap: anywhere; word-break: break-word;
}
/* 启动参数是命令行：等宽 + 自己滚动，别让逐字符换行把卡片撑成一整屏 */
.bs-meta-item.wide-cmd .bs-meta-val {
  font-family: var(--mono); font-size: 11.5px; white-space: pre-wrap; max-height: 108px; overflow: auto;
}
.bs-summary-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.bs-stat { padding: 12px 14px; background: var(--panel-2); }
.bs-stat-k { display: block; color: var(--muted); font-size: 12px; margin-bottom: 4px; }
.bs-stat-v { display: block; font-size: 22px; font-weight: 650; color: var(--text-strong); font-variant-numeric: tabular-nums; }
.bs-stat.up .bs-stat-v { color: var(--green); }
.bs-stat.down .bs-stat-v { color: var(--red); }
.bs-stat.accent .bs-stat-v { color: var(--blue); }
.bs-stat.warn .bs-stat-v { color: var(--amber); }
/* 评测结论徽标：未评估用灰，不能和「通过」的绿混在一起 */
.bs-eval {
  display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px;
  white-space: nowrap; background: var(--panel-3); color: var(--muted); border: 1px solid var(--border);
}
.bs-eval.up { background: var(--green-soft); color: var(--green); border-color: var(--green-border); }
.bs-eval.down { background: var(--red-soft); color: var(--red); border-color: var(--red-border); }
.bs-eval.muted { background: var(--panel-3); color: var(--muted); }

/* 两张图：宽屏并排（同一量纲的两张图并排才好比），窄屏自动堆叠 */
/* 两张图**始终纵向**排列，各占整行。
   曾经 1500px 以上就并排，实测得不偿失：内容区有 max-width 兜底，2K 屏上并排
   每张图也只有 635px，而纵向是 1312px——横轴刻度挤成一团、柱状图的用例名折三行、
   图下注脚被压成细长条。并排只省一次滚动，代价是两张图都读不清，所以直接不做并排。 */
.bs-panels { display: grid; grid-template-columns: 1fr; gap: 12px; }
.bs-panel { padding: 14px; }
.bs-panel h3, .bs-latest-head h3 { margin: 0 0 10px; font-size: 13.5px; font-weight: 650; }
.bs-chart {
  height: 300px; border-radius: var(--radius); background: var(--panel-2);
  border: 1px solid var(--border); overflow: hidden;
}
.bs-chart.empty { display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 13px; }
.bs-chart-note { margin-top: 8px; line-height: 1.7; }
.bs-chart-note.hidden { display: none; }
.bs-svg { width: 100%; height: 100%; display: block; }
/* 图内线条/文字全部走 token：浅底上要更淡的网格、更深的文字 */
.bs-grid { stroke: var(--chart-grid); stroke-width: 1; }
.bs-axis-line { stroke: var(--chart-axis); stroke-width: 1; }
.bs-ref-line { stroke: var(--chart-ref); stroke-width: 1; stroke-dasharray: 5 5; }
.bs-ref-label { fill: var(--muted); font-size: 11px; }
.bs-axis { fill: var(--muted); font-size: 11px; }
.bs-axis-unit { fill: var(--muted); font-size: 11px; font-weight: 600; }
.bs-point { fill: var(--text-strong); font-size: 11px; font-weight: 600; }
.bs-latest-wrap { padding: 14px; }
.bs-latest-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.bs-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.bs-raw { padding: 12px 14px; }
.bs-raw summary, .bs-detail summary { cursor: pointer; color: var(--blue); font-size: 13px; }
.bs-detail-box { margin-top: 8px; color: var(--muted); line-height: 1.8; min-width: 420px; }
.bs-detail-box b { color: var(--text); }

/* ============================ 日志 / 步骤 ============================ */
.steps { color: var(--muted); line-height: 1.8; margin-bottom: 8px; }
.steps b { color: var(--text); }
.logs {
  background: var(--panel-2); border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);   /* 终端窗口的左边条 */
  border-radius: var(--radius); padding: 12px 14px;
  max-height: 533px; overflow: auto;
  font-family: var(--mono); font-size: 12px; line-height: 1.65;
  color: var(--text); white-space: pre-wrap; word-break: break-word;
  margin: 0;
}

/* ============================ MTP 对比 ============================ */
.seq-list { display: flex; flex-direction: column; gap: 10px; }
.seq-card {
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 13px 15px;
}
.seq-card .seq-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.seq-card .seq-desc { color: var(--muted); font-size: 13px; margin: 6px 0 10px; }
.seq-card .seq-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.seq-progress { margin-top: 2px; }
/* 序列步骤里失败的那一行（app.js 里 `class="down-row"`）。
   **这条规则曾经缺失**：类名写了、style.css 里没有对应规则，于是失败的行与成功的
   行长得一模一样——结果表里唯一要盯的那一行没有标记。同 .stage 那一处是同一类静默失效。 */
table.mk tbody tr.down-row td { color: var(--red); }
table.mk tbody tr.down-row td:first-child { border-left: 2px solid var(--red); }

/* ============================ 启动参数弹窗 ============================ */
.start-ov {
  position: fixed; inset: 0; background: rgba(17, 24, 39, .4);
  backdrop-filter: blur(2px); z-index: 999;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.start-modal {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 20px 22px;
  width: min(540px, 94vw); max-height: 88vh; overflow-y: auto; box-shadow: var(--shadow-md);
}
.start-modal h3 { margin: 0 0 6px; font-size: 15px; color: var(--text-strong); }
.start-field { margin: 14px 0 6px; font-size: 13px; color: var(--muted); }
.start-opts { display: flex; flex-wrap: wrap; gap: 8px; }
.num-btn {
  padding: 7px 15px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--panel); color: var(--text); cursor: pointer; font-size: 13px; font-family: inherit;
  transition: border-color .12s, background .12s, color .12s;
}
.num-btn:hover { border-color: var(--text-strong); }
.num-btn.sel { border-color: var(--btn-primary-bg); background: var(--btn-primary-bg); color: var(--btn-primary-ink); font-weight: 600; }
.start-foot { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

/* ---------- 启动卡片里的「合理性计算器」（见 launchstd） ---------- */
/* 每张卡一行：卡名 + 一个比例输入框。混装机上两行并排看得见"谁分得多"。 */
.ratio-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
.ratio-row .ratio-card { flex: 1; font-size: 12.5px; color: var(--text); }
.ratio-row .ratio-card b { color: var(--text-strong); }
.ratio-row input[type=number] {
  width: 72px; padding: 5px 8px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--panel); color: var(--text); font-family: inherit; font-size: 13px;
}
/* 判定面板：每条一个颜色 + 一个标题 + 一段"算出来的数"。 */
.adv-box { margin-top: 14px; border-top: 1px dashed var(--border); padding-top: 12px; }
.adv-src { font-size: 11.5px; color: var(--muted); margin-bottom: 8px; }
.adv-rule { display: flex; gap: 8px; margin: 7px 0; font-size: 12.5px; line-height: 1.6; }
.adv-rule .adv-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; margin-top: 5px; background: var(--muted); }
.adv-rule .adv-body { flex: 1; }
.adv-rule .adv-title { color: var(--text-strong); font-weight: 600; }
.adv-rule .adv-detail { color: var(--muted); }
.adv-rule.ok .adv-dot { background: var(--green); }
.adv-rule.warn .adv-dot { background: var(--amber); }
.adv-rule.fail .adv-dot { background: var(--red); }
.adv-rule.unknown .adv-dot { background: var(--muted); border: 1px solid var(--border-strong); }
.adv-rule.fail .adv-title { color: var(--red); }
.adv-rule.warn .adv-title { color: var(--amber); }
.adv-total { font-size: 12.5px; margin-bottom: 6px; }
/* 备注/标准警告行复用 §7 已有的 .adv-warn（琥珀底框），这里不重复定义：
   顶层重复定义会静默覆盖前者（uitest 6.5 会拦），所以只留那一处。 */

/* ---------- ⑦ 硬件与能力（§7） / ⑧ 选型建议（§8） ---------- */
/* 圈码与 §N 是同一个序（①=§1 … ⑪=§11）。两个都写出来是让读者一眼看到
   「这一节在正文里叫什么号」——它们永远相等，只写一个也不算错。 */
/* 令牌输入行：写操作鉴权。样式刻意低调——它是运维项，不该抢主视觉。 */
.token-row { display: flex; gap: 6px; margin-bottom: 4px; }
.token-row input {
  flex: 1 1 auto; min-width: 0; font-size: 11px; padding: 4px 6px;
  background: var(--panel-3); border: 1px solid var(--border); border-radius: 6px;
  color: var(--text);
}
.token-state { font-size: 10.5px; line-height: 1.5; margin-bottom: 6px; }
.token-state code { font-size: 10px; }
/* 左侧那枚徽标可点：点一下直接打开设置并聚焦到令牌输入框（见 app.js renderAuthState
   里的 nav.onclick）。不给颜色，只给手势 —— 颜色由 .ok/.bad/.warn 决定，别在这里覆盖。 */
.token-state.clickable { cursor: pointer; }
.token-state.ok { color: var(--green); }
.token-state.bad { color: var(--red); }
.token-state.warn { color: var(--amber); }

/* 证据分层图例：实测 / 厂商规格 / 未核实 */
.ev-legend { display: flex; flex-wrap: wrap; gap: 18px; font-size: 12px; color: var(--muted); }

/* 能力矩阵与目录里的"本机在用"行 */
table.bench tr.row-active { background: var(--green-soft); }
table.bench tr.row-active td { font-weight: 500; }
.tiny { font-size: 10.5px; color: var(--muted); line-height: 1.5; }

/* 本机实测硬件：复用全局 .hw-grid（文件上部已定义），**不要再定义一遍**——
   重复定义会连带改到「2.2 硬件信息」那张宿主硬件卡（同一个 class）。
   这条是踩过的坑：tests/test_frontend_js.py 现在会拦住顶层选择器的重复定义。 */
.hw-item {
  background: var(--panel-3); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; display: flex; flex-direction: column; gap: 2px;
}
.hw-k { font-size: 11px; color: var(--muted); }
.hw-v { font-size: 14px; font-weight: 600; color: var(--text-strong); }
.hw-v i { font-style: normal; font-size: 11px; font-weight: 400; color: var(--muted); }

/* 表格里的多行单元格（矩阵格子：t/s + TTFT + goodput + 证据） */
table.bench td .tiny { display: block; }

/* 推荐结果里「没有候选满足约束」的提示：要显眼，因为它是**拒绝给结论**，不是出错 */
.steps.down {
  color: var(--red); background: var(--red-soft);
  border: 1px solid var(--red-border); border-radius: 8px; padding: 10px 12px; line-height: 1.7;
}

/* ==================== 设置浮层（不属于任何视图） ==================== */
/* 这里不再写圈码：设置是右上角齿轮打开的浮层，不占导航序号。
   圈码留给导航顺序，浮层不参与编号，写一个「⑩」只会和 ⑩ 配置 撞车。 */
/* 主题选择器：三张卡片式选项，各带一个小色板预览。
   用 <button role="radio"> 而不是 <select>——主题是"看一眼就知道"的东西，
   预览比名字有用；同时也比原生下拉更好做键盘可达（aria-checked 已设）。 */
.theme-picker { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.theme-opt {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding: 12px; text-align: left; cursor: pointer; font: inherit; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius);
  transition: border-color .15s, background .15s;
}
.theme-opt:hover { border-color: var(--border-strong); }
.theme-opt.on { border-color: var(--accent-hover); background: var(--accent-soft); }
.theme-opt b { font-size: 13.5px; color: var(--text-strong); }
.theme-opt .hint { font-size: 11.5px; }
/* 色板预览：三条横杠 = 画布 / 卡片 / 强调色。**用字面色是刻意的**——
   它要展示的是"那个主题长什么样"，必须与实际主题解耦，不能跟着当前主题变。 */
.theme-swatch {
  display: flex; gap: 3px; width: 46px; height: 26px; padding: 3px;
  border-radius: var(--radius-sm); border: 1px solid var(--border); overflow: hidden;
}
.theme-swatch i { flex: 1; border-radius: 2px; }
.sw-light { background: #ffffff; }
.sw-light i:nth-child(1) { background: #f3f4f6; }
.sw-light i:nth-child(2) { background: #d6dae0; }
.sw-light i:nth-child(3) { background: #ffd21e; }
.sw-night { background: #0b0f19; border-color: #30363d; }
.sw-night i:nth-child(1) { background: #161b22; }
.sw-night i:nth-child(2) { background: #30363d; }
.sw-night i:nth-child(3) { background: #ffd21e; }
.sw-system { background: linear-gradient(105deg, #ffffff 0 50%, #0b0f19 50% 100%); }
.sw-system i:nth-child(1) { background: #d6dae0; }
.sw-system i:nth-child(2) { background: #30363d; }
.sw-system i:nth-child(3) { background: #ffd21e; }

/* 导航底部的"写令牌 / 主题"入口：整块可点，跳到设置页 */
.token-jump {
  display: block; width: 100%; text-align: left; cursor: pointer; font: inherit;
  background: none; border: 0; padding: 0; margin-bottom: 6px;
}
.token-jump .token-state { margin-bottom: 2px; }
.token-jump-hint { font-size: 10.5px; color: var(--muted-2); }
.token-jump:hover .token-jump-hint { color: var(--accent-hover); text-decoration: underline; }

/* ==================== 设置浮层（右上角齿轮） ==================== */
/* 位置：视口右上角固定。用 fixed 而不是绝对定位——它要在滚动时始终够得着。
   z-index 高于侧边导航（10）与卡片，低于任何未来的模态遮罩。 */
.gear-btn {
  position: fixed; top: 14px; right: 16px; z-index: 60;
  width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  padding: 0; cursor: pointer; color: var(--muted);
  background: var(--panel); border: 1px solid var(--border); border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: color .15s, border-color .15s, transform .15s;
}
.gear-btn:hover { color: var(--text-strong); border-color: var(--border-strong); }
.gear-btn:active { transform: scale(.94); }
.gear-btn[aria-expanded="true"] { color: var(--accent-ink); background: var(--accent); border-color: var(--accent-hover); }
.gear-btn:focus-visible { outline: 2px solid var(--accent-hover); outline-offset: 2px; }

/* 浮层本体：贴着齿轮往下展开，从右边对齐 */
.settings-pop {
  position: fixed; top: 56px; right: 16px; z-index: 61;
  width: 430px; max-width: calc(100vw - 32px);
  max-height: calc(100vh - 72px); overflow-y: auto;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}
.settings-pop[hidden] { display: none; }   /* hidden 属性会被 flex/其他 display 规则覆盖，显式声明 */
.set-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; background: var(--panel);
  border-bottom: 1px solid var(--border);
}
.set-head b { font-size: 13.5px; color: var(--text-strong); }
.set-close {
  width: 26px; height: 26px; padding: 0; cursor: pointer; font: inherit; line-height: 1;
  color: var(--muted); background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
}
.set-close:hover { color: var(--text-strong); background: var(--panel-3); border-color: var(--border); }
.set-body { padding: 4px 16px 16px; }
.set-block { padding: 14px 0; border-bottom: 1px solid var(--border); }
.set-block:last-child { border-bottom: 0; padding-bottom: 4px; }
.set-block > h3 {
  margin: 0 0 10px; font-size: 13px; font-weight: 650; color: var(--text-strong);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
}
.set-block .hint { font-size: 11.5px; line-height: 1.65; margin-top: 6px; }

/* 窄栏里的主题选择器：三列等宽、色板缩小，一行放下（浮层只有 430px） */
.settings-pop .theme-picker { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.settings-pop .theme-opt { padding: 8px; align-items: center; gap: 6px; }
.settings-pop .theme-opt b { font-size: 12px; }
.settings-pop .theme-swatch { width: 100%; height: 22px; }

/* 令牌行：输入框吃掉剩余宽度，两个按钮不换行 */
.set-token-row { display: flex; gap: 6px; flex-wrap: wrap; }
.set-token-row input {
  flex: 1 1 160px; min-width: 0; font-size: 12px; padding: 6px 8px;
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.set-token-row input:focus { outline: none; border-color: var(--accent-hover); }

/* 关于：窄栏里改成单列，键值一行 */
.settings-pop .hw-grid { grid-template-columns: 1fr; gap: 6px; }
.settings-pop .hw-item { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 10px; padding: 6px 10px; }
.settings-pop .hw-v { font-size: 12px; text-align: right; word-break: break-all; }

@media (max-width: 560px) {
  .settings-pop { top: 52px; right: 8px; left: 8px; width: auto; }
  .gear-btn { top: 10px; right: 10px; }
}

/* ==================== ⑨ 天梯图（§9） ==================== */
/* 为什么是 CSS 条而不是 SVG：这张图文字密集，而 SVG 用 preserveAspectRatio="none"
   拉伸时 viewBox 宽度必须贴近整行的像素宽，否则文字会被拉变形（见 renderBenchBarChart
   的注释）。用 HTML/CSS 条则文字不缩放、可选中，而且**颜色全走 CSS 变量**，
   切主题自动跟着变，不必进 refreshAfterThemeChange() 逐点重上色。

   行的栅格与轴行（.lad-axisrow）**必须用同一套列宽**，否则刻度与条对不齐 ——
   这是这张图唯一靠"两处写法保持一致"的地方，改一处要改两处。 */
.lad-sec { margin-bottom: 14px; }
.lad-sec-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 6px 0; border-bottom: 1px solid var(--border); margin-bottom: 6px;
}
.lad-sec-head > b { color: var(--text-strong); font-size: 13px; }

.lad-row, .lad-axisrow {
  display: grid;
  grid-template-columns: 30px 62px minmax(110px, 1.3fr) 66px minmax(150px, 2.6fr) 92px 44px 52px 76px;
  gap: 8px; align-items: center;
}
.lad-row { min-height: 30px; border-radius: var(--radius-sm); }
.lad-row:hover { background: var(--panel-2); }
.lad-axisrow { min-height: 18px; }

.lad-rank { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; text-align: right; }
.lad-tier { font-size: 11px; }
.lad-name { min-width: 0; display: flex; flex-direction: column; }
.lad-name > b {
  color: var(--text-strong); font-size: 12.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lad-fw { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lad-val { text-align: right; font-variant-numeric: tabular-nums; font-size: 12.5px; }
.lad-val > i { font-style: normal; color: var(--muted); font-size: 10.5px; }
.lad-n, .lad-stab { text-align: right; }
.lad-src { text-align: right; }

/* 轨道：档带在最底层（全高），中位数条在上半部，实测区间须线在下半部。
   三层分开画是有意的 —— 叠在同一条线上会互相遮住，而这三件事（档、中位数、
   实测范围）恰好是这张图要说清楚的三件不同的事。 */
.lad-track { position: relative; height: 22px; }
/* 档带。**零宽度的档要看得见**：档的共同区间经常退化成一个点（两行的实测区间只在
   某一个值上相接，例如 [118,120] 与 [120,122]），此时 width=0 —— 若只靠背景色，
   这一档在图上就彻底消失了，而「这两行同档」恰恰是整张图最要说的一句话。
   所以左右各留 1px 边框：内容盒为 0 时两条边框叠在一起，正好画出一根 2px 的竖线 ——
   **一个点本来就该画成一个点**。这不是给了它宽度，是给了它可见性。 */
.lad-band {
  position: absolute; top: 0; bottom: 0; border-radius: 3px;
  background: var(--accent-soft); border: 1px solid var(--accent-border);
}
.lad-bar {
  position: absolute; top: 3px; height: 10px; border-radius: 3px;
  background: var(--blue); opacity: .85;
}
.lad-whisk { position: absolute; top: 17px; height: 2px; background: var(--muted-2); opacity: .8; }
/* 虚线 = 2≤n<3：区间本身也不可靠，样式就要与 n≥3 的实线**看得出区别**，
   否则「这条须可信吗」只能靠去数 n 列。 */
.lad-whisk.dashed {
  background: repeating-linear-gradient(90deg, var(--muted-2) 0 3px, transparent 3px 6px);
}
/* 须线的端点小竖线：没有它，两个相邻的须线看不出各自的起止。 */
.lad-whisk::before, .lad-whisk::after {
  content: ""; position: absolute; top: -4px; height: 10px; width: 0;
  border-left: 2px solid var(--muted-2);
}
.lad-whisk::before { left: 0; }
.lad-whisk::after { right: 0; }

.lad-axis { position: relative; height: 16px; grid-column: 5; }
.lad-tick {
  position: absolute; top: 0; font-style: normal; white-space: nowrap;
  color: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums;
}
.lad-tick::before {
  content: ""; position: absolute; left: 0; top: -3px; width: 1px; height: 3px;
  background: var(--chart-axis);
}

.lad-warn {
  border-left: 3px solid var(--amber-border); background: var(--amber-soft);
  color: var(--amber); border-radius: var(--radius-sm);
  padding: 6px 10px; margin-top: 6px; font-size: 12px; line-height: 1.7;
}
.lad-warn + .lad-warn { margin-top: 4px; }
.lad-empty {
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  padding: 14px; color: var(--muted); font-size: 12.5px; line-height: 1.8;
}
/* 排在梯子之上的一句提示（整榜都没判上档时才有），与下面的分段拉开一点距离。 */
.lad-tip { margin-bottom: 12px; }

/* 窄屏：先丢掉两列"辅助信息"（框架/稳定性），保住名次、条与数值 —— 图先要能读，
   再谈信息全不全。丢掉的两列信息在行的 title 与 n 列里仍有线索。 */
@media (max-width: 900px) {
  .lad-row { grid-template-columns: 26px 54px minmax(90px, 1.2fr) minmax(120px, 2fr) 78px 40px 70px; }
  .lad-axisrow { grid-template-columns: 26px 54px minmax(90px, 1.2fr) minmax(120px, 2fr) 78px 40px 70px; }
  .lad-row .lad-fw, .lad-row .lad-stab { display: none; }
  .lad-axis { grid-column: 4; }
}

/* ==================== ⑩ 配置（§10） ==================== */
/* 这一页有两条特殊的渲染纪律，样式要帮着表达（详见 app.js 顶部那一段）：
   ① 「没配」与「配了但不对」必须是**看得出区别**的两种样子——
      .tag.warn（未配置/无从核对/权重目录还没就位）与 .tag.down（部署目录不存在）
      不能混成同一种灰。注意（2026-10-09 起）：**权重目录**缺失走 .tag.warn，
      只有**部署目录**（compose/script）缺失才走 .tag.down——权重目录缺了只是"本机
      还没下这份权重"，模型照常按「尚未下载」显示，不是故障；部署目录缺了切模型真的会失败。
      两者共用一套颜色会把常态说成故障，这正是这次要修的。见 fillConfigDir 里的 spec.weights。
   ② gguf_audit 的 missing 要**原文照登**（含大小写不符与重复项），
      所以 .cfg-missing 用等宽字逐个列，不做任何视觉上的归并。 */

/* 目录字段：一栏一项，窄屏自动单列（不写 @media，省得和别处的响应式规则打架） */
.cfg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
.cfg-field {
  border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px;
  background: var(--panel-2); display: flex; flex-direction: column; gap: 6px;
}
.cfg-field-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.cfg-field-head code { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.cfg-tags { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.cfg-field input[type=text] { font-family: var(--mono); font-size: 12px; }
.cfg-field input[type=text]:disabled { background: var(--panel-3); color: var(--muted); cursor: not-allowed; }
.cfg-follow { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }

/* 10.2 登记表：来源标注、核对结果、JSON 编辑栏 */
.cfg-h3 { font-size: 13.5px; margin: 14px 0 6px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.cfg-audit { margin-bottom: 4px; }
.cfg-warn { margin-bottom: 8px; }
.cfg-warn-box {
  color: var(--amber); background: var(--amber-soft);
  border: 1px solid var(--amber-border); border-radius: var(--radius);
  padding: 10px 12px; font-size: 12.5px; line-height: 1.7;
}
.cfg-warn-box b { color: var(--text); }
.cfg-warn-box ul { margin: 6px 0 0 18px; }
.cfg-ok-box {
  color: var(--green); background: var(--green-soft);
  border: 1px solid var(--green-border); border-radius: var(--radius);
  padding: 10px 12px; font-size: 12.5px; line-height: 1.7;
}
.cfg-ok-box b { color: var(--green); }
/* 缺失清单：等宽、逐个一行。**刻意不排序、不去重、不做大小写折叠**——
   原文照登正是这一段存在的理由，任何"整理"都会把要看的线索抹掉。 */
.cfg-missing { font-family: var(--mono); font-size: 12px; line-height: 1.9; }
.cfg-missing code { word-break: break-all; }
.cfg-edit { margin-top: 10px; }
.cfg-edit summary { cursor: pointer; font-size: 13px; color: var(--text); }
.cfg-json-label { display: block; margin-top: 8px; font-size: 12px; color: var(--muted); }
.cfg-json {
  width: 100%; margin-top: 4px; font-family: var(--mono); font-size: 12px; line-height: 1.6;
  white-space: pre; overflow-wrap: normal; overflow-x: auto;
}

/* 10.3 部署产物 */
.cfg-targets { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; font-size: 13px; }
.cfg-targets label { display: flex; align-items: center; gap: 6px; color: var(--text); }
.cfg-deploy { margin-top: 10px; }
.cfg-deploy-head { font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
.cfg-deploy-head b { color: var(--text-strong); }
.cfg-target-card {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 8px 10px; margin-bottom: 8px; background: var(--panel-2);
}
.cfg-target-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; margin-bottom: 4px;
}
.cfg-target-head code { font-family: var(--mono); font-size: 11px; color: var(--muted); word-break: break-all; }
.cfg-raw { margin-top: 6px; }
.cfg-raw summary { cursor: pointer; font-size: 12px; color: var(--muted); }
.cfg-raw pre { margin-top: 6px; max-height: 280px; }

/* ==================== ⑪ 使用说明（§11） ==================== */
/* 2026-10-11：八节改成**分节页签**（页签条即目录，见 §11 的 activateTab），原先的
   页内目录 .doc-toc 与 jumpTo() 滚动跳转一并撤掉——正文里只剩下面这种页面跳转按钮
   （11.1 快速上手那三个，用 switchViewTo() 切视图，不是 <a href="#...">：hash 被
   视图路由占用，写 href 会污染路由，点一下再刷新就掉回总览）。 */
.toc-link {
  font: inherit; font-size: 12.5px; cursor: pointer; padding: 3px 9px;
  color: var(--blue); background: var(--panel-2);
  border: 1px solid var(--border); border-radius: 999px;
}
.toc-link:hover { color: var(--accent-ink); background: var(--accent); border-color: var(--accent-hover); }
.toc-link:focus-visible { outline: 2px solid var(--accent-hover); outline-offset: 1px; }
/* 正文里的页面跳转链接（复用同一个按钮样式，但不要药丸底色） */
.steps .toc-link { padding: 0 2px; background: none; border: 0; border-radius: 0; text-decoration: underline; }
.steps .toc-link:hover { background: none; color: var(--blue); }
#view-usage h3 { margin: 14px 0 6px; }
#view-usage table.bench td:first-child { white-space: nowrap; }
#view-usage .steps p { margin: 6px 0; }

/* ---------- 下拉框里的长文本 ----------
   起因：⑥ 容器日志的容器名是 `1cat-qwen36-35b-a3b-heretic-nvfp4-server`
   （40+ 字符），被塞进 minmax(230px) 的栅格列后，下拉箭头正好压在 "server" 上，
   显示成 `...nvfp4-se□er` —— 看着像名字里真有个方框。

   两条修法：
   ① select **吃掉 label 里的剩余空间**（数字输入框仍保持定宽：它要的是"短而稳"）；
   ② 内容超长时用省略号收尾，让"被截断"看得出来，而不是被硬裁成半个字。 */
.bench-form > label > select { flex: 1 1 auto; min-width: 0; text-overflow: ellipsis; }

/* 说明：这里**不再**给 form-wide 单独写 select 规则。
   已有的 `.bench-form > label.form-wide { align-items: flex-start }` 是给
   Prefill 档位芯片那种"会折行"的控件用的，改成 center 会把它带歪；
   而 `flex: 1 1 auto` 已经能让选择框吃掉整行剩余宽度，够长名字排下。
   （顶层选择器重复定义会被 tests/test_frontend_js.py 拦下。） */

/* =============================================================================
 * 仪器感细节（主题基调不变，只加"质感"）
 * 三条自我约束：
 *   ① 所有颜色走 token，组件里不写字面色（换主题只改 token 块）；
 *   ② 动画都要有 prefers-reduced-motion 出口——这是给人看数据的工具，不是宣传页；
 *   ③ 动效必须**表示状态**（正在切换 / 数据新鲜度），不做纯装饰的循环动画。
 * ========================================================================== */

/* 滚动条（Chromium）：细、圆、跟主题走。原生滚动条在暗色下是一道刺眼的白条。 */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--track); }
::-webkit-scrollbar-thumb {
  background: var(--thumb); border-radius: 999px;
  border: 2px solid var(--track);     /* 用轨道色描边 → 滑块看起来更细 */
}
::-webkit-scrollbar-thumb:hover { background: var(--muted-2); }
::-webkit-scrollbar-corner { background: var(--track); }

/* 键盘焦点环：统一成强调色，键盘用户在任何控件上都能一眼看到焦点在哪。
   鼠标点击不会触发（:focus-visible），所以不会满屏描边。 */
:focus-visible { outline: 2px solid var(--accent-hover); outline-offset: 2px; }

/* 数据新鲜度指示灯（左下角）。
   它**不是**装饰：绿色 = 最近一次 /api/status 成功；黄色 = 超过 9 秒没成功
   （后端挂了或浏览器把定时器节流了）。灰 = 尚未拿到过数据。
   这一条是刻意的：给一个"永远在呼吸的绿灯"会假装健康，而这个控制台的原则是
   "读不到就说读不到"。 */
.live-led {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 5px; vertical-align: middle;
  background: var(--muted-2); box-shadow: 0 0 0 3px var(--panel-3);
  transition: background .25s, box-shadow .25s;
}
.live-led[data-state="ok"] {
  background: var(--green); box-shadow: 0 0 0 3px var(--green-soft), 0 0 8px var(--green);
  animation: led-breathe 2.6s ease-in-out infinite;
}
.live-led[data-state="stale"] {
  background: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft);
}

/* 进度条扫光 */
@keyframes bar-scan {
  from { background-position: 190% 0, 0 0; }
  to   { background-position: -90% 0, 0 0; }
}
/* 指示灯呼吸：幅度很小，只是"活着"的暗示 */
@keyframes led-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}

/* ============================================================
   ⓪ 集群总览（AR10 §八）：算力端选择器 + 实时集群卡片网格
   ============================================================ */
.sent-selector { display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 0 8px; }
.sent-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 11px; border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--panel); color: var(--text); cursor: pointer; font-size: 13px;
  transition: border-color .12s, background .12s, color .12s;
}
.sent-chip:hover { border-color: var(--accent); }
.sent-chip.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-ink); }
.sent-chip.off { opacity: .45; }
.sent-chip b { font-weight: 600; }
.sent-chip em { font-style: normal; color: var(--accent-ink); font-weight: 600; }
.sent-chip .hint { color: var(--muted); font-size: 12px; }
.sent-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted-2); vertical-align: middle;
}
.sent-dot[data-st="on"] { background: var(--green); box-shadow: 0 0 6px var(--green); }
.sent-dot[data-st="off"] { background: var(--red); }

.sent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.sent-card {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--panel); padding: 12px 14px; display: flex; flex-direction: column; gap: 8px;
}
.sent-card.off { opacity: .5; }
.sent-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sent-card-head b { font-size: 14px; }
.sent-card-head code { font-size: 11.5px; color: var(--muted); }
.sent-cardsub { display: flex; gap: 12px; flex-wrap: wrap; color: var(--muted); font-size: 12.5px; }
.sent-gpus { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 6px; }
.sent-gpu {
  border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px;
  display: flex; flex-direction: column; gap: 2px; background: var(--panel-2);
  font-variant-numeric: tabular-nums;
}
.sent-gpu b { font-size: 12px; color: var(--text); }
.sent-gpu span { font-size: 11.5px; color: var(--muted); }

/* ---------- 排序（表头点击） ----------
   可点与不可点必须**看起来不一样**：表头没有排序样式时，用户不知道能点，
   或者点了发现没反应以为是坏的。⇅ 表示"这列可以排"，↑/↓ 表示"正按这列排"。 */
/* 只有**可排序的表**才给这套样式：选择器里必须带 table[data-sort-key]。
   写成 table.bench th 会把⑥使用说明那几张纯文字表（术语表、路径表）也画上 ⇅，
   而它们没有 data-sort-key、点了不会有任何反应——一个看得见却点不动的箭头，
   比没有箭头更坏：读的人会以为排序坏了。 */
table[data-sort-key] th { cursor: pointer; user-select: none; }
table[data-sort-key] th::after { content: "⇅"; opacity: .3; margin-left: 4px; font-size: 10px; }
table[data-sort-key] th.sort-on { color: var(--accent); }
table[data-sort-key] th.sort-on::after { content: "↑"; opacity: 1; }
table[data-sort-key] th.sort-on.sort-desc::after { content: "↓"; }

/* 10.5 排序默认值那一卡：一列一行，窄屏自动换行 */
.sort-cfg-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.sort-cfg-row select { flex: 1 1 160px; min-width: 0; }

/* ② 卡片排序条：与筛选条同一行的观感，但不抢筛选的位 */
#card-sort { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 10px; font-size: 12.5px; }
#card-sort select { flex: 0 1 auto; min-width: 0; }

/* 「其它服务」那一行（① 总览）：门类 + 名称 + 三态。样式与 2.1 卡片上的 chip 一致，
   但**不能复用 .model .chip**——那一行不在 .model 里，复用等于没有样式（静默失效）。 */
#current .chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 6px; margin: 2px 4px 2px 0;
  border: 1px solid var(--border); border-radius: 999px; background: var(--panel-3);
  font-size: 12px; color: var(--muted);
}
#current .chip b { font-weight: 600; color: var(--text); }
#current .tag { margin-left: 2px; }

/* 通用小字（此前只有作用域内的 .sub/.muted，直接写在别处就没样式了） */
.muted { color: var(--muted); }

/* ==================== 1.1 完整启动参数 ====================
   一行命令 + 一个复制按钮。这一块存在的理由：页面原先只有 `max_len=262144 / 并发=4`，
   而张量并行、KV 缓存 dtype、attention backend 这些真正决定「怎么起的」的东西
   得去机器上敲 docker inspect 才看得到。
   white-space: pre-wrap 让长命令自己折行——它的长度取决于启动参数个数
   （190 上那条 vllm 命令 356 字符），不折行会把整张卡片撑出横向滚动条。 */
.cmd-args {
  margin: 6px 0 2px; padding: 8px 10px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.cmd-args .tag { margin-right: 6px; }
.cmd-args code {
  display: block; margin-top: 6px; font-family: var(--mono);
  font-size: 12px; line-height: 1.5; color: var(--text);
  white-space: pre-wrap; word-break: break-all;
  -webkit-user-select: all; user-select: all;   /* 点一下就能整条选中，便于手动复制 */
}
/* 行为相关的环境变量：MTP 这类「命令行里没有、只由环境变量开」的开关挂在这里。
   与命令区分开渲染（不带 code 的块级样式），因为两者不是同一种事实：
   上面是 argv，这里是引擎读到的环境。 */
.cmd-env { margin: 6px 0 2px; font-size: 12px; color: var(--muted); }
.cmd-env code {
  display: inline-block; margin: 2px 4px 0 0; padding: 1px 6px;
  font-family: var(--mono); font-size: 12px;
  background: var(--panel-3); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text);
}

/* ==================== 3.3 悬停浮层 ====================
   每行悬停显示该条记录的完整信息组合。
   必须是 position: fixed：表格在 .bench-scroll { overflow: auto } 里，
   浮层若做它的子元素会被滚动区裁掉。挂 body + 每帧算位置（见 benchTipShow）。
   z-index 取 60：压住内容，但低于 .settings-pop(61) 与启动弹窗(.start-ov 999)——
   设置面板是用户主动打开的，不该被一个悬停提示盖住。 */
.bench-tip {
  position: fixed; z-index: 60;
  width: 460px; max-width: calc(100vw - 24px);
  max-height: calc(100vh - 24px); overflow-y: auto;
  padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
  font-size: 12px; color: var(--text); line-height: 1.5;
}
.bench-tip[hidden] { display: none; }   /* hidden 会被 display 规则覆盖，显式声明 */
.bench-tip .tip-head {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding-bottom: 6px; margin-bottom: 4px; border-bottom: 1px solid var(--border);
}
.bench-tip .tip-head b { color: var(--text-strong); }
/* 分节标题：把「是什么配置」与「这个数怎么来的」分开。
   一个 CV 很高的旧记录不该显得和一条干净基线一样权威。 */
.bench-tip .tip-sec {
  display: flex; align-items: center; gap: 6px;
  margin: 8px 0 2px; color: var(--muted); font-weight: 600; font-size: 11px;
  letter-spacing: .04em;
}
.bench-tip .tip-row { display: flex; gap: 8px; align-items: baseline; }
.bench-tip .tip-k { flex: 0 0 122px; color: var(--muted); }
.bench-tip .tip-v { flex: 1 1 auto; color: var(--text); word-break: break-all; }
/* 启动参数在浮层里用等宽 + 预折行：它是**要拿去复制**的那一项，
   而浮层宽度固定，不折行就会撑出横向滚动条。 */
.bench-tip .tip-code {
  display: block; margin-top: 4px; padding: 6px 8px;
  font-family: var(--mono); font-size: 11px; line-height: 1.5;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  white-space: pre-wrap; word-break: break-all; color: var(--text);
  -webkit-user-select: all; user-select: all;
}

/* 4.1 基准测试的失败提示（#bs-fail，贴在页面顶部）。
   存在的理由：失败原因原先只写进页尾 <details> 里那枚 #bs-result <pre>，默认折叠，
   于是「启动被拒」在页面上没有任何可见痕迹。颜色全部走 token，夜间主题自动跟着变
   （theme_test 禁止组件规则里出现字面色值）。 */
.bench-fail {
  margin: 0 0 10px; padding: 9px 11px;
  background: var(--red-soft); border: 1px solid var(--red-border);
  border-radius: var(--radius); color: var(--red);
  font-size: 12px; line-height: 1.65;
}
.bench-fail[hidden] { display: none; }   /* hidden 会被上面的 display/padding 规则盖掉，显式声明 */
.bench-fail code { font-size: 11px; }
.bench-fail .btn { margin-top: 6px; }

/* 尊重系统的"减少动态效果"：把动画与过渡压到接近 0。
   注意不是 display:none——状态提示（进度条、指示灯）仍要能看见，只是不动。 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ==================================================================
   1.4b 实时时间带（配合 app.js 的 pb* 一组函数）
   ------------------------------------------------------------------
   为什么单独一条带：上面那些格子是 /api/status 的**3 秒快照**，
   而实测一个 prefill 只持续 1.1 秒、功耗在 1.00 秒内就跳完了——
   3 秒的格子撞不上它。这条带按 1Hz 常驻（按钮切 5Hz）单独采，
   所以它更可能抓到一个已经结束的突发。两块**并存**，不互相冒充。

   三条与全站一致的显示纪律：
     · 缺失 ≠ 0：没读到的格子写「—」，不写 0（0 是测到的结果）
     · 阶段分段着色：prefill/decode/busy/waiting 各有底色，一眼看出哪段是哪段
     · 颜色全部走 token：浅色值在文件顶部的 :root，夜间值在 [data-theme="night"]
       （两处都要有，否则暗色下静默继承浅色值）
   ================================================================== */
.perfband {
  margin-top: 14px; padding: 12px 0 0;
  border-top: 1px solid var(--pb-line);
}
.pb-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.pb-title { font-weight: 600; color: var(--text-strong); font-size: 14px; }
.pb-title .hint { color: var(--muted); font-weight: 400; font-size: 12px; margin-left: 6px; }
.pb-btn {
  margin-left: auto; cursor: pointer;
  background: var(--panel-2); color: var(--text-strong);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 5px 12px; font-size: 12px; font-family: inherit;
}
.pb-btn:hover:not(:disabled) { background: var(--panel-3); }
.pb-btn:disabled { opacity: .55; cursor: default; }
.pb-note { font-size: 12px; color: var(--muted); }
.pb-note .warn { color: var(--red); }
/* canvas 的 CSS 宽度交给容器（设备像素比在 JS 里按 clientWidth 处理），
   高度定格在 120px：它是"走势条"，不是可交互图表。 */
.pb-canvas {
  display: block; width: 100%; height: 164px;
  background: var(--panel-2); border: 1px solid var(--pb-line);
  border-radius: var(--radius-sm);
}
.pb-legend {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-top: 8px; font-size: 12px; color: var(--muted);
}
.pb-chip { display: inline-flex; align-items: center; gap: 5px; }
.pb-chip i {
  display: inline-block; width: 11px; height: 11px; border-radius: 3px;
  border: 1px solid var(--pb-line);
}
.pb-sep { width: 1px; height: 12px; background: var(--pb-line); }
.pb-rows {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px; margin-top: 10px;
}
.pb-cell {
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 7px 10px; min-width: 0;
}
.pb-cell i { display: block; font-style: normal; color: var(--muted); font-size: 11px; margin-bottom: 3px; }
.pb-cell b { display: block; color: var(--text-strong); font-size: 13px; font-variant-numeric: tabular-nums; }
.pb-cell.dim b { color: var(--muted); font-weight: 500; }
.pb-help { margin-top: 10px; }
.pb-help summary { cursor: pointer; font-size: 12px; color: var(--muted); }
.pb-help .pur.tip { margin-top: 8px; }


/* ==================================================================
   1.4c 图形数据模块（仪表盘 / 环形图 / 柱图 / 走线）

   为什么单独一块：1.4 从前只有一行文字和一排格子，而"占比"这类量用文字
   表达不出来——12.3% 与 96.1% 都是一行数字，要看第二遍才知道哪个是哪个。
   下面四格各自回答一个问题：
     阶段构成  这 10 分钟到底花在哪（注入 / 生成 / 排队 / 空闲）
     效率      每焦耳出多少 token（数据中心真正比价的量）
     健康      为什么慢（温度 / 时钟 / 降频 / PCIe 链路）
     可用率    掉过线没有（掉了几次、最后一次是什么原因）

   显示纪律与全站一致：**缺失 ≠ 0**。读不到就画一条虚线底槽 + 写「未采集」，
   绝不画成 0（0 是一个测到的结果，画成 0 等于报了一个假的读数）。
   ================================================================== */

.pggrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px; margin-top: 12px;
}
.pg { min-width: 0; }
/* 仪表盘是正方偏扁的：中心要留出写数字的位置，太宽会让弧显得平。 */
.pg-canvas {
  display: block; width: 100%; height: 118px;
  background: var(--panel-2); border: 1px solid var(--pb-line);
  border-radius: var(--radius-sm);
}
.pg-note { margin-top: 6px; font-size: 12px; }

/* 数据模块的栅格：**定 2 列**，不是 auto-fit。
 *
 * 从前写的是 repeat(auto-fit, minmax(280px, 1fr))，而这一块正好 4 个 .dc-sec：
 * 在 1088px 的容器里 auto-fit 会挑 3 列（273px 够摆 3 个 280px 的最小宽度），
 * 于是第 4 块被挤到第二行，**第二行右边留下一个 2/3 宽的空洞**。
 * 那一片空白在界面上看起来像一个没加载出来的面板，实测截图里非常显眼。
 *
 * 2 列既填满栅格、又让每块宽到 538px——里面那些 KPI 是两列排的，
 * 宽一点标签才不至于折成三行。窄屏（<860px）自动落成一列。 */
.dcm {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px; margin-top: 14px;
}
@media (max-width: 860px) {
  .dcm { grid-template-columns: minmax(0, 1fr); }
}
/* 每一格自带边框：四格是四个独立的问题，不该被读成一张大表的四列。 */
.dc-sec {
  min-width: 0; padding: 10px 12px 12px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.dc-sec h3 {
  margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--text-strong);
}
.dc-sec h3 .hint { color: var(--muted); font-weight: 400; font-size: 11px; margin-left: 6px; }
/* 环形图：正方形，居中，宽度封顶——直径再大也不会多读出信息。 */
.dc-donut {
  display: block; width: 100%; max-width: 190px; height: 150px;
  margin: 0 auto;
}
.dc-bars { display: block; width: 100%; height: 104px; margin-top: 8px; }
.dc-spark { display: block; width: 100%; height: 56px; margin-top: 8px; }
.dc-kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 6px; margin-top: 8px;
}
.dc-kpi {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px 9px; min-width: 0;
}
.dc-kpi i {
  display: block; font-style: normal; color: var(--muted); font-size: 11px;
  margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dc-kpi b {
  display: block; color: var(--text-strong); font-size: 14px;
  font-variant-numeric: tabular-nums;
}
/* na = 这一项**没采集到**（不是 0）。灰一点，并靠 .dc-kpi.na 让读者一眼跳过。 */
.dc-kpi.na b { color: var(--muted); font-weight: 500; }
.dc-kpi.warn b { color: var(--red); }
.dc-legend {
  display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center;
  margin-top: 8px; font-size: 11px; color: var(--muted);
}
.dc-chip { display: inline-flex; align-items: center; gap: 5px; }
.dc-chip i {
  display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  border: 1px solid var(--pb-line);
}
.dc-badge {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: 11px; font-weight: 600; border: 1px solid var(--border-strong);
  color: var(--muted); background: var(--panel);
}
.dc-badge.bad { color: var(--red); border-color: var(--red-border); background: var(--red-soft); }
.dc-badge.ok { color: var(--green); border-color: var(--green-border); background: var(--green-soft); }

/* 1.1 卡尾的「本类缺省」选择器：一行，标签在左、下拉在右。
   它故意做得不显眼（次要色、窄），因为它改的是"以后默认显示谁"，不是现场动作——
   与卡里的「启动 / 停止」视觉上必须分得开，否则会被当成第三个启停按钮。 */
/* 1.1 的卡上没有「本类缺省」选择器（2026-11-08 用户定）：缺省只在 ② 模型页选
   （在那里点一张卡的启动 = 切换并记为本类缺省）。1.1 是"此刻这一类是谁"的一屏事实，
   能在那里改的东西越多，它就越不像事实、越像一个需要维护的配置页。 */
