/* ==========================================================================
   strawhatAI 面板 — 设计令牌与基础样式
   ==========================================================================

   组织方式：按"令牌 → 基础元素 → 布局 → 组件 → 工具类"分层。
   所有颜色都走 CSS 变量，主题切换只改变量，不改任何组件规则。

   渠道配色是这套设计的核心：四个渠道各占一个色相，且都避开状态色
   （红/琥珀/绿），这样"渠道徽章"与"健康状态"在视觉上永不混淆。
   ========================================================================== */

:root {
  /* ---- 渠道色（与 Go 侧 channel.Spec.Color 一一对应） ---- */
  --ch-cn: #0284c7;         /* 天蓝 */
  --ch-com: #059669;        /* 翠绿 */
  --ch-lobster: #a21caf;    /* 品红 */
  --ch-kuku: #b45309;       /* 琥珀 */
  --ch-traesolo: #0d9488;   /* 青蓝 */
  --ch-loomy: #7c3aed;      /* 紫罗兰（与 LobsterAI 品红拉开） */

  /* ---- 表面 ---- */
  --bg: #f6f8fb;
  --bg2: #eef2f7;
  --card: #ffffff;
  --card2: #f8fafc;
  --sidebar: #ffffff;
  --border: #e3e8ef;
  --border2: #cbd5e1;

  /* ---- 文字 ---- */
  --txt: #0f172a;
  --muted: #52637a;
  --dim: #8b9bb0;

  /* ---- 品牌 ---- */
  --pri: #4f46e5;
  --pri-soft: #eef2ff;
  --grad: linear-gradient(135deg, #4f46e5, #0ea5e9);

  /* ---- 状态 ---- */
  --ok: #0d9668;
  --ok-bg: #e9f9f1;
  --warn: #b45309;
  --warn-bg: #fef6e7;
  --err: #dc2626;
  --err-bg: #fdeeee;
  --info: #0369a1;
  --info-bg: #e8f4fd;

  /* ---- 形状 ---- */
  --r: 12px;
  --r-sm: 8px;
  --r-lg: 16px;
  --r-full: 999px;

  /* ---- 阴影（亮色下用极浅的层级阴影，保持扁平克制） ---- */
  --sh: 0 1px 2px rgba(16, 30, 54, 0.05);
  --sh-md: 0 2px 8px -2px rgba(16, 30, 54, 0.08);
  --sh-lg: 0 12px 32px -12px rgba(16, 30, 54, 0.16);

  /* ---- 字体 ---- */
  --font: "Inter", "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont,
          "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ---- 尺寸 ---- */
  --side-w: 216px;
  --top-h: 60px;
}

[data-theme="dark"] {
  --ch-cn: #38bdf8;
  --ch-com: #34d399;
  --ch-lobster: #e879f9;
  --ch-kuku: #fbbf24;
  --ch-traesolo: #2dd4bf;
  --ch-loomy: #a78bfa;

  --bg: #080c14;
  --bg2: #0d1420;
  --card: #111a28;
  --card2: #0d1420;
  --sidebar: #0b1220;
  --border: #1e2c40;
  --border2: #2f4159;

  --txt: #e8eef7;
  --muted: #93a4bb;
  --dim: #5f7288;

  --pri: #6366f1;
  --pri-soft: #1a2340;

  --ok: #34d399;
  --ok-bg: #0c2418;
  --warn: #fbbf24;
  --warn-bg: #2a1f08;
  --err: #f87171;
  --err-bg: #2b1315;
  --info: #38bdf8;
  --info-bg: #0c2233;

  --sh: 0 1px 2px rgba(0, 0, 0, 0.4);
  --sh-md: 0 2px 10px -2px rgba(0, 0, 0, 0.5);
  --sh-lg: 0 16px 40px -16px rgba(0, 0, 0, 0.7);
}

/* ==========================================================================
   基础
   ========================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--txt);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* 数字等宽：让表格里的数字列上下对齐，读起来不跳 */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 650;
  letter-spacing: -0.015em;
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

a { color: var(--pri); text-decoration: none; }
a:hover { text-decoration: underline; }

code, pre, .mono { font-family: var(--mono); font-size: 0.92em; }

/* 滚动条：细、无色、悬停才显形，避免在浅色界面上留下粗重色块 */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: var(--r-full);
  border: 3px solid transparent;
  background-clip: content-box;
}
:hover::-webkit-scrollbar-thumb { background: var(--border2); background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--dim); background-clip: content-box; }

/* 焦点可见性：键盘用户必须能看见焦点，鼠标用户不该看到 */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--pri);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ==========================================================================
   应用骨架
   ========================================================================== */

#app {
  display: grid;
  grid-template-columns: var(--side-w) 1fr;
  min-height: 100vh;
}

/* ---- 侧栏 ---- */

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 12px 14px;
  background: var(--sidebar);
  border-right: 1px solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 18px;
}

.brand-mark {
  width: 32px;
  height: 32px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 9px;
  background: var(--grad);
  color: #fff;
  font-weight: 800;
  font-size: 15px;
  box-shadow: var(--sh-md);
}

.brand-text { min-width: 0; }
.brand-name {
  font-weight: 700;
  font-size: 14.5px;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
/* 这里是版本号，不是 "Gateway" 那种装饰性标签：
   不做 uppercase（会把 v1.2.3-rc1 变成 V1.2.3-RC1，与 --version 的
   输出对不上），并横向截断，免得长版本号把侧栏顶宽。 */
.brand-sub {
  font-size: 11px;
  color: var(--dim);
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav { display: flex; flex-direction: column; gap: 2px; flex: 1; overflow-y: auto; }

.nav-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 550;
  text-align: left;
  cursor: pointer;
  transition: background 0.14s, color 0.14s;
}
.nav-btn svg { flex: none; opacity: 0.72; transition: opacity 0.14s; }
.nav-btn:hover { background: var(--bg2); color: var(--txt); }
.nav-btn[aria-current="page"] {
  background: var(--pri-soft);
  color: var(--pri);
  font-weight: 650;
}
.nav-btn[aria-current="page"] svg { opacity: 1; }

/* 渠道状态点：侧栏里一眼看出哪个渠道有问题 */
.nav-dot {
  margin-left: auto;
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--dim);
}

.side-foot {
  border-top: 1px solid var(--border);
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.side-actions { display: flex; gap: 6px; }

/* ---- 主区 ---- */

.main { min-width: 0; display: flex; flex-direction: column; }

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 12px;
  height: var(--top-h);
  padding: 0 26px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}

.topbar-title { font-size: 16px; font-weight: 680; letter-spacing: -0.02em; }
.topbar-sub { font-size: 12.5px; color: var(--muted); font-weight: 450; }
.topbar-spacer { flex: 1; }

.content { padding: 24px 26px 60px; max-width: 1500px; width: 100%; }

.view { display: none; }
.view.is-active { display: block; animation: fade 0.22s ease; }

@keyframes fade {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* ---- 首屏骨架 ----
 *
 * 首屏要打 5 个端点（其中 /api/models 冷缓存时会向上游拉目录，可能好几秒）。
 * 等数据回来再画会让用户面对一片空白，看起来像"坏了"。骨架给的是
 * "正在加载、结构已就绪"的即时反馈，比一个居中转圈更接近最终布局，
 * 页面也就不会在数据到达时整体跳一下。
 *
 * 动画在 prefers-reduced-motion 下由全局规则压成 0.01ms（见文件末尾）。
 */
.skeleton { display: flex; flex-direction: column; gap: 14px; }
.sk-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sk-row { display: flex; gap: 12px; align-items: center; }
.sk-bar {
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(90deg, var(--card2) 25%, var(--bg2) 37%, var(--card2) 63%);
  background-size: 400% 100%;
  animation: sk-shimmer 1.4s ease infinite;
}
.sk-bar.w20 { width: 20%; }
.sk-bar.w35 { width: 35%; }
.sk-bar.w50 { width: 50%; }
.sk-bar.w70 { width: 70%; }
.sk-bar.w100 { width: 100%; }
.sk-dot { width: 34px; height: 34px; border-radius: 50%; flex: none; }
.sk-box { height: 60px; }
@keyframes sk-shimmer {
  0% { background-position: 100% 0; }
  100% { background-position: 0 0; }
}

/* ==========================================================================
   图标按钮
   ========================================================================== */

.iconbtn {
  width: 32px;
  height: 32px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.iconbtn:hover:not(:disabled) { background: var(--bg2); color: var(--txt); border-color: var(--border2); }
.iconbtn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ==========================================================================
   按钮
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 13px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--txt);
  font-size: 13px;
  font-weight: 550;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.14s, border-color 0.14s, opacity 0.14s;
}
.btn:hover:not(:disabled) { background: var(--bg2); border-color: var(--border2); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn-pri {
  border-color: transparent;
  background: var(--pri);
  color: #fff;
}
.btn-pri:hover:not(:disabled) { background: color-mix(in srgb, var(--pri) 86%, #000); }

.btn-danger { color: var(--err); border-color: color-mix(in srgb, var(--err) 35%, var(--border)); }
.btn-danger:hover:not(:disabled) { background: var(--err-bg); }

.btn-sm { padding: 4px 9px; font-size: 12.5px; }
.btn-block { width: 100%; }

/* ==========================================================================
   卡片
   ========================================================================== */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--sh);
}

.card-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border);
}
.card-title { font-size: 14.5px; font-weight: 650; }
.card-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.card-body { padding: 18px; }
.card-head-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }

/* ==========================================================================
   徽章
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: var(--r-full);
  background: var(--bg2);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.7;
  white-space: nowrap;
}
.badge i {
  width: 5px;
  height: 5px;
  flex: none;
  border-radius: 50%;
  background: currentColor;
}
.badge.ok { background: var(--ok-bg); color: var(--ok); }
.badge.warn { background: var(--warn-bg); color: var(--warn); }
.badge.err { background: var(--err-bg); color: var(--err); }
.badge.info { background: var(--info-bg); color: var(--info); }

/* 渠道徽章：颜色由内联 style 的 --c 提供（数据驱动，不硬编码渠道） */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px;
  border-radius: var(--r-full);
  font-size: 11.5px;
  font-weight: 650;
  color: var(--c, var(--muted));
  background: color-mix(in srgb, var(--c, var(--muted)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--c, var(--muted)) 26%, transparent);
  white-space: nowrap;
}
.chip i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: none; }

/* 胶囊内的文本层。
   min-width:0 是 flex 子项能被压缩的前提（默认 min-width:auto 会撑住内容
   宽度，胶囊就收不下来），配合省略号让 .badge/.chip 在窄列里优雅截断。 */
.badge-text,
.chip-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 只在日志的两张表里启用「胶囊收窄」。
   它们的列宽是固定百分比，必然出现内容比列宽长的情况；而收窄要靠
   .badge-text / .chip-text 这层包裹才能出省略号 —— 其它视图的胶囊没有这层
   包裹（空间也够用），一旦跟着收窄，文字会溢出胶囊底色，反而更像渲染坏了。 */
table.logtable .badge,
table.logtable .chip { max-width: 100%; }

/* ==========================================================================
   表格
   ========================================================================== */

.tablewrap { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: 13px; }

th {
  padding: 9px 14px;
  text-align: left;
  font-size: 11.5px;
  font-weight: 650;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
  background: var(--card2);
  position: sticky;
  top: 0;
}

td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--card2); }

.num { text-align: right; font-family: var(--mono); font-size: 12.5px; }
.trunc { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 日志页表格（请求日志 / 模型消耗）。
   auto 布局下表格会被内容顶宽：8 列实测 1312px，而卡片只有 1020px，
   「延迟」「Token」两列得横向滚动才看得到。改用固定布局 + colgroup 的
   百分比列宽，表格宽度恒等于容器，列与列之间也不再随内容左右跳。
   内边距首列左、末列右取 18px，与 .card-head 一致 —— 表格文字和卡片
   标题落在同一条竖线上（原来是 14px，整块表格比标题左突 4px 看着是歪的）；
   中间列用 12px，把省下的宽度还给内容。
   类名不能叫 .grid —— panel.css 里已有 .grid{display:grid} 的布局工具类，
   撞名会让 <table> 变成 CSS Grid，表头表体各成一块、colgroup 全部失效。 */
table.logtable { table-layout: fixed; }
table.logtable th,
table.logtable td {
  padding-left: 12px;
  padding-right: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
}
table.logtable th:first-child,
table.logtable td:first-child { padding-left: 18px; }
table.logtable th:last-child,
table.logtable td:last-child { padding-right: 18px; }

/* 9 列表格（请求日志 / 模型消耗）的收紧版。
   请求日志的渠道列必须完整显示渠道全名（"WorkBuddy 国内版" 137px），
   而 1280 窗口下卡片只有 1010px、列数又多 —— 从单元格与胶囊的内边距里
   各挤出 2px，是唯一不牺牲任何一列可读性的取宽处。
   12→10 / 18→14 / 胶囊 9→6：视觉上仍与其它表格保持同档密度。 */
table.logtable.logtable-tight th,
table.logtable.logtable-tight td {
  padding-left: 10px;
  padding-right: 10px;
}
table.logtable.logtable-tight th:first-child,
table.logtable.logtable-tight td:first-child { padding-left: 14px; }
table.logtable.logtable-tight th:last-child,
table.logtable.logtable-tight td:last-child { padding-right: 14px; }
table.logtable.logtable-tight .chip {
  padding-left: 6px;
  padding-right: 6px;
  gap: 5px;
}

/* 表头里的 .num 只保留「右对齐」，字号与字体回归 th 自己的设定。
   .num 是给**数据单元格**用的（等宽数字便于按位对齐）；套到 th 上之后
   「延迟」「TOKEN总(入[缓]出)」两个表头字号从 11.5px 变成 12.5px、字体变成
   等宽 —— 同一行里两个表头比另外七个大一号又换了字体，是肉眼可见的不齐。
   更实际的影响是宽度：等宽字体把「TOKEN总(入[缓]出)」撑到 202px，是全表
   最宽的表头，直接决定 TOKEN 列的下限；改成继承 th 的 11.5px 无衬线后
   只要 161px（实测，1440 窗口）。 */
table.logtable th.num {
  font-size: 11.5px;
  font-family: inherit;
}

/* 模型目录的「测试」列。
   四个测试档 + 复制必须排在一行 —— 换行会让每行高度翻倍（一屏从十几行
   掉到七八行）。nowrap 同时把这一列的最小宽度钉在按钮组上，表格的自动
   布局会给它让出空间；按钮也相应压小，把省下的宽度还给模型名。 */
.model-ops-cell { white-space: nowrap; }
.model-ops {
  justify-content: flex-end;
  gap: 4px;
  flex-wrap: nowrap;
}
.model-ops .btn-sm {
  padding: 3px 7px;
  font-size: 11.5px;
}

/* 日志表里「一个单元格两行」的那两行：时间/日期、模型/上游模型、总量/明细。
   每一行必须各自带省略号 —— td 上的 overflow + text-overflow 只作用于**直接**
   的行内内容，块级子 div 里的溢出会被 td 直接裁掉、不带省略号，数字看着像被
   切掉半个字，比"截断"更难读。
   （「模型」列的第二行原本用内联 style 写这两条，现在收敛到这里。） */
.cell-line {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 分页条：请求日志与模型消耗共用，排版与文案一致。
   上边框把翻页控件与表格分开，整体居中；信息用等宽数字，页码从
   1 位变到 3 位时整条不会左右抖动。 */
.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
}
.pager-info { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   表单
   ========================================================================== */

.field { display: flex; flex-direction: column; gap: 5px; }
.field-label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.field-hint { font-size: 11.5px; color: var(--dim); line-height: 1.5; }

/* ---- 设置行（渠道设置弹窗里的"标签+说明 / 控件"）----
   两列网格：左列 minmax(0,1fr) 放标签与说明，右列 auto 放控件；
   .srow-text 用 display:contents 让"标签 / 说明"直接成为网格项，控件于是
   跨两行竖直居中。三种屏幕共用这一个结构 —— 窄屏也不会把控件甩到下一行、
   更不会把输入框压成看不清自己填了什么。 */
.srow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 14px;
  row-gap: 1px;
  min-width: 0;
}
.srow-text { display: contents; }
.srow-name {
  grid-column: 1;
  grid-row: 1;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}
.srow-hint {
  grid-column: 1;
  grid-row: 2;
  font-size: 11.5px;
  color: var(--dim);
  line-height: 1.45;
  /* 两行封顶 + title 悬停看全文：说明长短不一，不封顶就排成阶梯 */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(1.45em * 2);
}
.srow-ops {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  align-items: center;
  gap: 8px;
  justify-self: end;
}
/* 控件组子项一律不压缩：'默认' 被压成竖排、'分钟' 被顶到容器外，都是
   子项默认可压缩 + 输入框全局 width:100% 造成的。输入框宽度交给各自的
   max-width，这里改回按内容排。 */
.srow-ops > * { flex: 0 0 auto; }
.srow-ops .input { width: auto; max-width: 100%; }
.srow-ops .small { white-space: nowrap; }

/* ---- 密钥编辑器（keys.js 的 openEditor） ----
   结构固定为：名称 → 渠道横向导航 → 设置区（推理强度/启用/秒请求数）→ 模型清单。
   三种屏幕共用同一套结构，随宽度变化的只有列数与间距 —— 布局变了但层级不变，
   用户换了设备也不用重新找控件。 */
.keyform { display: flex; flex-direction: column; gap: 18px; min-width: 0; }

/* 小节：标题 + 说明 + 内容，竖向节奏统一 */
.keysec { display: flex; flex-direction: column; gap: 7px; min-width: 0; }

/* 设置区：手机单列 → 平板两列 → 桌面三列。
   窄屏下硬排三列会把输入框压到只剩几十像素，光标都看不清。 */

/* 窄屏再收一档：输入框的宽度是各调用点用内联 max-width 给的（120/160/200），
   手机上 200px 会把标签挤成"排程抖 / 动"这种断词。这里用 !important 压过内联值
   —— 只在这一档生效，桌面照旧按各自的 max-width。'默认 / 已单独配置' 那颗小字
   也一并隐藏：占位符里已经写着"默认（30）"，两个都显示只是重复占位。 */
@media (max-width: 560px) {
  .srow-ops .input { max-width: 104px !important; }
  .srow-ops .small { display: none; }
}
.keysec-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px 16px;
  min-width: 0;
}
@media (min-width: 561px) {
  .keysec-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 861px) {
  .keysec-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* 渠道横向导航：
   - nowrap：渠道永远排一行，高度不随渠道数量抖动
   - overflow-x:auto：溢出在**容器内**滚动，页面本身不出横向滚动条
   - min-width:0：flex/grid 子项默认 min-width:auto，不设它容器会被内容顶宽，
     结果整页横向溢出、而滚动区反而不出现（面板导航踩过同一个坑） */
.chnav {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  min-width: 0;
  max-width: 100%;
  padding: 2px 0 4px;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* 宽屏（≥561px）允许换行：这里有空间，把六个渠道一次全摆出来比让用户
   横向滑着找更好 —— 滑动是窄屏没得选时的退路，不是桌面上的常态。
   换行后不再需要横向滚动，overflow 收回去，免得留一条空滚动条。 */
@media (min-width: 561px) {
  .chnav {
    flex-wrap: wrap;
    overflow-x: visible;
    overflow-y: visible;
  }
}
.chnav-item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none; /* 不被压缩：宁可滚动，也别把渠道名截成两个字 */
  max-width: 100%;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  background: var(--card2);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.14s, background 0.14s, color 0.14s;
}
.chnav-item:hover { border-color: var(--border2); color: var(--txt); }
.chnav-item.is-on {
  border-color: var(--pri);
  color: var(--txt);
  background: color-mix(in srgb, var(--pri) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pri) 30%, transparent);
}
.chnav-item:focus-visible { outline: 2px solid var(--pri); outline-offset: 2px; }
.chnav-dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--c, var(--muted));
}
.chnav-name { overflow: hidden; text-overflow: ellipsis; }
.chnav-cnt {
  font-size: 11px;
  font-weight: 500;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.chnav-item.is-on .chnav-cnt { color: var(--muted); }

/* 模型清单：纵向单列 + 限高滚动。
   - 纵向：模型名较长（deepseek-v4-pro 之类），横排会挤成几个字就折行
   - 限高：一个渠道几十个模型，全展开会把弹窗撑成一堵墙
   - overflow-x:hidden：异常长的名字也只在本行省略，不撑破弹窗 */
.modellist {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  max-height: 208px;
  padding: 5px;
  overflow-x: hidden;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card2);
}
.modelrow {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  padding: 6px 8px;
  border-radius: 6px;
  cursor: pointer;
}
.modelrow:hover { background: var(--card); }
.modelrow input { flex: none; margin: 0; }
.modelrow .modelname {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 手机上的弹窗：四周留白收窄，把宽度让给内容。
   20px 的边距在 360px 屏上等于吃掉 11% 宽度，渠道导航与模型名都会更早截断。 */
@media (max-width: 560px) {
  .modal-backdrop { padding: 10px; }
  .modal-head { padding: 13px 14px; }
  .modal-body { padding: 14px; }
  .modal-foot { padding: 12px 14px; }
  .keyform { gap: 16px; }
  .modellist { max-height: 40vh; }
}

/* 授权链接的展示条：左侧是可换行的等宽 URL，右侧是操作按钮。
   用 flex + min-width:0 让长 URL 能真正换行（不设 min-width 时
   flex 子项不会收缩到内容宽度以下，长链接会把按钮挤出容器）。 */
.codeurl {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card2);
}
.codeurl > .mono {
  flex: 1;
  min-width: 0;
  line-height: 1.5;
}

/* 接入指引里的「标签 + 端点」一行：左侧固定标签、右侧可换行的等宽地址。
   窄屏下改为上下堆叠（见响应式一节），让地址拿到整行宽度 —— 否则
   88px 的标签会把地址挤到每行只剩十来个字符，端口号被拦腰折断。 */
.kv { display: flex; gap: 12px; align-items: flex-start; }
.kv-label { min-width: 88px; padding-top: 8px; font-weight: 600; }
.kv-box {
  display: flex;
  flex: 1;
  gap: 10px;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card2);
}
.kv-box .mono { min-width: 0; word-break: break-all; line-height: 1.5; }

.input {
  width: 100%;
  padding: 7px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  font-size: 13px;
  transition: border-color 0.14s, box-shadow 0.14s;
}
.input:hover { border-color: var(--border2); }
.input:focus {
  border-color: var(--pri);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pri) 14%, transparent);
}
.input::placeholder { color: var(--dim); }

textarea.input { resize: vertical; min-height: 84px; font-family: var(--mono); font-size: 12.5px; }

/* 开关 */
.switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 34px;
  height: 19px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--border2);
  position: relative;
  transition: background 0.18s;
}
.switch-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: transform 0.18s;
}
.switch input:checked + .switch-track { background: var(--pri); }
.switch input:checked + .switch-track::after { transform: translateX(15px); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--pri); outline-offset: 2px; }

/* 表单栅格 */
.grid { display: grid; gap: 14px; }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* 仪表盘的「渠道健康 + 用量概况」两栏。
   健康表是 6 列表格，需要比统计卡更宽：等分两列时账号列会被挤到
   换行（"235 / 235" 折成两行）、TOKEN 列被顶宽。1.7 : 1 给表格让位。 */
.dash-split { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); }

/* ==========================================================================
   布局工具
   ========================================================================== */

.row { display: flex; align-items: center; gap: 10px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.col { display: flex; flex-direction: column; gap: 10px; }
.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 14px; }
.muted { color: var(--muted); }
.dim { color: var(--dim); }
.small { font-size: 12.5px; }
.nowrap { white-space: nowrap; }

/* ==========================================================================
   空状态 / 加载 / 提示
   ========================================================================== */

.empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--dim);
  font-size: 13px;
}
.empty-title { font-size: 14px; font-weight: 600; color: var(--muted); margin-bottom: 4px; }

/* note 是"解释性提示块"，用在卡片里说明为什么某项不可用、
   或者某个操作有什么后果。与 toast 的区别：toast 是瞬时的操作
   反馈，note 是常驻的上下文说明，所以它不带动画、不自动消失。 */
.note {
  padding: 9px 12px;
  border-radius: var(--r-sm);
  font-size: 12.5px;
  line-height: 1.55;
  background: var(--card2);
  border: 1px solid var(--border);
  color: var(--muted);
}
/* warn 用左侧色条而不是整块染色：整块黄色在浅色主题下会显得
   很吵，而一个 3px 的色条已经足够把注意力引过来。 */
.note.warn {
  background: color-mix(in srgb, var(--warn) 9%, var(--card2));
  border-color: color-mix(in srgb, var(--warn) 32%, var(--border));
  border-left: 3px solid var(--warn);
  color: var(--txt);
}
.note.err {
  background: color-mix(in srgb, var(--err) 9%, var(--card2));
  border-color: color-mix(in srgb, var(--err) 32%, var(--border));
  border-left: 3px solid var(--err);
  color: var(--txt);
}
.note.ok {
  background: color-mix(in srgb, var(--ok) 9%, var(--card2));
  border-color: color-mix(in srgb, var(--ok) 32%, var(--border));
  border-left: 3px solid var(--ok);
  color: var(--txt);
}

.spinner {
  width: 15px;
  height: 15px;
  border: 2px solid var(--border2);
  border-top-color: var(--pri);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
  flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Toast */
.toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  min-width: 240px;
  max-width: 400px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--pri);
  border-radius: var(--r-sm);
  background: var(--card);
  box-shadow: var(--sh-lg);
  font-size: 13px;
  pointer-events: auto;
  animation: toast-in 0.2s ease;
}
.toast.ok { border-left-color: var(--ok); }
.toast.err { border-left-color: var(--err); }
.toast.warn { border-left-color: var(--warn); }
.toast-msg { flex: 1; min-width: 0; word-break: break-word; }
.toast-close {
  border: 0;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  padding: 0;
  line-height: 1;
  font-size: 16px;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateX(16px); }
  to { opacity: 1; transform: none; }
}

/* 模态 */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 150;
  display: grid;
  place-items: center;
  padding: 20px;
  background: color-mix(in srgb, #0b1220 55%, transparent);
  backdrop-filter: blur(3px);
  animation: fade 0.16s ease;
}
.modal {
  width: 100%;
  max-width: 560px;
  max-height: 86vh;
  /* 竖排三段（头/身/脚），只有**身体**滚动：这样"保存/取消"永远在视口里。
     早先是整个 .modal 滚动，内容一长按钮就滚出屏幕 —— 手机上尤其像"没有保存
     按钮"。overflow:hidden 是为了让 max-height 真正生效于 flex 容器。 */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-lg);
}
.modal-head {
  display: flex;
  flex: none;
  align-items: center;
  gap: 10px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.modal-title { font-size: 15px; font-weight: 650; }
.modal-body {
  padding: 20px;
  flex: 1 1 auto;
  min-height: 0; /* flex 子项默认 min-height:auto，不设它内部滚动不会生效 */
  overflow-y: auto;
}
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
  background: var(--card2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* 代码块 */
.code {
  position: relative;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card2);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  overflow-x: auto;
  white-space: pre;
  margin: 0;
}

/* 进度条 */
.bar {
  height: 6px;
  border-radius: var(--r-full);
  background: var(--bg2);
  overflow: hidden;
}
.bar-fill {
  height: 100%;
  border-radius: var(--r-full);
  background: var(--grad);
  transition: width 0.4s ease;
}

/* ==========================================================================
   号池（账号池）
   ==========================================================================

   页面自上而下分四层，样式也按这个层次组织：

     - .pool-topbar / .pool-tab   切渠道（最常用的动作，放最上面）
     - .pool-title / .pool-stats  这一屏是哪个渠道、它现在什么状况
     - .pool-filter / .pool-fchip 在这个渠道的账号里找 / 筛
     - 表格与分页                 明细本身

   每一层只负责一件事：切换条不承担统计，统计不承担筛选，筛选不承担明细。
   分层清楚后，任何一层变化（渠道多了、要加一个状态口径）都不会牵动其它层。
   ========================================================================== */

/* ---- 渠道切换条 ---- */

.pool-topbar {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* 渠道多到一行放不下时换行，而不是横向滚动：渠道是固定的几个，
   藏起任何一个都比多占一行更贵 —— 用户看不到它就不会想到去点。
   放在这里而不是撑宽容器，是因为父级 .pool-topbar 不换行，
   撑宽会把右侧的「刷新全部余额」挤出屏幕。 */
.pool-nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  flex: 1;
  min-width: 0;
  padding: 2px 1px;
}

/* 渠道色由内联的 --c 提供（数据驱动，不硬编码渠道）。
   底色/描边都由 color-mix 从 --c 派生：新增渠道时这里零改动。 */
.pool-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding: 6px 12px;
  border: 1px solid color-mix(in srgb, var(--c, var(--border)) 22%, var(--border));
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--c, var(--muted)) 6%, var(--card));
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, color 0.14s, box-shadow 0.14s;
}
.pool-tab i {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--c, var(--muted));
}
.pool-tab:hover {
  color: var(--txt);
  border-color: color-mix(in srgb, var(--c, var(--border)) 45%, var(--border));
}
.pool-tab[aria-pressed="true"] {
  color: var(--txt);
  background: color-mix(in srgb, var(--c, var(--pri)) 14%, var(--card));
  border-color: color-mix(in srgb, var(--c, var(--pri)) 50%, var(--border));
  box-shadow: var(--sh);
}
/* 计数用胶囊垫底：它是"这个渠道有几个账号"，与渠道名是两层信息 */
.pool-tab-count {
  min-width: 20px;
  padding: 0 5px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--c, var(--muted)) 16%, transparent);
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.pool-tab[aria-pressed="true"] .pool-tab-count {
  background: color-mix(in srgb, var(--c, var(--pri)) 22%, transparent);
}

/* ---- 卡片头：渠道名 + 统计 ---- */

.pool-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 680;
  letter-spacing: -0.015em;
}
.pool-title .dot {
  width: 9px;
  height: 9px;
  flex: none;
  border-radius: 50%;
}

/* 统计排一行；换行时行距略大，免得两行数字粘在一起 */
.pool-stats {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin-top: 5px;
}
.pool-stat {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
}
.pool-stat b {
  font-size: 13px;
  font-weight: 700;
  color: var(--txt);
  font-variant-numeric: tabular-nums;
}
.pool-stat.ok b { color: var(--ok); }
.pool-stat.warn b { color: var(--warn); }
.pool-stat.dim b { color: var(--dim); }

/* ---- 筛选栏 ---- */

/* 与表格拉开层次：底色比卡片浅一层，让"这是筛选控件"一眼可辨 */
.pool-filter {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card2);
}
.pool-filter-left {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}
.pool-search { max-width: 220px; }

/* 状态筛选胶囊："全部 / 正常 / 冷却 / 禁用"各带一个数量。
   选中态用品牌色描边 + 淡底，而不是仅靠 opacity —— 后者在浅色主题下
   几乎看不出差别，用户会以为筛选没生效。 */
.pool-fchip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  background: var(--card);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, color 0.14s;
}
.pool-fchip:hover { border-color: var(--border2); color: var(--txt); }
.pool-fchip[aria-pressed="true"] {
  border-color: var(--pri);
  background: color-mix(in srgb, var(--pri) 12%, transparent);
  color: var(--pri);
}

/* ---- 账号行内操作 ---- */

/* 四个按钮挤在一格里：收紧间距并右对齐。窄屏的覆盖规则在响应式一节。 */
.pool-ops {
  justify-content: flex-end;
  gap: 6px;
  flex-wrap: nowrap;
}

/* ---- 账号列表：单元格里的主/次两行 ---- */

/* 账号标识。主行是昵称（没有就用邮箱 / UID），次行是登录标识。
   两行都限制宽度并省略号：账号名可能很长（邮箱、UID），不加限制会把
   表格顶宽、把后面的列挤出可视区。 */
.acct-name {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-weight: 550;
}
.acct-name > .acct-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acct-sub {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 额度：主行是剩余总量，次行是套餐条数。数字用等宽对齐。 */
.acct-credit {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}
.acct-credit b { font-weight: 700; }

/* 需要单独上色的短标记（快过期额度 / 临近到期）。 */
.acct-flag { font-size: 11.5px; font-weight: 650; }
.acct-flag.warn { color: var(--warn); }

/* 异常账号：行首一条色条。冷却用琥珀、禁用用灰 —— 与状态徽标同一套色，
   但胜在整列扫过去就能定位，不必逐行读徽标。 */
tbody tr.acct-cooling > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
tbody tr.acct-disabled > td:first-child { box-shadow: inset 3px 0 0 var(--border2); }

/* ==========================================================================
   响应式
   ==========================================================================

   两档断点：
     1000px  栅格降列（平板竖屏 / 窄窗口）
     760px   手机布局：侧栏改顶部两行、表格改卡片、弹窗贴近全屏

   手机端按三条硬要求取舍每一条规则：
     1. 不出现横向滚动 —— 整页左右晃是"没适配"最直观的信号；
     2. 文字与按钮完整可见 —— 手机上**没有悬停**，被省略号截断的内容
        等于永久丢失（桌面端还能靠 title 补救）；
     3. 点击目标够大 —— 一行里挤四个小按钮是点不中的。
   ========================================================================== */

@media (max-width: 1000px) {
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* 仪表盘两栏在平板竖屏 / 窄窗口下改回单列 */
  .dash-split { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 760px) {
  /* ---- 骨架：侧栏变顶部两行（品牌 + 操作 / 导航） ----
     用 grid 划出两块区域，而不是把 brand、nav、side-foot 塞进同一行：
     同一行时那排操作按钮会被导航挤到屏幕外，用户得横向滚过八个菜单项
     才找得到「退出登录」。 */
  #app { grid-template-columns: minmax(0, 1fr); }

  .sidebar {
    position: sticky;
    top: 0;
    z-index: 30;
    height: auto;
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "brand foot"
      "nav   nav";
    align-items: center;
    gap: 8px 10px;
    padding: 8px 12px 10px;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .brand { grid-area: brand; padding: 0; }
  .brand-sub { display: none; }
  .side-foot { grid-area: foot; border-top: 0; padding-top: 0; }

  /* 导航横向滚动。文字**必须留着** —— 八个图标排一行，用户只能靠猜，
       猜错一次就要把每个都点开看。 */
  .nav {
    grid-area: nav;
    flex-direction: row;
    flex: none;
    gap: 6px;
    /* min-width:0 是这整块的关键：grid 子项默认 min-width:auto，
       会被内容（八个按钮）顶宽，于是整页横向溢出 —— 滚动区也就不会出现。 */
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* 负外边距 + 等量内边距：滚到两端时首尾项不贴边，不像被裁掉一截。 */
    margin: 0 -12px;
    padding: 2px 12px;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav-btn {
    width: auto;
    flex: none;
    padding: 7px 11px;
    white-space: nowrap;
  }
  .nav-btn span { display: inline; }
  /* 角标回到文字右边。桌面端它靠 margin-left:auto 顶到行尾，
     在横向滚动的按钮里那样会把按钮撑得忽宽忽窄。 */
  .nav-dot { margin-left: 0; }

  /* 顶栏不再吸顶：上面已经有一条吸顶导航，两条叠起来吃掉近三分之一屏高。
     标题随内容滚走，导航留下 —— 导航是每次都要用的，标题看一次就够。 */
  .topbar {
    position: static;
    height: auto;
    padding: 10px 12px;
    gap: 10px;
  }

  .content { padding: 12px 12px 56px; }

  /* ---- 通用：窄屏一律允许换行 ----
     宁可占两行，也不要溢出或把相邻元素挤到看不见。 */
  .row { flex-wrap: wrap; }

  /* ---- 号池 ----
     渠道切换条占满一行横向滚动（与顶部导航同一套手势），
     「刷新全部余额」落到下一行；筛选栏改为上下堆叠，搜索框占满宽度。 */
  .pool-topbar { flex-wrap: wrap; }
  /* 手机上渠道切换条回到横向滚动：六个胶囊换行会占掉三四行、把账号
     列表顶到屏幕外，而横向滚动是顶部主导航已经建立的同一套手势。 */
  .pool-nav {
    flex-basis: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .pool-nav::-webkit-scrollbar { display: none; }
  .pool-filter { align-items: stretch; }
  .pool-filter-left { flex-basis: 100%; }
  .pool-search { max-width: none; }

  /* 账号列表：主/次两行在手机上完整显示（没有悬停，省略号等于信息丢失） */
  table.stack .acct-name > .acct-title,
  table.stack .acct-sub { white-space: normal; overflow: visible; }
  table.stack .acct-credit { justify-content: flex-start; }
  .card-head {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 14px;
  }
  /* card-head 在窄屏变成纵向排列（align-items: stretch），直接子元素里的
     徽章会被拉成整行宽 —— 一条整行宽的绿色胶囊看起来像横幅，
     而不是状态标记。让徽章按内容宽度左对齐。 */
  .card-head > .badge { align-self: flex-start; }
  .card-head-actions { margin-left: 0; flex-wrap: wrap; }
  /* 卡片头里的搜索 / 筛选控件在窄屏占满整行：280px 的固定宽度在
     390px 屏上只剩一条，而它们本来就是"在这一屏里找"的输入。 */
  .card-head-actions .input { max-width: none; flex: 1 1 100%; }

  /* 接入指引的「标签 + 端点」在窄屏上下堆叠，让地址拿到整行宽度 */
  .kv { flex-direction: column; gap: 4px; }
  .kv-label { min-width: 0; padding-top: 0; }
  .card-body { padding: 14px; }
  .pager { flex-wrap: wrap; gap: 8px 12px; }

  /* 指标卡保持两列：改成单列会让六个数字卡占掉两屏多，
     而它们本来只需要扫一眼。 */
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* 输入控件字号必须 ≥16px：iOS Safari 在更小字号上会在获得焦点时
     自动放大整个页面，而且放大后不会自己缩回来。 */
  .input, select.input, textarea.input { font-size: 16px; }

  /* ---- 表格：一条记录一张卡片 ----
     手机上任何多列表格都会退化成"每列几十像素 + 满屏省略号"，横向滚动
     又看不到行内上下文。改成把每一行拆成"标签: 值"的堆叠块，标签取自
     表头 —— 由 ui.js 的 labelize() 在渲染时写进 data-label，
     这样加列、改列名时不会与这里脱节。 */
  .tablewrap { overflow-x: visible; }

  table.stack { display: block; width: 100%; }
  table.stack colgroup,
  table.stack thead { display: none; }
  table.stack tbody { display: block; }
  table.stack tr {
    display: block;
    margin-bottom: 10px;
    padding: 5px 0;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--card2);
  }
  table.stack tbody tr:last-child { margin-bottom: 0; }

  /* 明细行（错误详情 / 模型测试结果 / 账号套餐明细）跟在主卡片后面，
     它不是"另一条记录"：去掉边框与内边距、并向上收一点，看起来才像
     上一张卡的延续，而不是又一条记录。
     判据用"行里有没有跨列单元格" —— 这五处明细行都是这个形状，而正常
     记录行的单元格数与表头一一对应，永远不会命中。
     :has() 在旧浏览器上会让整条规则失效，退化成普通卡片样式，不会坏。 */
  table.stack tr:has(> td[colspan]) {
    margin: -4px 0 10px;
    padding: 0;
    border: 0;
  }

  /* 三个选择器写全：table.logtable 对首/末列另有 padding 规则（特异性更高），
     只写 `table.stack td` 会让表格左右两条边缩进 18px、中间 12px。 */
  table.stack td,
  table.stack td:first-child,
  table.stack td:last-child {
    display: block;
    width: auto;
    padding: 5px 12px;
    border: 0;
    text-align: left;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    word-break: break-word;
  }
  /* 真·空单元格（账号套餐明细末尾的占位列）在卡片里只会多出一条空白 */
  table.stack td:empty { display: none; }
  table.stack td::before {
    content: attr(data-label);
    display: inline-block;
    min-width: 66px;
    margin-right: 8px;
    color: var(--dim);
    font-size: 11.5px;
    font-weight: 650;
    vertical-align: top;
  }
  /* 没有表头的列（操作列）不摆标签，只留内容 */
  table.stack td[data-label=""]::before { display: none; }

  /* 两行单元格在卡片里：首行与标签同一行，次行另起一行 ——
     否则标签会孤零零占一行（块级子元素把 ::before 挤到独立行盒）。
     手机上**没有悬停**，被省略号截断等于永久丢失，所以这里把省略号关掉，
     靠换行完整显示（与 .stack 的其它规则同一条原则）。 */
  table.stack td > .cell-line {
    display: inline;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
  }
  table.stack td > .cell-line:first-child + .cell-line { display: block; }
  /* 数字列不再右对齐：左对齐后与上面的标签落在同一条竖线上 */
  table.stack td.num { text-align: left; }
  /* 桌面端靠 max-width + 省略号控制宽度的单元格，在卡片里应当完整显示 */
  table.stack td.trunc { max-width: none; }
  /* 行内动作按钮改左对齐并换行：桌面端靠右对齐，手机上四个按钮挤一行时
     最窄的那个会窄到点不中。 */
  table.stack td > .row { justify-content: flex-start !important; }
  table.stack tbody tr:hover { background: var(--card2); }

  /* ---- 弹窗：贴近全屏 ----
     留 10px 边框是为了让用户仍能看出"这是一个浮层"、点边缘可以关掉。 */
  .modal-backdrop { padding: 10px; }
  .modal {
    max-height: calc(100vh - 20px);
    /* dvh 跟随移动端地址栏的收放；不支持时上面那行生效 */
    max-height: calc(100dvh - 20px);
  }

  /* ---- 其它 ---- */
  /* 代码块保持横向滚动（折行会毁掉 curl 命令的可复制性），
     但字号要压小，否则一行只看得见十几个字符。 */
  .code { font-size: 11.5px; padding: 10px 11px; }
  .toasts { left: 12px; right: 12px; bottom: 12px; }
  .toast { max-width: none; min-width: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---- 模型测试结果框 ---- */
/*
 * 对标旧项目的测试结果展示：把**回复原文**展开在模型行下面。
 *
 * 只给一个 "1234ms" 徽标时，测试通过也看不出模型答了什么 ——
 * 失败时更分不清是"模型不认"、"参数不对"还是"内容被拦"。
 *
 * 等宽字体 + pre-wrap：回复里常有换行与缩进，用普通字体+nowrap
 * 会把格式吃掉，而格式本身往往就是判断"这个模型答得对不对"的依据。
 */
.tresult {
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  background: var(--card2);
  padding: 8px 10px;
  font-size: 0.86rem;
  line-height: 1.55;
}
.tresult.ok  { border-left-color: var(--ok); }
.tresult.err { border-left-color: var(--err); }

.tresult .trmeta {
  color: var(--muted);
  font-size: 0.8rem;
  margin-bottom: 4px;
}
.tresult.err .trmeta { color: var(--err); }

.tresult .trtext {
  font-family: var(--mono);
  font-size: 0.84rem;
  color: var(--txt);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 260px;
  overflow: auto;
}

/* 模型测试结果里的"上游实际作答"一行。
 *
 * 用 warn 色而不是 err：它**不是失败**（请求成功了），但"上游给的模型标识
 * 和你要的不是一个"同样需要一眼看见 —— 用普通文字色的话，测试通过时用户
 * 不会注意到这一行，会继续以为那个模型真的可用。
 *
 * 布局用 flex + wrap：模型标识可能很长（accounts/fireworks/models/...），
 * 窄屏下必须能折行，否则会把结果框撑出横向滚动。 */
.tresult .trroute {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
  padding: 6px 8px;
  border-radius: var(--r-sm);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: 0.8rem;
  line-height: 1.5;
  word-break: break-all;
}
.tresult .trroute-tag {
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid currentColor;
}
.tresult .trroute-arrow { font-weight: 600; }
.tresult .trroute-real { font-weight: 600; }
.tresult .trroute-note {
  /* 说明文字弱化：它是注解，不该和"实际用了哪个模型"抢注意力。 */
  opacity: 0.75;
  flex-basis: 100%;
}
