/* ==========================================================================
   tokengo 面板样式（观感对齐 FreeLLMAPI 面板的那套排法）
   --------------------------------------------------------------------------
   这套样式是照 freellmapi/client 里那套设计语言重做的，抄的是**排法与刻度**，
   不是抄它的 Tailwind/React 实现。落成六条规矩：

   1. 页面是「一条横线切开的页头 + 一叠卡片」：页头大标题 + 一句话说明，
      底下一条分隔线；内容区一律是卡片（`.card`），卡片里再放表格 / 表单。
   2. 卡片用**发丝边**（1px，颜色是前景色的一成）而不是深色描边 + 阴影；
      阴影只留给浮起来的东西（弹窗、提示条）。
   3. 圆角分三级：控件 8px（按钮 / 输入框）、列表与表格 16px、卡片与弹窗 20px。
   4. 颜色一律走变量，色调是中性灰（对齐上游），蓝色只做「链接 / 焦点 /
      能点的开关」这几处的强调，**主按钮是黑白色反转**（深色模式里白底黑字，
      浅色模式里黑底白字）—— 这是上游最显眼的一条观感。
   5. 数字一律等宽（tabular-nums）+ 右对齐；正文对比度不低于 4.5:1；
      能点的东西一律有 hover / active / focus-visible 三态。
   6. 尺寸只从这套刻度里挑：间距 4/8/12/16/20/24/32，栏高 32（控件）/44（手机）。

   结构（顶层）：.shell > .topbar + .main > .content > .page > (.page-head / .card / .stats)
   ========================================================================== */

/* ---------- 1. 变量：深色（默认） ---------- */
:root {
  /* 三层底色：页面 / 卡片 / 更亮一层（表头、输入框、二级按钮） */
  --bg: #101216;
  --bg-soft: #16181d;
  --surface: #1a1d22;
  --surface-2: #22262c;
  --surface-3: #2b3038;

  /* 发丝边：卡片的边就是「前景色一成」，所以深浅两套自动都对 */
  --line: rgba(255, 255, 255, 0.1);
  --line-strong: rgba(255, 255, 255, 0.2);

  /* 字色三级：正文 / 次要 / 最弱（表头、单位、占位）。最弱的一级也要看清。 */
  --fg: #eceef2;
  --fg-2: #a3aab5;
  --fg-3: #878e99;

  /* 强调色与状态色（各配一个半透明底，给标签和提示用） */
  --accent: #548dff;
  --accent-ink: #0a1020;
  --accent-soft: rgba(84, 141, 255, 0.16);
  --ok: #3fce8f;
  --ok-soft: rgba(63, 206, 143, 0.15);
  --warn: #e8b04b;
  --warn-soft: rgba(232, 176, 75, 0.16);
  --bad: #f2707c;
  --bad-soft: rgba(242, 112, 124, 0.16);

  /* 主按钮 = 前景色反转（上游那条）：按钮底色 / 按钮字色 */
  --btn-solid: #eceef2;
  --btn-solid-ink: #16181d;

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.28);
  --shadow-2: 0 10px 30px rgba(0, 0, 0, 0.45);
  --shadow-3: 0 18px 48px rgba(0, 0, 0, 0.55);

  --radius: 20px; /* 卡片、弹窗 */
  --radius-md: 16px; /* 表格、列表容器 */
  --radius-sm: 8px; /* 按钮、输入框 */
  --bar: 32px;
  --speed: 140ms;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* ---------- 2. 变量：浅色（html[data-theme="light"]） ---------- */
html[data-theme="light"] {
  --bg: #fafafa;
  --bg-soft: #ffffff;
  --surface: #ffffff;
  --surface-2: #f5f5f5;
  --surface-3: #e9e9e9;

  --line: rgba(23, 23, 23, 0.1);
  --line-strong: rgba(23, 23, 23, 0.22);

  --fg: #171717;
  --fg-2: #525252;
  --fg-3: #6b6b6b;

  --accent: #2563eb;
  --accent-ink: #ffffff;
  --accent-soft: rgba(37, 99, 235, 0.1);
  --ok: #0f7a51;
  --ok-soft: rgba(15, 122, 81, 0.1);
  --warn: #8f5d0c;
  --warn-soft: rgba(143, 93, 12, 0.12);
  --bad: #c02c44;
  --bad-soft: rgba(192, 44, 68, 0.1);

  --btn-solid: #1b1e24;
  --btn-solid-ink: #ffffff;

  --shadow-1: 0 1px 2px rgba(20, 26, 34, 0.06);
  --shadow-2: 0 8px 24px rgba(20, 26, 34, 0.1);
  --shadow-3: 0 16px 40px rgba(20, 26, 34, 0.16);
}

/* ---------- 3. 基础 ---------- */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 13px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC",
    "PingFang SC", "Microsoft YaHei", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.01em; }
/* 页头标题比上游小一号（上游 24px 是英文；中文两字 24px 会显得空） */
h1 { font-size: 1.375rem; line-height: 1.25; letter-spacing: -0.02em; }
h2 { font-size: 0.875rem; }
h3 { font-size: 0.8125rem; }

p { margin: 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

code, .code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}

pre.code-block { margin: 0; }

.muted { color: var(--fg-2); }
.small { font-size: 0.75rem; }
.nowrap { white-space: nowrap; }
.tight { margin: 0; }
.centered { text-align: center; }
.spacer { flex: 1 1 auto; }
.num, td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* 数字一律等宽：位数一变，宽度不跳 */
.stat-value, .probe-row, td { font-variant-numeric: tabular-nums; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---------- 4. 骨架：顶栏 + 内容 ---------- */
.shell { min-height: 100vh; display: flex; flex-direction: column; }

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid var(--line);
  /* 半透明 + 毛玻璃：内容从下面滚过去能透出来一点，比一块实色板轻 */
  background: color-mix(in oklab, var(--bg) 82%, transparent);
  backdrop-filter: blur(10px);
}

.topbar-inner {
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 24px;
  height: 56px;
  display: flex;
  align-items: center;
  gap: 4px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin-right: 20px;
  white-space: nowrap;
}
/* 品牌小方块：一个 t，别加渐变 */
.brand::before {
  content: "t";
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
}

/* 顶栏导航：横排一条，选中的那条在底下画一道线（上游的排法），
   不是一块彩色药丸 —— 五条短名并排时，药丸会把顶栏切得一块一块的。 */
.topnav { display: flex; align-items: stretch; align-self: stretch; gap: 2px; }

.topnav > .nav {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: auto;
  padding: 0 14px;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--fg-2);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  transition: color var(--speed) var(--ease);
}
.topnav > .nav:hover { background: none; color: var(--fg); }
.topnav > .nav.active { color: var(--fg); font-weight: 600; }
.topnav > .nav.active::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 0;
  height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--fg);
}

/* 收起来的菜单里的条目（手机上点 ☰ 出来的那列） */
.menu-item {
  display: block;
  width: 100%;
  height: auto;
  padding: 8px 11px;
  text-align: left;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--fg-2);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease);
}
.menu-item:hover { background: var(--surface-2); color: var(--fg); border-color: transparent; }
.menu-item.active { background: var(--surface-2); color: var(--fg); font-weight: 600; }
.menu-item.danger { color: var(--bad); }
.menu-item.danger:hover { background: var(--bad-soft); }

/* 「☾」「☰」这类小方按钮：不带边，只在自己那格里 hover */
.icon-btn {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--fg-2);
  font-size: 0.875rem;
  line-height: 1;
}
.icon-btn:hover { background: var(--surface-2); color: var(--fg); border-color: transparent; }
button.icon-btn { border-color: transparent; background: none; color: var(--fg-2); }
button.icon-btn:hover { border-color: transparent; background: var(--surface-2); color: var(--fg); }

.menu-wrap { position: relative; }
.menu-pop {
  /* 固定定位、贴右上角：它跟遮罩一起放在顶栏外面（见下面 .nav-scrim 的说明），
     不能再靠「相对 ☰ 按钮定位」了 —— 那样会被顶栏的毛玻璃包含块限制住。 */
  position: fixed;
  right: 14px;
  top: 62px;
  min-width: 11rem;
  padding: 5px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow-2);
  /* 手机上条目多、视口矮：给个上限 + 自己能滚，别长出屏幕外看不全 */
  max-height: calc(100dvh - 96px);
  overflow-y: auto;
  overscroll-behavior: contain;
  z-index: 50;
}
.menu-sep { height: 1px; background: var(--line); margin: 6px 4px; }

.nav-toggle { display: none; } /* 只在手机上出现 */

/* 菜单遮罩：盖满整个视口、**全透明**（不挡看内容，只接住「点外面」那一击 → 收起菜单）。
   它必须是 .topbar 的兄弟节点：顶栏带 backdrop-filter，会成为 fixed 后代的包含块，
   那时候 inset:0 只铺满顶栏那 54px，点页面别处根本碰不到遮罩（2026-10-18 用户实测）。
   z-index 45 在顶栏（40）之上、菜单弹窗（50）之下 —— 点哪儿都能收起菜单。 */
.nav-scrim {
  position: fixed;
  inset: 0;
  height: auto;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  cursor: default;
  z-index: 45;
}
.nav-scrim:hover { background: none; border: 0; }

.main { flex: 1 1 auto; }

.content {
  max-width: 72rem;
  margin: 0 auto;
  padding: 28px 24px 72px;
}

.page { display: flex; flex-direction: column; gap: 20px; }

/* 页头：左边大标题 + 一句话，右边按钮；底下一道线把页头和内容切开。
   按钮跟标题**底对齐**（不是顶对齐）—— 顶对齐时按钮会飘在标题上面，看着没靠住。 */
.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--line);
}
.page-head-text { display: flex; flex-direction: column; gap: 3px; min-width: 15rem; }
.page-head-text p { color: var(--fg-2); font-size: 0.75rem; max-width: 56rem; }
.page-head-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* 章节标题（还没包进 .card 的地方用）：前面一小段竖条，把板块切开 */
.page > h2 {
  padding-top: 4px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.page > h2::before {
  content: "";
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--accent);
}

/* ---------- 5. 卡片与板块 ---------- */
/* 一张卡 = 头（标题 + 说明）+ 体。头体之间的空气靠 padding 给，不靠 margin。 */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.card-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 20px 0;
}
.card-head h2 { font-size: 0.875rem; font-weight: 600; }
.card-head p { color: var(--fg-2); font-size: 0.75rem; max-width: 60rem; }
.card-body { padding: 14px 20px 18px; }

/* 卡片里放表格时用这个体：表格铺满整张卡（左右不缩进、上下不缩进），
   表头那行上下各一道线，读起来像卡片自带的一栏 —— 表格再套一层边框会显脏。 */
.card-body.flush { padding: 12px 0 4px; }
.card-body.flush .table-wrap {
  border: none;
  background: none;
  border-radius: 0;
}
.card-body.flush th, .card-body.flush td { padding-left: 20px; padding-right: 20px; }
.card-body.flush thead th {
  background: color-mix(in oklab, var(--surface-2) 70%, transparent);
  border-top: 1px solid var(--line);
}
.card-body.flush tbody tr:last-child td { border-bottom: none; }

/* 旧写法（裸 .panel）继续能用：它自己就是一张卡 */
.panel, .chan-card, .auth-card, .modal, .fold {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.panel { padding: 18px 20px; display: flex; flex-direction: column; gap: 14px; }
/* 卡里的 panel 不再画第二层边框（两层框套在一起最显脏） */
.card .panel, .card .fold { border: none; background: none; border-radius: 0; }
.card .panel { padding: 0; }
.card .fold > summary { padding-left: 0; padding-right: 0; }
.card .fold > *:not(summary) { padding-left: 0; padding-right: 0; }

.fold { overflow: hidden; }
.fold > summary {
  cursor: pointer;
  padding: 12px 20px;
  font-size: 0.8125rem;
  font-weight: 600;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: background var(--speed) var(--ease);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before {
  content: "▸";
  color: var(--fg-3);
  font-size: 0.6875rem;
  transition: transform var(--speed) var(--ease);
}
.fold[open] > summary::before { transform: rotate(90deg); }
.fold > summary:hover { background: var(--surface-2); }
.fold > *:not(summary) { padding: 0 20px 18px; }

/* 数字卡（总览顶上那四个） */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
  gap: 14px;
}
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.stat-title { color: var(--fg-2); font-size: 0.75rem; font-weight: 500; }
.stat-value {
  font-size: 1.6875rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}
.stat-sub { color: var(--fg-3); font-size: 0.6875rem; }

/* 空态：虚线框 + 一句话（上游的空态都是这么一张虚线卡）。
   **空态一定要给出下一步** —— 只说「没有」等于把人晾在那儿。 */
.empty {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-md);
  padding: 32px 20px;
  text-align: center;
  color: var(--fg-2);
  font-size: 0.75rem;
}

/* 卡片网格。**注意别把 <table class="grid"> 也变成网格** —— 表格一旦 display:grid，
   表头和单元格就各自成块、彻底错位（这个坑踩过一次，改名前先看这行注释）。 */
.grid:not(table) {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
}
.split { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }

.chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: 0.75rem;
  color: var(--fg-2);
}

/* 分段控件（时间窗口、测一下发什么、显示多少条）：一条带边的槽 + 里面几个小块，
   选中的那块**反色**（深色模式里白底黑字）。比一排各自带边的 chip 干净，
   而且「现在选的是哪个」一眼就能看出来。 */
.seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-2);
}
.seg > button, .seg > .chip {
  height: 26px;
  padding: 0 11px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--fg-2);
  font: inherit;
  font-size: 0.6875rem;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease);
}
.seg > button:hover, .seg > .chip:hover { background: var(--surface-2); color: var(--fg); }
.seg > button.active, .seg > .chip.active {
  background: var(--fg);
  color: var(--bg);
  font-weight: 600;
}
.seg > .seg-label { padding: 0 6px 0 8px; font-size: 0.6875rem; color: var(--fg-3); }

/* ---------- 6. 表格 ---------- */
/* 表格自己就是一张卡：别再让它裸着铺满整页 */
.table-wrap {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
table { width: 100%; border-collapse: collapse; }
th, td {
  padding: 11px 20px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--line);
}
th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  color: var(--fg-2);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background var(--speed) var(--ease); }
tbody tr:hover { background: color-mix(in oklab, var(--surface-2) 65%, transparent); }
td { font-size: 0.75rem; }
/* 弹窗里那种密排小表（key-value、映射关系）：行矮一点 */
table.tight th, table.tight td { padding: 6px 10px; }
td:first-child, th:first-child { padding-left: 20px; }
td:last-child, th:last-child { padding-right: 20px; }

/* 表头行的等宽数字别跟标题抢眼睛 */
thead th.num { font-weight: 500; }

/* 「按客户端」那一列是 IPv6 这种长串：**别让它把整张表的列宽撑歪**，
   截断显示，鼠标悬停看全（title 里就是原文）。 */
td[data-label="来源"] code {
  display: inline-block;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
/* 错误原因那种长句子：让它正常折行，别一行铺到底 */
td[data-label="原因"] { white-space: normal; overflow-wrap: anywhere; color: var(--fg-2); }

.empty-row td, .empty-cell {
  color: var(--fg-3);
  text-align: center;
  padding: 28px 20px;
  font-size: 0.75rem;
}
.err-cell { color: var(--bad); }

/* 表格里的状态字：成功 / 失败 / 提醒 */
.ok, .bad, .warn { font-weight: 600; }
.ok { color: var(--ok); }
.bad, .bad-text { color: var(--bad); }
.warn { color: var(--warn); }
/* 结果列的小标签统一最小宽度：不然「成功」两个字的窄、『客户端错』宽，整列看着忽宽忽窄 */
td .tag { min-width: 4.5rem; justify-content: center; }

/* ---------- 7. 标签 ---------- */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--fg-2);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.6;
  white-space: nowrap;
}
.tag::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
}
.tag.ok { color: var(--ok); background: var(--ok-soft); border-color: transparent; }
.tag.warn { color: var(--warn); background: var(--warn-soft); border-color: transparent; }
.tag.bad { color: var(--bad); background: var(--bad-soft); border-color: transparent; }
.tag.muted { color: var(--fg-3); }
/* 「视觉」标签用强调色，跟状态色区分开（它不是「好/坏」，是「能力」） */
.tag.vision { color: var(--accent); background: var(--accent-soft); border-color: transparent; }

/* ---------- 8. 开关（一键启用/停用） ---------- */
/* 面板上点一下就启停。**故意做成「灰滑轨 + 白滑块 / 绿滑轨 + 白滑块」的实物开关**：
   只写状态文字会被当成死标签（第一次就是这么被认错的），所以滑轨要够粗够显眼。 */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 2px 7px 2px 1px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--fg-2);
  font: inherit;
  font-size: 0.6875rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease);
}
.switch:hover { background: var(--surface-2); color: var(--fg); }
.switch .knob {
  width: 32px;
  height: 18px;
  border-radius: 999px;
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  position: relative;
  transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease);
  flex: 0 0 auto;
}
.switch .knob::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--fg-2);
  transition: transform var(--speed) var(--ease), background var(--speed) var(--ease);
}
.switch.on { color: var(--ok); }
.switch.on .knob { background: var(--ok); border-color: var(--ok); }
.switch.on .knob::after { transform: translateX(14px); background: #ffffff; }
.switch:disabled { opacity: 0.5; cursor: progress; }
.switch-text { white-space: nowrap; }

/* ---------- 9. 按钮 ---------- */
button, .primary, .ghost, .danger {
  font: inherit;
  font-size: 0.75rem;
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease),
    border-color var(--speed) var(--ease), opacity var(--speed) var(--ease);
}

button {
  height: var(--bar);
  padding: 0 12px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--fg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  white-space: nowrap;
}
button:hover { background: var(--surface-2); border-color: var(--line-strong); }
button:active { opacity: 0.85; }
button:disabled { opacity: 0.45; cursor: not-allowed; }
button:disabled:hover { background: var(--surface); border-color: var(--line); }

/* 主按钮 = 反色（上游那条）：深色模式白底黑字、浅色模式黑底白字。
   页面上只有「新建 / 保存 / 登录」这类主动作才用它，一页最多一个。 */
.primary {
  background: var(--btn-solid);
  border-color: transparent;
  color: var(--btn-solid-ink);
  font-weight: 600;
}
.primary:hover {
  background: color-mix(in oklab, var(--btn-solid) 88%, var(--fg));
  border-color: transparent;
}
.ghost { background: none; border-color: transparent; color: var(--fg-2); }
.ghost:hover { background: var(--surface-2); color: var(--fg); border-color: transparent; }
.ghost.danger { color: var(--bad); }
.ghost.danger:hover { background: var(--bad-soft); }
/* 上膛状态：第一下点完按钮变实心红，提示「再点一下才真删」 */
.ghost.danger.armed{background:var(--bad);color:#fff;border-color:var(--bad);}
.ghost.danger.armed:hover{background:var(--bad);}
/* 页头那一组操作要像一组：次按钮也带边 —— 光秃秃两个字挨着实心主按钮，看着像没对齐 */
.page-head-actions .ghost:not(:disabled) {
  border-color: var(--line);
  background: var(--surface);
}
.page-head-actions .ghost:not(:disabled):hover {
  border-color: var(--line-strong);
  background: var(--surface-2);
}

/* 表格与卡片里的小按钮（升 / 降 / 移除 / 试一下） */
button.arrow, .chan-actions button, .probe-row button, td button {
  height: 28px;
  padding: 0 10px;
  font-size: 0.6875rem;
}
/* 开关不受小按钮高度影响 */
td button.switch { height: auto; padding: 3px 10px 3px 5px; }

.toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* 「测一下」那行：分段控件后面的说明单独占一小截，不挤在控件里把整条撑高 */
.toolbar .toolbar-hint {
  color: var(--fg-3);
  font-size: 0.6875rem;
  line-height: 1.5;
}
/* 按钮组。**td.actions 要留在外面** —— 单元格一旦成了 flex 容器，整行就散架了。 */
.actions:not(td) { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
td.actions { white-space: nowrap; }
td.actions button + button { margin-left: 6px; }
td.wrap { white-space: normal; overflow-wrap: anywhere; }

/* ---------- 10. 表单 ---------- */
.form-grid {
  display: grid;
  gap: 14px 20px;
  grid-template-columns: minmax(9rem, 14rem) minmax(0, 1fr);
  align-items: start;
}
/* 表单里标签在左、控件在右（上游那套卡片表单的排法）；窄屏再竖过来 */
.form-grid > label { padding-top: 8px; }
label {
  display: block;
  margin-bottom: 6px;
  color: var(--fg-2);
  font-size: 0.75rem;
  font-weight: 500;
}
input[type="text"], input[type="password"], input[type="number"], input[type="search"],
select, textarea {
  width: 100%;
  height: var(--bar);
  padding: 0 11px;
  background: var(--bg-soft);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 0.75rem;
  transition: border-color var(--speed) var(--ease), background var(--speed) var(--ease),
    box-shadow var(--speed) var(--ease);
}
textarea { height: auto; min-height: 5rem; padding: 9px 11px; line-height: 1.5; resize: vertical; }
input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
select {
  cursor: pointer;
  appearance: none;
  padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-3) 50%),
    linear-gradient(135deg, var(--fg-3) 50%, transparent 50%);
  background-position: calc(100% - 16px) 14px, calc(100% - 11px) 14px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
input:hover, select:hover, textarea:hover { border-color: var(--line-strong); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input::placeholder, textarea::placeholder { color: var(--fg-3); }
input:disabled, select:disabled { opacity: 0.55; cursor: not-allowed; }

/* 勾选项那种小胶囊：复选框 + 文字。**别做得比文字高** ——
   否则夹在说明句里会把那一行的节奏撑断（页头「测一下」那几个就是）。 */
.pick, .kind-pick {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 1px 9px 1px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  cursor: pointer;
  font-size: 0.75rem;
  line-height: 1.6;
  transition: border-color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.pick:hover, .kind-pick:hover { border-color: var(--line-strong); }
.kind-group { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 6px;
  max-height: 20rem;
  overflow: auto;
  padding: 2px;
}

/* 密钥、代码块 */
.keybox, .code-block {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  color: var(--fg-2);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* ---------- 11. 渠道列表 ---------- */
/* 一条渠道 = 一行（不是一个方块）。以前是四列卡片墙：卡片一多，最后一行只剩
   一两张、右边空一大片，标题长的那几张还把开关挤到第二行去 —— 现在改成
   一行一条，靠 flex 的 basis 自己折成 1~3 栏，怎么都不会散。 */
.chan-list { display: flex; flex-direction: column; gap: 10px; }

.chan-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 24px;
  padding: 14px 20px;
  transition: border-color var(--speed) var(--ease);
}
.chan-card:hover { border-color: var(--line-strong); }

/* 第一栏：编号 + 名字 + 启停开关。名字太长就省略，**开关永远留在这一行里**
   （以前会自动换行，长名字那几张的开关跑到第二行左边，整张卡看着是歪的）。 */
.chan-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 15rem;
  min-width: 0;
}
.chan-id { color: var(--fg-3); font-size: 0.6875rem; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
.chan-name {
  font-size: 0.8125rem;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chan-head .switch { margin-left: auto; flex: 0 0 auto; }

.chan-url { flex: 1 1 14rem; min-width: 0; }
.chan-url code {
  color: var(--fg-2);
  overflow-wrap: anywhere;
}

.chan-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 16px;
  color: var(--fg-2);
  font-size: 0.75rem;
  /* 独占一行：哪一行都不许因为「模型名」那截长一点就折成两行 —— 有的行两行、
     有的行一行，整列的开关和按钮就会忽高忽低 */
  flex: 1 1 100%;
  min-width: 0;
}
.chan-meta > span { display: inline-flex; align-items: center; gap: 6px; }
.chan-model-row { flex: 0 0 100%; min-width: 0; color: var(--fg-2); font-size: 0.75rem; }
.chan-model-row > .chan-models { display: block; min-width: 0; max-width: 100%; }

/* 「上游协议」那一条：跟模型清单同一行风格（发丝线分隔），勾选框 + 一个保存。
   四个勾选框横排，窄屏换行；不许挤成一行长句 —— 否则「允许转换」会跑到屏幕外。 */
.chan-proto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  flex: 0 0 100%;
  min-width: 0;
  padding: 8px 0 2px;
  border-top: 1px solid var(--line);
  color: var(--fg-2);
  font-size: 0.75rem;
}
.chan-proto .proto-pick {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
}
.chan-proto .proto-pick input { margin: 0; accent-color: var(--accent); }
.chan-proto button.ghost { padding: 2px 10px; font-size: 0.72rem; }

/* 模型清单：一个模型一行 —— 名字 / 上游名 / 用途 / 看图 / 「测」。
   老样子把「名字+用途+测」挤在一串「、」里，一长就折成两行、按钮忽高忽低，
   三种模型挂着的时候根本认不出谁是谁。改成一张小表：行与行之间发丝线，
   用途用颜色区分（对话蓝 / 嵌入绿 / 重排黄），「测」永远对齐在最右边。 */
.cm-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 5px;
  color: var(--fg-3);
  font-size: 0.6875rem;
}
.cm-count {
  min-width: 1.25rem;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--fg-2);
  font-size: 0.625rem;
  line-height: 1.5;
  text-align: center;
}
.cm-list {
  /* 卡片在大屏上很宽：列表别一路铺到最右边，否则名字和「测」之间空出一大片，
     看着像两截断开的东西。56rem 之内右对齐就够看了。 */
  max-width: 56rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-soft);
  overflow: hidden;
}
.cm-row {
  /* 四列固定：名字 / 上游名 / 用途徽章 / 「测」。三行都对得齐，
     不会出现「第二行有上游名、第三行没有」就整行错位的情况。 */
  display: grid;
  /* 名字列按内容宽（最多 16rem），上游名紧跟其后 —— 别让名字列占满半张卡、
     把「→ 上游名」甩到半空中，中间空出一大片。 */
  grid-template-columns: fit-content(16rem) minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px;
  min-height: 34px;
  padding: 7px 12px;
  line-height: 1.5;
}
.cm-row + .cm-row { border-top: 1px solid var(--line); }
.cm-row:hover { background: var(--surface); }
.cm-name {
  overflow: hidden;
  color: var(--fg);
  font-size: 0.8125rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cm-up {
  min-width: 0;
  overflow: hidden;
  color: var(--fg-3);
  font-size: 0.6875rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cm-tags {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
.cm-badge {
  padding: 0 8px;
  border-radius: 999px;
  font-size: 0.625rem;
  line-height: 1.6;
  white-space: nowrap;
}
.cm-chat { background: var(--accent-soft); color: var(--accent); }
.cm-emb { background: var(--ok-soft); color: var(--ok); }
.cm-rr { background: var(--warn-soft); color: var(--warn); }
.cm-other { background: var(--surface-3); color: var(--fg-3); }
.cm-pic {
  border: 1px solid var(--line-strong);
  color: var(--fg-2);
  line-height: 1.4;
}
.cm-probe {
  height: 24px;
  padding: 0 11px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--fg-2);
  font: inherit;
  font-size: 0.6875rem;
  cursor: pointer;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.cm-probe:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--fg); }
.cm-empty { color: var(--fg-3); }
.legacy-key-pool { flex: 0 0 auto; min-width: 0; }
.legacy-key-pool > button { height: 28px; padding: 0 10px; font-size: 0.6875rem; border: 1px solid var(--line-strong); }
.legacy-key-pool fieldset { border: 0; padding: 12px 0 0; margin: 0; min-width: 0; }
.legacy-key-pool form label { display: grid; gap: 6px; margin: 12px 0; }
.legacy-key-pool input { width: 100%; min-width: 0; }
.legacy-key-pool td { overflow-wrap: anywhere; }
.legacy-key-pool td button { margin: 2px; }
/* 卡片里的状态小标签**要贴在文字基线上**：做成和字差不多高，别把这一行撑得忽高忽低 */
.chan-meta .tag {
  padding: 0 7px;
  font-size: 0.625rem;
  line-height: 1.7;
}

/* Keep every channel action, including the credential dialog, on one row. */
.chan-actions {
  display: flex;
  gap: 4px;
  flex-wrap: nowrap;
  flex: 1 1 100%;
  min-width: 0;
  align-items: center;
}
.chan-actions > button, .chan-actions .legacy-key-pool > button { height: 28px; padding: 0 7px; font-size: 0.6875rem; white-space: nowrap; }
.key-pool-modal { max-height: calc(100dvh - 48px); }
.key-pool-modal > .modal-body { min-height: 0; overflow-y: auto; }
.key-pool-modal > header, .key-pool-modal > footer { flex-shrink: 0; }
.failed-call-error { margin: 4px 0; color: var(--fg-2); overflow-wrap: anywhere; }
.chan-card button.switch { height: auto; }

/* 探活 / 测视觉那一行：占满整行，跟在渠道下面 */
.probe-row, .chan-probe {
  flex: 1 1 100%;
  font-size: 0.75rem;
}
.probe-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.chan-probe {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--fg-2);
  overflow-wrap: anywhere;
}
.chan-probe.ok { color: var(--ok); }
.chan-probe.bad { color: var(--bad); }
.chan-empty { padding: 40px 20px; text-align: center; color: var(--fg-3); }

.vision { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.vision-cell { color: var(--fg-2); }

/* 拉取上游清单那块 */
.pull-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pull-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.75rem; }
.pull-list, .pull-rows { display: flex; flex-direction: column; gap: 6px; max-height: 18rem; overflow: auto; }

/* 「获取上游模型」拿回来的那份清单：一个模型一格。勾上的行整格标出来，
   这样「哪几个名字这条渠道认」扫一眼就知道，不用去数勾选框。 */
.m-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 24rem;
  overflow: auto;
  padding: 2px;
}
.m-row {
  display: block;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.m-row.on { border-color: var(--accent); background: var(--surface-3); }
.m-col { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
/* 勾选框和模型名必须在同一行：同一个 flex 行、一起垂直居中 */
.m-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.m-pick {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  /* 全局给 label 留了 6px 下边距，会把勾选框顶上去 —— 这一行里必须清掉，
     不然勾选框和模型名不在同一条中线上（实测差 3px） */
  margin: 0;
}
.m-pick input { display: block; width: 16px; height: 16px; margin: 0; }
/* 名字这一格占满剩下的宽度、长了就用省略号，别把「详情」挤出去 */
.m-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.75rem;
  line-height: 20px;
}
.m-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* 「详情」按钮：小一号（全局 button 是 44px 触摸高度），别把那一行撑到 44px */
.m-line > button { height: 28px; padding: 0 10px; font-size: 0.6875rem; }
.m-tags:empty { display: none; }
.m-tags .tag { font-size: 0.625rem; }
.m-detail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border-radius: var(--radius-sm);
  background: var(--bg-soft);
}
.m-map { font-size: 0.75rem; color: var(--fg-2); overflow-wrap: anywhere; }
/* 详情里那几行：左边固定宽的字段名，右边输入框占满 */
.m-f { display: flex; align-items: center; gap: 8px; }
.m-f > span:first-child { flex: 0 0 6.5rem; color: var(--fg-3); font-size: 0.75rem; }
.m-f input[type="text"] { flex: 1 1 auto; min-width: 0; }
.m-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
@media (max-width: 46rem) {
  .m-row { padding: 9px 10px; gap: 8px; }
  .m-f { flex-wrap: wrap; row-gap: 4px; }
  .m-f > span:first-child { flex: 0 0 auto; }
}

/* ---------- 12. 弹窗 ---------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: color-mix(in oklab, #05070b 60%, transparent);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 6vh 16px 16px;
  overflow: auto;
}
.modal {
  width: 100%;
  max-width: 46rem;
  border-color: var(--line-strong);
  box-shadow: var(--shadow-3);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  animation: pop var(--speed) var(--ease);
}
@keyframes pop {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}
.modal-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: flex-end;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
/* 卡里的操作条不再画第二条线（卡自己就有边） */
.card .modal-foot { border-top: 1px solid var(--line); padding-top: 14px; }

/* 弹窗标题下面那条三段切换（基本信息 / 请求头 N / 模型 N）。
   之前它俩都没样式，就靠默认按钮外观，手机上看着像三块说明文字、不像能按 ——
   用户实测反馈「找不到去上游拉模型的地方」。做法：分段按钮的常态描边、选中项
   加重描边 + 极浅实色底 + 加粗（用户口径：按钮要描边，不要实色大块）。 */
.modal-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: -6px;
}
.modal-tabs .tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  font-size: 0.75rem;
  line-height: 1.5;
  border-radius: 999px;
}
.modal-tabs .tab.active {
  border-color: var(--fg);
  background: var(--surface-3);
  font-weight: 600;
}

/* ---------- 13. 提示条 ---------- */
/* 顶栏那条短提示：浮在内容右上角，**反色药丸**（深色模式白底黑字），
   一眼能看见又不抢内容 */
.toast {
  position: fixed;
  right: 24px;
  top: 72px;
  z-index: 70;
  max-width: min(28rem, calc(100vw - 48px));
  padding: 9px 14px;
  background: var(--btn-solid);
  border: 1px solid transparent;
  border-radius: 12px;
  box-shadow: var(--shadow-2);
  color: var(--btn-solid-ink);
  font-size: 0.75rem;
  font-weight: 500;
  animation: pop var(--speed) var(--ease);
}
.toast:empty { display: none; }

.banner {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--fg-2);
  font-size: 0.75rem;
}
.banner.ok {
  border-color: transparent;
  background: var(--ok-soft);
  color: var(--fg);
}
.banner.warn {
  border-color: transparent;
  background: var(--warn-soft);
  color: var(--fg);
}
.banner.bad {
  border-color: transparent;
  background: var(--bad-soft);
  color: var(--fg);
}
.notice {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--fg-2);
  font-size: 0.75rem;
}

/* ---------- 14. 登录页 ---------- */
.auth-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px 16px;
  background: var(--bg);
}
.auth-card {
  width: 100%;
  max-width: 24rem;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: var(--shadow-2);
}
.auth-card h1 { font-size: 1.1875rem; }

/* ---------- 15. 手机（≤ 46rem）---------- */
@media (max-width: 46rem) {
  .topbar-inner {
    height: auto;
    min-height: 54px;
    padding: 10px 14px;
    flex-wrap: wrap;
    row-gap: 8px;
  }
  .brand { margin-right: auto; }
  .nav-toggle { display: inline-grid; }
  /* 导航收成一条可展开的竖列表，不再跟标题挤一行 */
  .topnav { display: none; }
  .menu-item { height: auto; padding: 7px 10px; }
  .menu-sep { margin: 4px 4px; }
  .content { padding: 18px 14px 56px; }
  .page { gap: 16px; }
  .page-head { padding-bottom: 14px; gap: 12px; }
  .page-head-actions { width: 100%; }
  :root { --bar: 44px; }

  /* 卡里的表格：手机上横着拖很难受，改成一张张卡片（只动包在 .table-wrap 里的数据表，
     弹窗里那种两列小表保持原样，别把它们也拆散）。
     注意：「模型」页 v0.11.3 起不用表格了，它有自己的紧凑卡片（.model-card）。 */
  .table-wrap { border: none; background: none; overflow: visible; }
  .card-body.flush { padding: 10px 0 0; }
  .card-body.flush th, .card-body.flush td { padding-left: 14px; padding-right: 14px; }
  .table-wrap table, .table-wrap thead, .table-wrap tbody, .table-wrap tr, .table-wrap td {
    display: block;
    width: 100%;
  }
  .table-wrap thead { display: none; }
  .table-wrap tbody tr {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    padding: 6px 2px;
    margin-bottom: 10px;
  }
  .table-wrap tbody tr:hover { background: var(--surface); }
  .table-wrap td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    border-bottom: none;
    padding: 7px 12px;
    text-align: left;
  }
  .table-wrap td::before {
    content: attr(data-label);
    color: var(--fg-3);
    font-size: 0.6875rem;
    font-weight: 500;
    flex: 0 0 auto;
  }
  .table-wrap td:first-child, .table-wrap td:last-child { padding-left: 12px; padding-right: 12px; }
  .table-wrap td.num { justify-content: flex-end; }
  .table-wrap td[data-label="操作"] { justify-content: flex-start; }
  .table-wrap td.empty-cell { display: block; }

  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-value { font-size: 1.4375rem; }

  /* 渠道那几行：手机上竖着排，操作条跟在最后一行 */
  .chan-card { gap: 6px; padding: 14px; align-items: flex-start; }
  .chan-head, .chan-url, .chan-meta { flex: 1 1 100%; }
  .chan-head .switch { margin-left: auto; }
  .chan-actions { flex: 1 1 100%; margin-left: 0; }
  .chan-actions > button { flex: 0 0 auto; justify-content: center; }

  .form-grid { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .form-grid > label { padding-top: 0; }

  .modal-backdrop { padding: 12px; }
  .modal { max-width: none; }
  .toast { left: 14px; right: 14px; top: 64px; max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

@media print {
  .topbar, .page-head-actions, .toast { display: none; }
}

/* ── 「模型」页的用途分档条（v0.10.0）：一个「模型」页，用途当标签用
   （参考 freellmapi 的排版）。手机上左右滑动，不折行、也不用下拉。── */
.kind-tabs {
  display: flex;
  flex-wrap: wrap; /* 手机上折成两行，**不横滑** —— 横滑会把「Video」藏在屏幕外，
                       用户报过「手机看不完整」 */
  gap: 6px;
  margin-bottom: 10px;
}
.kind-tabs::-webkit-scrollbar {
  display: none;
}
.kind-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  /* 压住全局 button 的 44px 触摸高度，不然一排按钮占掉半屏 */
  height: 30px;
  padding: 0 10px;
  font-size: 0.7375rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--fg-2);
  white-space: nowrap;
}
.kind-tab:hover {
  background: var(--surface-3);
  color: var(--fg);
}
.kind-tab.on {
  border-color: var(--accent);
  background: var(--surface-3);
  color: var(--fg);
  font-weight: 600;
}
.kind-tab .kind-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--fg-3);
  flex: 0 0 auto;
}
.kind-tab.kind-chat .kind-dot {
  background: #3b82f6;
}
.kind-tab.kind-emb .kind-dot {
  background: #10b981;
}
.kind-tab.kind-image .kind-dot {
  background: #a855f7;
}
.kind-tab.kind-video .kind-dot {
  background: #ec4899;
}
.kind-tab.kind-audio .kind-dot {
  background: #06b6d4;
}
.kind-tab .tab-n {
  font-variant-numeric: tabular-nums;
  font-size: 0.6875rem;
  color: var(--fg-3);
}
.kind-tab.on .tab-n {
  color: var(--accent);
}

.model-detail {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 0;
}
.detail-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.detail-label {
  font-size: 0.7175rem;
  color: var(--fg-3);
}
.detail-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* 类型标记：在「模型名 → 渠道顺序」那张表里也照同一套颜色走 */
.tag.count {
  font-variant-numeric: tabular-nums;
}
.tag.kind-chat {
  border-color: #3b82f6;
  color: #3b82f6;
}
.tag.kind-emb {
  border-color: #10b981;
  color: #10b981;
}
.tag.kind-rerank {
  border-color: #f59e0b;
  color: #f59e0b;
}
.tag.kind-image {
  border-color: #a855f7;
  color: #a855f7;
}
.tag.kind-video {
  border-color: #ec4899;
  color: #ec4899;
}
.tag.kind-audio {
  border-color: #06b6d4;
  color: #06b6d4;
}
.tag.kind-unknown {
  border-color: var(--line-strong);
  color: var(--fg-3);
}

/* 用途列容易被别的列挤成竖排碎字（v0.10.0 实测观感）：给下拉框和旁边那句
   「你定的 / 实测认的」都留够宽度、各自不折行。 */
td[data-label="用途"] {
  min-width: 9.5rem;
  white-space: nowrap;
}
td[data-label="用途"] .muted.small {
  white-space: nowrap;
}
td[data-label="类型"] .tag {
  white-space: nowrap;
}
td[data-label="类型"] code {
  white-space: nowrap;
  color: var(--fg-3);
}

/* ---------- 15. 日志视图（面板「日志」页） ---------- */
/* 日志是给人读现场的：等宽字体、允许选中复制、长行自动折行（不要横向滚动条 ——
   手机上横滚等于看不了），软换行而不是截断，错误与警告各自带一点颜色。 */
.logview {
  max-height: 60vh;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-2);
  padding: 8px 10px;
}
.logline {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.6875rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--fg-2);
  padding: 1px 0;
  border-bottom: 1px solid transparent;
}
.logline + .logline { border-top: 1px solid var(--line); }
.logline.warn { color: var(--warn); }
.logline.error { color: var(--bad); }
.logview .muted { font-size: 0.75rem; padding: 6px 2px; }



/* Model catalog: identity, capability tags, then channel provenance. */
.model-search { margin-bottom: 12px; font-size: 15px; }
.model-list { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; }
.model-card { min-width: 0; padding: 15px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); display: flex; flex-direction: column; gap: 10px; align-self: start; }
.model-card.open { border-color: var(--line-strong); }
.model-card .mline { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.model-card .mname { min-width: 0; overflow-wrap: anywhere; font-size: 14px; line-height: 1.5; }
.model-card .mline > button { flex: 0 0 auto; height: 28px; padding: 0 10px; border: 1px solid var(--line); border-radius: 7px; font-size: 11px; }
.model-badges { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.model-channel { color: var(--fg-2); font-size: 11px; overflow-wrap: anywhere; }
.model-facts { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: 11px; color: var(--fg-2); }
.model-card .model-detail { border-top: 1px solid var(--line); padding-top: 12px; }
.model-empty { grid-column: 1/-1; padding: 24px; color: var(--fg-3); text-align: center; }
@media(max-width:46rem) { .model-list { grid-template-columns: minmax(0,1fr); gap: 8px; } .model-card { padding: 12px; gap: 8px; } }

.auto-current { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.auto-current > span { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.auto-current small { color: var(--fg-3); white-space: nowrap; }
.auto-current b { font-size: 12px; overflow-wrap: anywhere; }

/* Recent calls use two compact rows, independent of mobile table conversion. */
.recent-list { padding: 0 16px 12px; display:flex; flex-direction:column; gap:6px; }
.recent-call { border:1px solid var(--line); border-radius:9px; padding:8px 10px; min-width:0; }
.recent-main,.recent-meta { display:flex; align-items:center; gap:8px; min-width:0; }
.recent-main { min-height:24px; }
.recent-main .tag,.recent-duration { flex:0 0 auto; }
.recent-model { flex:1; min-width:0; overflow-wrap:anywhere; font-size:12px; line-height:1.4; }
.recent-duration { font-size:11px; color:var(--fg-2); font-variant-numeric:tabular-nums; }
.recent-meta { margin-top:3px; color:var(--fg-3); font-size:11px; }
.recent-meta time { flex:0 0 auto; font-variant-numeric:tabular-nums; }
.recent-channel { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.recent-meta button { height:24px; padding:0 8px; border:1px solid var(--line); font-size:11px; flex:0 0 auto; }
.recent-detail { border-top:1px solid var(--line); margin-top:8px; padding-top:8px; display:flex; flex-wrap:wrap; gap:6px 14px; font-size:11px; overflow-wrap:anywhere; }
.recent-detail p { flex:1 1 100%; margin:0; white-space:pre-wrap; }
@media(max-width:46rem) { .recent-list { padding:0 12px 12px; } }

.brand-home { height:auto; border:0; background:none; padding:0; color:var(--fg); }
.brand-home::before { content:none; display:none; }
.brand-home:hover { background:none; border-color:transparent; }
.overview-tabs { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:6px; }
.overview-tabs button { height:34px; padding:0 8px; font-size:12px; border:1px solid var(--line); background:var(--surface); }
.overview-tabs button.active { border-color:var(--accent); color:var(--accent); font-weight:600; }
.metric-list { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; padding:0 16px 16px; }
.metric-card { padding:12px; border:1px solid var(--line); border-radius:10px; min-width:0; align-self:start; }
.metric-head { display:flex; align-items:flex-start; gap:10px; }
.metric-head b { flex:1; min-width:0; overflow-wrap:anywhere; font-size:13px; }
.metric-head button { flex:0 0 auto; height:25px; padding:0 8px; border:1px solid var(--line); font-size:11px; }
.metric-primary { font-size:12px; margin-top:6px; font-variant-numeric:tabular-nums; }
.metric-secondary { font-size:11px; color:var(--fg-2); margin-top:4px; }
.metric-detail { font-size:11px; color:var(--fg-2); border-top:1px solid var(--line); margin-top:10px; padding-top:8px; white-space:pre-wrap; overflow-wrap:anywhere; }
.callable-models .card-head:empty { display:none; }
@media(max-width:46rem){ .overview-tabs {grid-template-columns:repeat(3,minmax(0,1fr));} .metric-list {grid-template-columns:minmax(0,1fr);padding:0 12px 12px;} }

.page-head { display:flex; flex-direction:column; align-items:stretch; gap:5px; }
.page-title-row { display:flex; align-items:center; flex-wrap:wrap; gap:10px; }
.page-title-row h1 { margin:0; flex:0 0 auto; }
.page-title-row .page-head-actions { width:auto; display:flex; align-items:center; flex-wrap:wrap; gap:6px; }
.page-title-row .page-head-actions button { height:28px; padding:0 9px; font-size:11px; border:1px solid var(--line); }
.page-head > p { margin:0; }

/* One quiet statistics strip; secondary figures stay collapsed. */
.overview-dashboard { display:flex; flex-direction:column; gap:6px; }
.overview-summary { border:1px solid var(--line); border-radius:8px; overflow:hidden; }
.overview-metrics { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); padding:0; }
.overview-metrics .stat { display:flex; flex-direction:row; align-items:center; justify-content:space-between; gap:8px; padding:13px 14px; border:0; border-right:1px solid var(--line); border-radius:0; background:none; min-width:0; }
.overview-metrics .stat:last-child { border-right:0; }
.overview-metrics .stat-title { color:var(--fg-3); font-size:11px; }
.overview-metrics .stat-value { font-size:14px; line-height:1.2; text-align:right; letter-spacing:-.3px; overflow-wrap:anywhere; }
.overview-stat-details { padding:4px 14px 5px; border-top:1px solid var(--line); background:var(--surface); }
.overview-stat-details summary { width:fit-content; cursor:pointer; color:var(--fg-2); font-size:11px; }
.overview-stat-details dl { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px 18px; margin:14px 0 2px; }
.overview-stat-details dl > div { min-width:0; }
.overview-stat-details dt { color:var(--fg-3); font-size:11px; }
.overview-stat-details dd { margin:4px 0 0; font-size:12px; font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
@media(max-width:46rem) {
  .overview-metrics { grid-template-columns:repeat(2,minmax(0,1fr)); padding:0; }
  .overview-metrics .stat { padding:13px 12px; }
  .overview-metrics .stat:nth-child(2) { border-right:0; }
  .overview-metrics .stat:nth-child(-n+2) { border-bottom:1px solid var(--line); }
  .overview-stat-details { padding:4px 12px 5px; }
  .overview-stat-details dl { grid-template-columns:repeat(2,minmax(0,1fr)); }
}

/* On-demand request inspection shares the overview's compact visual language. */
.request-lookup { display:flex; align-items:center; flex-wrap:wrap; gap:8px; padding:0 16px 12px; }
.request-lookup input { flex:1; min-width:100px; max-width:240px; }
.request-inspector { margin:0 16px 14px; padding:12px; border:1px solid var(--line); border-radius:9px; min-width:0; overflow-wrap:anywhere; }
.request-inspector header,.attempt-head { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.request-inspector h3,.request-inspector h4 { margin:0; }
.request-inspector h4 { margin-top:16px; }
.request-attempts { list-style:none; padding:0; margin:8px 0 0; }
.request-attempt { padding:10px 0; border-top:1px solid var(--line); font-size:11px; font-variant-numeric:tabular-nums; }
.request-attempt p { margin:6px 0 0; white-space:pre-wrap; overflow-wrap:anywhere; }
@media(max-width:46rem) { .request-lookup { padding:0 12px 12px; } .request-inspector { margin:0 12px 12px; } }
.route-spotlight {border:1px solid var(--line-strong);border-radius:12px;padding:16px;background:var(--surface);}
.route-spotlight-head {display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.route-spotlight-head h2 {font-size:15px;margin:0;}
.route-selections {display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:12px;}
.route-selection {display:flex;flex-direction:column;gap:5px;min-width:0;overflow-wrap:anywhere;}
.route-selection small,.score-toolbar {color:var(--fg-2);font-size:11px;}
.score-toolbar {display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap;}
.model-controls {display:flex;gap:6px;align-items:center;}
.model-controls input {font-size:15px;flex:1;min-width:0;}
.model-controls button {height:32px;white-space:nowrap;padding:0 9px;font-size:11px;}
.model-controls button.active {border-color:var(--accent);color:var(--accent);}
.score-model-list {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
.score-model {border:1px solid var(--line);border-radius:12px;background:var(--surface);padding:10px;min-width:0;align-self:start;}
.score-model header {display:flex;gap:12px;justify-content:space-between;align-items:flex-start;}
.score-model header>div:first-child {min-width:0;flex:1;}
.score-model h2 {font-size:14px;line-height:1.5;overflow-wrap:anywhere;margin:0 0 4px;}
.score-number {text-align:right;display:flex;flex-direction:column;flex:0 0 auto;}
.score-number b {font-size:24px;line-height:1.2;font-variant-numeric:tabular-nums;}
.score-number small {font-size:10px;color:var(--fg-3);}
.model-telemetry {display:grid;grid-template-columns:1fr 1.4fr .8fr 1fr;gap:8px;border-top:1px solid var(--line);margin-top:7px;padding-top:6px;}
.model-telemetry span {display:flex;flex-direction:column;gap:2px;}
.model-telemetry small {font-size:10px;color:var(--fg-3);}
.model-telemetry b {font-size:12px;font-variant-numeric:tabular-nums;}
.score-model footer {display:flex;align-items:center;gap:8px;margin-top:12px;}
.score-model footer>span {flex:1;font-size:11px;color:var(--fg-2);overflow-wrap:anywhere;min-width:0;}
.score-model footer button {height:27px;border:1px solid var(--line);padding:0 9px;font-size:11px;}
.score-details {border-top:1px solid var(--line);margin-top:10px;padding-top:10px;display:flex;flex-direction:column;gap:8px;font-size:11px;overflow-wrap:anywhere;}
.score-details p {white-space:pre-wrap;margin:0;}
@media(max-width:46rem){.score-model-list{grid-template-columns:minmax(0,1fr);}.score-model{padding:9px;}.route-spotlight{padding:12px;}}

.policy-controls{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:10px 0;}.policy-controls button,.model-cooling button{height:28px;padding:0 9px;font-size:11px;border:1px solid var(--line);background:none;}.policy-controls button.active{border-color:var(--accent);color:var(--accent);}.model-cooling{font-size:11px;color:var(--fg-2);}.model-cooling .tag{font-size:10px;line-height:1.25;font-weight:400;padding:0 6px;gap:3px;}.model-cooling .tag::before{width:4px;height:4px;}.model-cooling>div{display:flex;gap:6px;align-items:center;justify-content:space-between;margin-top:8px;flex-wrap:wrap;}
.recent-call > .request-inspector {margin:10px 0 0;padding:10px 0 0;border:0;border-top:1px solid var(--line);border-radius:0;}
.call-facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 16px;margin:10px 0;}
.call-facts>div{min-width:0;}.call-facts dt{font-size:10px;color:var(--fg-3);}.call-facts dd{margin:3px 0 0;font-size:12px;overflow-wrap:anywhere;}
.call-facts>div:nth-child(1),.call-facts>div:nth-child(12),.call-facts>div:last-child{grid-column:1/-1;}
.call-flags{display:flex;gap:6px;}.call-error{border-top:1px solid var(--line);padding-top:10px;margin-top:10px;font-size:11px;color:var(--bad);}.call-error p{white-space:pre-wrap;overflow-wrap:anywhere;margin:6px 0 0;}

.model-detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding-top:12px;border-top:1px solid var(--line);margin-top:10px;}.model-detail-grid section{min-width:0;}.model-detail-grid h3{font-size:12px;margin:0 0 8px;}.model-detail-grid dl{margin:0;display:grid;gap:7px;}.model-detail-grid dl>div{display:flex;justify-content:space-between;gap:12px;}.model-detail-grid dt{font-size:11px;color:var(--fg-3);}.model-detail-grid dd{margin:0;font-size:11px;overflow-wrap:anywhere;text-align:right;}.model-mapping,.model-cooldown-detail p,.model-last-error p{white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;}.model-cooldown-detail{display:grid;gap:4px;font-size:11px;margin:8px 0;}.model-last-error{grid-column:1/-1;}@media(max-width:600px){.model-detail-grid{grid-template-columns:1fr;}.model-detail-grid section+section{border-top:1px solid var(--line);padding-top:10px;}}

.model-status-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:5px;}.model-status-row .model-cooling{min-width:0;overflow-wrap:anywhere;}.model-status-row .model-cooling>div{margin:0;}.model-status-row>button{flex:0 0 auto;height:28px;padding:0 8px;font-size:11px;}

.auto-header{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:nowrap;}.auto-header .route-spotlight-head{flex:0 0 auto;gap:5px;}.auto-header-actions{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;}.auto-header-actions .pick{display:flex;align-items:center;gap:4px;margin:0;white-space:nowrap;font-size:11px;}.auto-header-actions input[type=checkbox]{width:auto;flex:0 0 auto;}.auto-header-actions button{height:28px;padding:0 7px;font-size:11px;white-space:nowrap;background:none;border:1px solid var(--line);}@media(max-width:400px){.auto-header{gap:5px;}.auto-header .route-spotlight-head h2{font-size:12px;}.auto-header .route-spotlight-head code{font-size:10px;}.auto-header-actions{gap:5px;}.auto-header-actions .pick,.auto-header-actions button{font-size:10px;}.auto-header-actions button{padding:0 5px;}}

@media(max-width:600px){html body input:not([type=checkbox]):not([type=radio]),html body textarea,html body select{font-size:16px;}input[type=checkbox],input[type=radio]{font-size:inherit;}}

.model-card-actions{display:flex;flex-direction:row;align-items:center;gap:8px;flex-shrink:0;}.model-toggle{height:26px;padding:0 8px;font-size:11px;background:none;border:1px solid var(--line);}.model-toggle[aria-checked=true]{color:var(--accent);border-color:var(--accent);}
/* 删模型：红字提示它是破坏性动作（仍然只是摘掉这个名字，历史账不动） */
.model-toggle.model-delete{color:var(--bad);border-color:var(--line);}
.model-toggle.model-delete:hover{background:var(--bad-soft);}

.channel-editor{display:flex;flex-direction:column;max-height:calc(100dvh - 24px);overflow:hidden;}.channel-editor .modal-body{overflow-y:auto;min-height:0;padding:10px 12px;}.channel-editor .modal-head,.channel-editor .modal-tabs,.channel-editor .modal-foot{flex-shrink:0;}.channel-editor .modal-foot{margin-top:0;padding:8px 12px;border-top:1px solid var(--line);}.channel-editor .m-row{padding:7px 8px;}.channel-editor .m-detail{padding:8px;}.channel-editor .m-f{margin:6px 0;}.channel-editor .m-f small,.channel-editor .m-detail>p.muted{display:none;}.channel-editor .pull-bar{margin:0 0 8px;}.channel-editor .pull-head{display:flex;flex-wrap:wrap;gap:6px;}.channel-editor .pull-head input{flex-basis:100%;}.channel-editor .pull-head button{height:28px;padding:0 8px;}.catalog-kind-tabs{display:flex;flex-wrap:wrap;gap:5px;margin:8px 0;}.catalog-kind-tabs button{background:none;border:1px solid var(--line);height:27px;padding:0 7px;font-size:11px;}.catalog-kind-tabs button.active{border-color:var(--accent);color:var(--accent);}.catalog-kind-tabs small{margin-left:4px;}@media(max-width:600px){.channel-editor{width:100%;}.channel-editor .modal-body{padding:8px;}.channel-editor .modal-head{padding:10px 12px;}}

.overview-cooldowns .metric-head b{min-width:0;overflow-wrap:anywhere}.overview-cooldowns button{flex:0 0 auto;height:28px;padding:0 9px;background:none;border:1px solid var(--line)}.cooldown-facts{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cooldown-reason{white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px;margin:8px 0 0}

.overview-window { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:4px; width:min(100%,360px); padding:0; border:0; border-radius:0; background:none; }
.overview-window .chip { width:100%; height:30px; margin:0; padding:0 8px; border:1px solid var(--line); border-radius:6px; background:none; color:var(--fg-2); font-size:11px; white-space:nowrap; }
.overview-window .chip.active { border-color:var(--line-strong); background:var(--surface-3); color:var(--fg); font-weight:600; }
.overview-window .chip:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
@media(max-width:46rem) { .overview-window { width:100%; } .overview-window .chip { height:30px; } }

/* Keep badges full-width so narrow screens do not turn them into a tall column. */
.score-model header{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 8px;align-items:center;}
.score-model header>div:first-child{display:contents;}
.score-model header h2{grid-column:1;grid-row:1;margin:0;}
.score-model header .model-badges{grid-column:1/-1;grid-row:2;}
.score-model header .model-card-actions{grid-column:2;grid-row:1;}
.score-model .score-number b{font-size:20px;}

.score-model .model-badges{gap:4px;}
.score-model .model-badges .tag{padding:1px 4px;}

.score-model .model-badges{gap:3px;}
.score-model .model-badges .tag{padding:1px 2px;}
.score-model .score-number small{line-height:1.2;}
.score-model header{row-gap:3px;}

/* Provider groups retain per-source routing controls without duplicate cards. */
/* Channel-only layout: stable reading order, quiet actions, expandable details. */
.chan-list { gap: 16px; }
.chan-list .chan-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px 24px;
  padding: 22px 24px;
  box-shadow: none;
  border-radius: 12px;
}
.chan-list .chan-head { grid-column: 1 / -1; gap: 12px; }
.chan-list .chan-name { font-size: 15px; line-height: 1.5; }
.chan-list .chan-id { font-size: 11px; }
.chan-list .chan-url { grid-column: 1 / -1; }
.chan-list .chan-url code { font-size: 12px; line-height: 1.6; }
.chan-list .chan-meta { grid-column: 1 / -1; gap: 8px 20px; font-size: 12px; }
.chan-list .chan-meta .tag { border: 0; background: none; padding: 0; font-size: inherit; }
.chan-list .chan-model-row { grid-column: 1 / -1; padding: 10px 0; border-top: 1px solid var(--line); }
.chan-list .chan-model-row > .chan-models { line-height: 1.9; }
.chan-list .chan-actions { grid-column: 1 / -1; margin: 0; gap: 8px; justify-content: flex-start; }
.chan-list .chan-actions button { height: 32px; padding: 0 10px; font-size: 12px; border-color: transparent; }
.chan-list .chan-actions button:hover { background: var(--surface-2); border-color: var(--line); }
.chan-list .chan-probe { grid-column: 1 / -1; }
.chan-list .legacy-key-pool { margin: 0; padding: 0; border: 0; }
.chan-list .chan-actions { flex-wrap: wrap; }
.chan-list .key-pool-modal button { border: 1px solid var(--line-strong); }
.chan-list .key-pool-modal .primary { background: var(--accent); }
.chan-list .legacy-key-pool > button { height: 32px; padding: 0 10px; font-size: 12px; }
.chan-list .legacy-key-pool > p { max-width: 70ch; line-height: 1.8; }
.chan-list .legacy-key-pool [role="alert"]:empty { display: none; }
.chan-list .legacy-key-pool form { max-width: 36rem; }
.supplier-details { grid-column: 1 / -1; margin: 0; min-width: 0; }
.supplier-details summary { cursor: pointer; padding: 10px 0; color: var(--fg-2); font-size: 12px; }
.supplier-details summary:hover { color: var(--fg); }
.supplier-source { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px 24px; padding: 20px 0; border-top: 1px solid var(--line); min-width: 0; }
.chan-list button:focus-visible, .supplier-details summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (max-width: 46rem) {
  /* 手机上宽度不够：上游名列先收起来（title 里还留着），名字独占一列，
     徽章和「测」靠右 —— 别让三样东西挤成两行。 */
  .cm-row { grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; }
  .cm-up { display: none; }
  .cm-probe { height: 30px; padding: 0 12px; }
  .chan-list { gap: 12px; }
  .chan-list .chan-card { padding: 18px 16px; gap: 10px 12px; }
  .chan-list .chan-head { gap: 8px; }
  .chan-list .chan-actions { grid-column: 1 / -1; justify-content: flex-start; }
  .chan-list .chan-actions button { min-height: 40px; padding: 0 8px; }
  .chan-list .legacy-key-pool > button { min-height: 40px; }
  .supplier-source { gap: 10px 12px; padding: 18px 0; }
}

/* Keep all six channel actions together, including the credential dialog trigger. */
.chan-list .chan-actions { flex-wrap: nowrap; gap: 4px; min-width: 0; }
.chan-list .chan-actions > button, .chan-list .chan-actions .legacy-key-pool > button {
  flex: 0 0 auto; height: 28px; min-height: 28px; padding: 0 6px;
  font-size: 0.6875rem; white-space: nowrap; border: 1px solid var(--line-strong);
}
/* Portal dialogs are outside .chan-list and need their own form rules. */
.key-pool-modal { max-height: calc(100dvh - 80px); }
.key-pool-modal > .modal-body { min-height: 0; overflow-y: auto; }
.key-pool-modal > header, .key-pool-modal > footer { flex-shrink: 0; }
.key-pool-modal fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.key-pool-modal form label { display: grid; gap: 6px; margin: 12px 0; }
.key-pool-modal td { overflow-wrap: anywhere; }
.key-pool-modal td button { height: 28px; padding: 0 6px; margin: 2px; }
.key-pool-modal [role="alert"]:empty { display: none; }
.key-pool-modal input { width: 100%; min-width: 0; }
.key-pool-modal button { white-space: nowrap; }
@media (max-width: 46rem) {
  .key-pool-modal { max-height: calc(100dvh - 24px); padding: 14px; gap: 10px; }
}

/* Credential workspace: fixed controls, independently scrolling content. */
.key-pool-modal { display:flex; flex-direction:column; width:min(620px,100%); padding:0; gap:0; overflow:hidden; }
.key-pool-modal .kp-head { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:16px; border-bottom:1px solid var(--line); }
.kp-head h2 { margin:0; font-size:16px; }.kp-head small { color:var(--fg-2); }
.kp-head-actions,.kp-item-actions { display:flex; gap:6px; flex-shrink:0; }
.key-pool-modal button { background:none; border:1px solid var(--line-strong); height:30px; padding:0 10px; font-size:12px; }
.key-pool-modal .modal-body { padding:12px 16px; overscroll-behavior:contain; }
.kp-list { display:grid; gap:10px; }.kp-item { padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--surface-2); min-width:0; }
.kp-item-head { display:flex; flex-wrap:wrap; align-items:center; gap:6px; }.kp-item-head b { overflow-wrap:anywhere; min-width:0; }.kp-item-head .tag { margin:0; }
.kp-item-bottom { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:8px; margin-top:10px; }
.kp-facts { display:flex; flex-wrap:wrap; gap:10px; color:var(--fg-2); font-size:12px; }
.key-pool-modal .kp-foot { margin:0; padding:12px 16px; border-top:1px solid var(--line); display:flex; justify-content:flex-end; gap:8px; }
.key-pool-modal .kp-form label { margin:0 0 14px; font-size:12px; }.kp-form-pair { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }.kp-form-pair label { min-width:0; }
.kp-error { color:var(--danger); overflow-wrap:anywhere; }.kp-empty { text-align:center; color:var(--fg-2); padding:24px 0; }
@media(max-width:46rem) { .key-pool-modal { width:100%; height:calc(100dvh - 24px); max-height:calc(100dvh - 24px); padding:0; }.key-pool-modal .kp-head { padding:12px; }.key-pool-modal .modal-body { padding:12px; }.key-pool-modal .kp-foot { padding:12px; padding-bottom:max(12px,env(safe-area-inset-bottom)); }.kp-item { padding:10px; }.key-pool-modal input { font-size:16px; } }

.kp-backdrop { padding:24px 12px; align-items:center; }
@media(max-width:46rem) { .kp-backdrop { padding:12px; } }

.key-pool-modal > .modal-body { flex: 1 1 auto; }

/* 渠道卡片紧凑版（手机优先）：信息一条不少，只收留白 —— 用户口径「卡片太大了，紧凑些」。
   量法：360/390 宽度下同一批渠道卡片，5 个模型那张从 539px 降到 ~350px。 */
.chan-list { gap: 10px; }
.chan-list .chan-card { gap: 8px 12px; padding: 14px 16px; }
.chan-list .chan-head { gap: 8px; }
.chan-list .chan-name { font-size: 14px; line-height: 1.35; }
.chan-list .chan-url code { font-size: 11.5px; line-height: 1.4; }
.chan-list .chan-meta { gap: 2px 12px; font-size: 11.5px; }
.chan-list .chan-model-row { padding: 6px 0; }
.chan-list .chan-model-row > .chan-models { line-height: 1.35; }
.chan-list .cm-head { margin-bottom: 3px; }
.chan-list .cm-row { min-height: 0; padding: 3px 10px; gap: 8px; line-height: 1.35; }
.chan-list .cm-name { font-size: 12px; }
.chan-list .cm-badge { padding: 0 6px; font-size: 10px; line-height: 1.5; }
.chan-list .cm-probe { height: 22px; padding: 0 8px; font-size: 10.5px; }
.chan-list .chan-proto { padding: 5px 0 0; gap: 4px 10px; font-size: 11.5px; }
.chan-list .chan-proto .proto-pick { gap: 4px; }
.chan-list .chan-proto button.ghost { height: 24px; padding: 1px 8px; font-size: 11px; }
.chan-list .chan-actions { gap: 4px; }
.chan-list .supplier-source { padding: 12px 0; gap: 8px 12px; }
.chan-list .supplier-details summary { padding: 6px 0; }
@media (max-width: 46rem) {
  .chan-list { gap: 8px; }
  .chan-list .chan-card { padding: 10px 12px; gap: 6px 8px; border-radius: 10px; }
  .chan-list .chan-model-row { padding: 5px 0; }
  .chan-list .chan-proto { gap: 4px 8px; }
  .chan-list .supplier-source { padding: 10px 0; gap: 6px 10px; }
}

/* Settings owns its density; other pages keep their existing card rhythm. */
.settings-page { gap: 12px; }
.settings-page .card-head { flex-direction: row; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 14px 0; }
.settings-page .card-body { padding: 10px 14px 12px; min-width: 0; }
.settings-page button { height: 30px; min-height: 30px; padding: 0 10px; flex-shrink: 0; border: 1px solid var(--line); background: var(--surface); color: var(--fg); }
.settings-page button.danger { color: var(--bad); }
.settings-page input { min-width: 0; height: 34px; }
.settings-page label { margin: 0; padding: 0; }
.settings-columns { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; align-items: start; }
.settings-fields, .settings-password-fields { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,140px); align-items: center; gap: 9px 14px; }
.settings-number { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.settings-number input { width: 90px; text-align: right; }
.settings-number span { color: var(--fg-2); }
.settings-address { grid-template-columns: auto minmax(0,1fr); }
.settings-address code { overflow-wrap: anywhere; text-align: right; }
.settings-detail { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.settings-detail summary { cursor: pointer; color: var(--fg-2); font-size: .75rem; }
.settings-data { display: grid; grid-template-columns: auto minmax(0,1fr); gap: 8px 14px; margin: 10px 0 0; font-size: .75rem; }
.settings-data dt { color: var(--fg-2); }
.settings-data dd { margin: 0; overflow-wrap: anywhere; text-align: right; }
.settings-key-create { display: grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: center; gap: 10px; }
.settings-key { padding: 9px 0; border-bottom: 1px solid var(--line); overflow-wrap: anywhere; }
.settings-key:last-child { border-bottom: 0; padding-bottom: 0; }
.settings-key-main { display: grid; grid-template-columns: minmax(0,1fr) auto auto; align-items: center; gap: 8px; }
.settings-key-actions { display: flex; gap: 6px; }
.settings-key > .small { display: block; margin-top: 3px; }
.settings-maint-summary, .settings-maint-actions { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.settings-maint-summary { font-size: .75rem; }
.settings-password-fields { grid-template-columns: auto minmax(0,1fr) auto minmax(0,1fr); }
@media (max-width: 700px) {
  .settings-columns { grid-template-columns: minmax(0,1fr); }
  .settings-password-fields { grid-template-columns: auto minmax(0,1fr); }
  .settings-key-main { grid-template-columns: minmax(0,1fr) auto; }
  .settings-key-main strong { grid-column: 1; }
  .settings-key-main code { grid-column: 1; grid-row: 2; }
  .settings-key-actions { grid-column: 2; grid-row: 1 / 3; }
  html body .settings-page input { font-size: 16px; }
}

.auto-fold > summary {cursor:pointer;font-weight:600;display:flex;align-items:center;gap:8px;}
.auto-fold > summary::before {content:"▸";}
.auto-fold[open] > summary::before {content:"▾";}
.auto-fold > summary::-webkit-details-marker {display:none;}
.auto-fold .auto-strategy {margin-left:auto;white-space:nowrap;font-size:12px;color:var(--muted);font-weight:400;}
.auto-fold > summary code {margin-left:8px;}
.auto-fold[open] > summary {margin-bottom:12px;}
.auto-fold .auto-header {margin-bottom:8px;}

/* Keep machine credentials visually separate from client API keys. */
.operations-tokens .settings-fields { margin-block: 16px; }
.ops-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-block: 12px; }
.ops-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-block: 12px; border-bottom: 1px solid var(--border); }
.ops-row > div { min-width: 0; display: grid; gap: 4px; overflow-wrap: anywhere; }
.ops-secret { margin-block: 16px; }
.ops-secret textarea { width: 100%; box-sizing: border-box; resize: vertical; overflow-wrap: anywhere; }
.ops-audit { overflow-wrap: anywhere; }
@media (max-width: 600px) { .operations-tokens .settings-fields { grid-template-columns: 1fr; } }

.operations-tokens button { min-height: 44px; }

/* 最近调用的输入输出用量靠右，同一行展示。 */
.recent-token-usage {display:flex;gap:6px;flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--fg-2);}
@media(max-width:46rem) {.recent-call[data-request-id] > .recent-meta {gap:5px;}}

.recent-kind-tabs {display:flex;gap:5px;}
.recent-kind-tabs button {height:26px;padding:0 8px;font-size:11px;}
.recent-kind-tabs button.active {border-color:var(--accent);font-weight:600;background:var(--surface-3);}

/* 覆盖通用卡头纵排，标题与分类始终同行。 */
.recent-calls > .recent-calls-head {display:flex;flex-direction:row;align-items:center;flex-wrap:nowrap;gap:8px;}
.recent-calls-head h2,.recent-calls-head .recent-kind-tabs {flex:0 0 auto;white-space:nowrap;}
.recent-calls-head > .muted {margin-left:auto;white-space:nowrap;font-size:10px;}
.recent-calls-head > .spacer {display:none;}

/* 统计卡片缩至约三分之二，保留数字字号与完整详情。 */
.overview-summary .overview-metrics .stat {padding-top:5px;padding-bottom:5px;}
.overview-summary .overview-stat-details {padding-top:1px;padding-bottom:1px;}
.overview-summary .overview-stat-details summary {line-height:14px;}

.model-call-name{grid-column:1/-1;grid-row:2;overflow-wrap:anywhere;margin:0;}
.score-model header:has(.model-call-name) .model-badges{grid-row:3;}
.model-display-editor input{width:100%;box-sizing:border-box;margin-bottom:8px;}
.model-display-editor p{overflow-wrap:anywhere;}

/* 请求时间线（v0.58.0）：每次尝试一条，横条长度=耗时占比。 */
.call-timeline{margin-top:14px;border-top:1px solid var(--line);padding-top:10px;}
.call-timeline p{margin:4px 0 10px;}
.tl-row{display:grid;gap:4px;padding:8px 0;border-top:1px dashed var(--line);}
.tl-row:first-of-type{border-top:0;}
.tl-label{display:flex;align-items:center;gap:8px;font-size:11px;min-width:0;overflow-wrap:anywhere;}
.tl-track{position:relative;height:8px;background:var(--line);border-radius:4px;overflow:hidden;}
.tl-bar{position:absolute;top:0;bottom:0;border-radius:4px;min-width:3px;}
.tl-bar.ok{background:var(--ok);}
.tl-bar.bad{background:var(--bad);}
.tl-times{font-variant-numeric:tabular-nums;}
.tl-error{color:var(--bad);white-space:pre-wrap;overflow-wrap:anywhere;}
.replay-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:12px;padding-top:10px;border-top:1px solid var(--line);}


/* 按用途细分（总览）：跟「统计详情」同一条细杠 —— 默认收起，点摘要才出三行数字。
   对话 / 嵌入 / 重排各有固定色点，数字右对齐、同一套类型色。 */
.kind-split { padding: 4px 14px 9px; }
.kind-split .kind-table { margin-top: 8px; }
.kind-table { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.kind-row {
  display: grid;
  grid-template-columns: 108px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 0.7fr) minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 10px;
  align-items: baseline;
  padding: 6px 0;
  border-top: 1px solid var(--line);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  min-width: 520px;
}
.kind-row.kind-head {
  border-top: 0;
  padding: 0 0 5px;
  color: var(--fg-2);
  font-size: 11px;
  letter-spacing: 0.02em;
}
.kind-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kind-row > span:not(.kind-name), .kind-row > b { text-align: right; }
.kind-row.kind-head > span:first-child { text-align: left; }
.kind-row > b { font-weight: 600; }
.kind-row:not(.kind-head):hover { background: var(--surface-3); }
.kind-name { display: flex; align-items: center; gap: 7px; font-weight: 600; }
.kind-name .kind-dot { width: 7px; height: 7px; border-radius: 999px; flex: 0 0 auto; background: var(--fg-3); }
.kind-row[data-kind="chat"] .kind-dot { background: #3b82f6; }
.kind-row[data-kind="embeddings"] .kind-dot { background: #10b981; }
.kind-row[data-kind="rerank"] .kind-dot { background: #f59e0b; }
.kind-rate.good { color: var(--ok); }
.kind-rate.warn { color: var(--warn); }
.kind-rate.bad, .kind-fail.bad { color: var(--bad); font-weight: 600; }
.kind-rate.none { color: var(--fg-3); }
.kind-token, .kind-lat { color: var(--fg-2); }
/* 手机宽度不横滑：上排看量（名称/请求/成功率），下排小字看失败、Token、耗时。 */
@media (max-width: 640px) {
  .kind-split { padding: 4px 12px 8px; }
  .kind-table { overflow: visible; }
  .kind-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    column-gap: 10px;
    row-gap: 2px;
    min-width: 0;
    padding: 7px 0;
  }
  .kind-row.kind-head { display: none; }
  .kind-row > * { white-space: nowrap; overflow: visible; }
  .kind-name { grid-column: 1; grid-row: 1; }
  .kind-row > b { grid-column: 2; grid-row: 1; justify-self: end; font-size: 13px; }
  .kind-rate { grid-column: 3; grid-row: 1; justify-self: end; }
  .kind-fail { grid-column: 1; grid-row: 2; justify-self: start; }
  .kind-token { grid-column: 2; grid-row: 2; justify-self: end; }
  .kind-lat { grid-column: 3; grid-row: 2; justify-self: end; }
  .kind-fail, .kind-token, .kind-lat { font-size: 11px; color: var(--fg-3); }
  .kind-fail::before { content: "失败 "; }
  .kind-token::before { content: "Token "; }
  .kind-lat::before { content: "耗时 "; }
  .kind-fail.bad { color: var(--bad); }
  .kind-fail.bad::before { color: var(--fg-3); }
}
