/* =============================================================================
 * 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;

  /* 文字 */
  --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;
  --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);
  --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;

  /* 文字 */
  --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;
}

* { 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: center; gap: 10px;
  margin: 0 4px 18px;
  font-size: 15px; font-weight: 700; color: var(--text-strong);
  letter-spacing: -.01em;
  /* 名称与副标题竖排：横排时「V100 推理控制台」会在 224px 侧栏里折成两行，
     把品牌块撑成三行、与下面的分组标题挤在一起。 */
  flex-wrap: wrap; row-gap: 0;
}
/* 品牌标：HF 风的黄色方块 + 深色字，整个侧栏唯一的高饱和色块 */
.topnav .brand::before {
  content: "⬢";
  display: grid; place-items: center;
  width: 26px; height: 26px;
  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;
}
.nav-sep {
  margin: 14px 6px 6px;
  padding-top: 10px;
  border-top: 1px solid var(--border);   /* 分组之间划一道细线，导航从"一列按钮"变成"分区" */
  font-size: 11px; font-weight: 600; color: var(--muted-2);
  text-transform: uppercase; letter-spacing: .08em;
}
.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：圈码是导航序号，正文分节号在重组导航后与它不再一一对应，
   把分节号标出来，省得用户按「⑥」去找第 6 节却看到「5.1 容器日志」。 */
.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; }

/* 卡片 */
.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-sep, .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; }

/* 表单的「动作行」：勾选项 + 主/次按钮 + 报告链接排成一行、独占整行。
   不归组的话 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); }

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

/* 切换中横幅 */
.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;
}
.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); }
.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; }
/* 「没有值」的指标格：必须与「值为 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); }
/* 速率读数行：原来的走势图换成纯数字。
   这一行元素本身就是 `<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); }
.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); }
.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; }

/* ============================ 启动参数弹窗 ============================ */
.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(440px, 92vw); 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; }

/* ---------- ⑧ 硬件与能力 / ⑨ 选型建议 ---------- */
/* 令牌输入行：写操作鉴权。样式刻意低调——它是运维项，不该抢主视觉。 */
.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; }
.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; }
}

/* ==================== ⑦ 使用说明 ==================== */
/* 页内目录：用 button 而不是 <a href="#...">——hash 被视图路由占用了，
   写 href 会污染路由（点一下目录再刷新就掉回总览）。样式做成链接的样子即可。 */
.doc-toc {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--border);
  font-size: 12.5px; color: var(--muted);
}
.doc-toc b { color: var(--text-strong); font-weight: 650; margin-right: 2px; }
.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 .card[id^="usage-"] { scroll-margin-top: 12px; }
#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; }
}

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