/* ===========================================================================
   WorkBuddy2API 中转站 · 统一样式
   布局骨架：左侧固定侧边栏 + 右侧主区（.app / .sidebar / .main）
   组件：KPI 卡、实体卡片（.ecard）、设置卡、任务卡、台账卡、进度条、开关、迷你柱图
   =========================================================================== */

/* ---------------------------------------------------------------------------
   主题（v0.17.8）：浅色为默认，深色支持**手动切换 + 持久化**。
   两条路径，优先级从低到高：
     1) 未手选主题（<html> 无 data-theme）→ 跟随系统 prefers-color-scheme；
     2) 手选主题（data-theme="dark" / "light"）→ 强制生效，覆盖系统偏好。
   `color-scheme` 同步声明，让原生控件（滚动条 / 日期选择器）跟着变。
   --------------------------------------------------------------------------- */
:root {
  --bg: #f5f6f8;
  /* --bg-2：比 --bg 再压一档的代码块/预格式底色（v0.31.200）。
     它此前被 4 处 `var(--bg-2,#f6f7f9)` 引用**但从来没定义过** —— 永远走 fallback
     的浅灰，暗色主题下就成了"浅灰底 + 浅色字"，那几块内容等于看不见。 */
  --bg-2: #f6f7f9;
  --card: #ffffff;
  --panel: #ffffff;
  --text: #1c1e21;
  /* --muted 压深（v0.31.200）：#6b7280 压在 ok-tint(#e8f2ec) 上只有 4.23:1，
     而"次要说明 + 彩色底"这个组合在概览卡里到处都是。 */
  --muted: #5c6673;
  --border: #e3e5e8;
  /* --accent 压深（v0.31.200，审查 UI-6）：原 #4a6cf7 上压白字只有 **4.39:1**，
     差 0.11 到不了 AA 4.5。它是全站按钮/激活项/标签底色，143 处文本受影响。
     #4563f0 的白字对比 4.80:1，色相与观感几乎不变。
     --accent-ink 是**文字**用的更深一档（压在 tint 浅底上也要 4.5+）。 */
  --accent: #4563f0;
  --accent-ink: #2d47c8;
  /* on-accent：**压在 accent 上的前景色**。以前每处直接写 #fff，
     深色主题换 accent 时它们不会跟着变，只能一个个找。 */
  --on-accent: #fff;
  --accent-2: #7c5cf7;
  /* 三个语义色同样压深一档（v0.31.200）：它们既做文字色也做 tint 底，
     原值在 tint 上都差一点点（ok 4.38 / warn 4.26）。 */
  --ok: #146c33;
  --warn: #9a4708;
  --err: #b91c1c;
  --tint: rgba(74, 108, 247, .05);
  --ok-tint: rgba(23, 128, 61, .10);
  --warn-tint: rgba(180, 83, 9, .12);
  --err-tint: rgba(185, 28, 28, .10);
  --shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 8px 22px rgba(15, 23, 42, .05);
  --radius: 14px;
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg: #14161a;
    --bg-2: #22262d;
    --card: #1c1f25;
    --panel: #1c1f25;
    --text: #e8eaed;
    --muted: #9aa0a6;
    --border: #2e3238;
    /* 暗色下 --accent 保持亮蓝（深色主题里它要显眼），但**压在上面的字改成深色**：
       #5b7cfa 上的白字只有 3.68:1，深字 #0f1115 是 4.59:1。
       这也是 Material 深色主题的通行做法（亮底 + 深字），比"把蓝压暗"观感好得多。 */
    --accent: #5b7cfa;
    --accent-ink: #a9bcff;
    --on-accent: #0f1115;
    --accent-2: #8b74f8;
    --ok: #4ade80;
    --warn: #fbbf24;
    --err: #f87171;
    --tint: rgba(91, 124, 250, .10);
    --ok-tint: rgba(74, 222, 128, .12);
    --warn-tint: rgba(251, 191, 36, .14);
    --err-tint: rgba(248, 113, 113, .12);
    --shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 26px rgba(0, 0, 0, .35);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --bg: #14161a;
  --bg-2: #22262d;
  --card: #1c1f25;
  --panel: #1c1f25;
  --text: #e8eaed;
  --muted: #9aa0a6;
  --border: #2e3238;
  --accent: #5b7cfa;
  --accent-ink: #a9bcff;
  --on-accent: #0f1115;
  --accent-2: #8b74f8;
  --ok: #4ade80;
  --warn: #fbbf24;
  --err: #f87171;
  --tint: rgba(91, 124, 250, .10);
  --ok-tint: rgba(74, 222, 128, .12);
  --warn-tint: rgba(251, 191, 36, .14);
  --err-tint: rgba(248, 113, 113, .12);
  --shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 26px rgba(0, 0, 0, .35);
  color-scheme: dark;
}
:root[data-theme="light"] {
  --bg: #f5f6f8;
  /* --bg-2：比 --bg 再压一档的代码块/预格式底色（v0.31.200）。
     它此前被 4 处 `var(--bg-2,#f6f7f9)` 引用**但从来没定义过** —— 永远走 fallback
     的浅灰，暗色主题下就成了"浅灰底 + 浅色字"，那几块内容等于看不见。 */
  --bg-2: #f6f7f9;
  --card: #ffffff;
  --panel: #ffffff;
  --text: #1c1e21;
  /* --muted 压深（v0.31.200）：#6b7280 压在 ok-tint(#e8f2ec) 上只有 4.23:1，
     而"次要说明 + 彩色底"这个组合在概览卡里到处都是。 */
  --muted: #5c6673;
  --border: #e3e5e8;
  /* --accent 压深（v0.31.200，审查 UI-6）：原 #4a6cf7 上压白字只有 **4.39:1**，
     差 0.11 到不了 AA 4.5。它是全站按钮/激活项/标签底色，143 处文本受影响。
     #4563f0 的白字对比 4.80:1，色相与观感几乎不变。
     --accent-ink 是**文字**用的更深一档（压在 tint 浅底上也要 4.5+）。 */
  --accent: #4563f0;
  --accent-ink: #2d47c8;
  --accent-2: #7c5cf7;
  /* 三个语义色同样压深一档（v0.31.200）：它们既做文字色也做 tint 底，
     原值在 tint 上都差一点点（ok 4.38 / warn 4.26）。 */
  --ok: #146c33;
  --warn: #9a4708;
  --err: #b91c1c;
  --tint: rgba(74, 108, 247, .05);
  --ok-tint: rgba(23, 128, 61, .10);
  --warn-tint: rgba(180, 83, 9, .12);
  --err-tint: rgba(185, 28, 28, .10);
  --shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 8px 22px rgba(15, 23, 42, .05);
  color-scheme: light;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg); color: var(--text);
  font: 14px/1.6 -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
}
h1 { font-size: 19px; font-weight: 650; margin: 0 0 3px; }
h2 { font-size: 14px; font-weight: 600; margin: 0 0 12px; }
a { color: var(--accent-ink); word-break: break-all; text-decoration: none; }
a:hover { text-decoration: underline; }
.sub { color: var(--muted); font-size: 12px; }

/* 折叠说明（v0.39.82）：一屏里最该被看见的是**队列本身**，不是记账口径的长段落。
   收成一行摘要，展开才占位；摘要那行只留"讲的是什么"，细节在里面。 */
.disc > summary { cursor: pointer; font-size: 12px; color: var(--muted); margin: 0 0 10px; }
.disc > summary:hover { color: var(--accent-ink); }
.disc[open] > summary { color: var(--text); margin-bottom: 12px; }
.disc > .sub { margin: 0 0 12px; }

/* 设置页页内跳转条（v0.39.83）：16 张卡 / 约 6300px，没有导航时改一个参数要滚 7 屏。
   sticky 是为了"跳一次 → 读几屏 → 再跳"时不用先滚回顶部。 */
.page-nav, .set-nav { position: sticky; top: 0; z-index: 6; display: flex; flex-wrap: wrap; gap: 6px;
  background: var(--bg); padding: 6px 0 10px; margin: 0 0 10px; border-bottom: 1px solid var(--border); }
/* 16 个锚点会占两行；吸顶条每多一行就少一行内容，所以这里用小一号的 chip。 */
.page-nav .chip, .set-nav .chip { font-size: 11.5px; padding: 2px 9px; }

/* 市场双列（v0.39.82）：「在售账号」与「可上架候选」是同一件事的两面（谁在卖 / 谁可以卖），
   单列时两张卡右边各有 2/3 空白、整页被拉到 1100px+。窄屏仍退回单列。 */
.market-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
.market-pair > div > h3 { margin-top: 0; }
@media (max-width: 1100px) { .market-pair { grid-template-columns: 1fr; } }

input, select, textarea {
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 10px; font-size: 13px; font-family: inherit;
}
input[type=text], input[type=password], input[type=number] { min-width: 160px; }
textarea { width: 100%; min-height: 84px; resize: vertical; font-family: ui-monospace, Consolas, monospace; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--tint); border-color: var(--accent); }

button {
  background: var(--accent); color: var(--on-accent); border: 0;
  border-radius: 8px; padding: 7px 14px; font-size: 13px;
  cursor: pointer; font-family: inherit;
}
button:hover { filter: brightness(1.06); }
button:disabled { opacity: .5; cursor: not-allowed; filter: none; }
button.ghost { background: transparent; color: var(--text); border: 1px solid var(--border); }
button.ghost:hover { background: var(--tint); }
button.danger { background: transparent; color: var(--err); border: 1px solid var(--border); }
button.danger:hover { background: var(--err-tint); }
button.sm { padding: 5px 10px; font-size: 12px; }
button.block { width: 100%; }

.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.row.spread { justify-content: space-between; }
.row.end { justify-content: flex-end; }
.row.tight { gap: 5px; }
label { font-size: 12px; color: var(--muted); }

.card {
  min-width: 0;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px; margin-bottom: 16px;
}
.card > h2 { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

/* ============ v0.40：主题卡 + 一体多格（用户 #7/#15/#17） ============
 *
 * 为什么不再一卡一框：管理端概览峰值 15 张卡、report 20 张，且卡片套卡片、
 * 靠 `style="margin-top:16px"` 撑间距 —— 视觉上是"一堆框 + 一堆留白"，
 * 用户的原话是"卡片功能重复、同类数据区分太多、留空窗太多"。
 * 现在同主题的多块并进一张 .mcard 的多个 .mcell 格里：
 * 信息一点不少，但只有一层边框、一套间距。
 */
.mcard {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px 16px;
  margin-bottom: 14px;
  min-width: 0;
}
.mtitle {
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  color: var(--muted);
  margin: 0 0 10px;
}
.mgrid {
  display: grid;
  /* 12 列固定栅格 + 显式宽度类：auto-fit 看着聪明，实际会把"内部已是 grid 的块"
   * （一排 KPI、四张洞察）压成竖排 —— 第一版就这么翻车的（截图里 KPI 竖着排了一列）。
   * 固定 12 列后，每个格自己声明要几列，行为可预期。 */
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 14px 18px;
  /* v0.40.13 回退 stretch（用户 17 条复核：「留空窗太多」「间距和自适应完全出问题」）。
   *
   * 教训：v0.40 为消除"同排底边参差"改成 `stretch`，把**底部参差**换成了**大片空白** ——
   * 实测 admin 概览「账号健康度」684 高废 528、桌面托管「账号镜像」3732 高废 3621、
   * 设置「RateGate 配置」1261 高废 878。用户要的是"别留空窗"，不是"底边必须齐"。
   * 卡片各按内容高度、底边自然参差，才是这类"长短不一的内容块"的正确排法。
   * → 回到 start。**这一条不要再改回 stretch。** */
  align-items: start;
}
.mcell {
  min-width: 0; grid-column: span 4;
  /* 格内仍是 flex 列（内容顶端对齐）；但**不再靠外部 stretch 拉高** —— 
     格子的高度由内容决定，`margin-top:auto` 这类对齐只在格内自行需要时生效。 */
  display: flex; flex-direction: column;
}
.mcell.w12 { grid-column: span 12; }
.mcell.w6  { grid-column: span 6; }
.mcell.w3  { grid-column: span 3; }
.mcell > h2 {
  display: block; font-size: 12.5px; font-weight: 600; color: var(--muted);
  margin: 0 0 8px;
}
/* 跨列：图表这类需要宽度的格 */
.mcell.span2 { grid-column: span 2; }
.mcell.span3 { grid-column: span 3; }
@media (max-width: 1100px) {
  .mcell, .mcell.w6, .mcell.w3 { grid-column: span 6; }
  .mcell.w12 { grid-column: span 12; }
}
@media (max-width: 760px) {
  .mcell, .mcell.w6, .mcell.w3, .mcell.w12 { grid-column: span 12; }
}

/* v0.40（用户 #16）：页内分段按钮与上下的卡片贴得太近（"按钮与上下卡片间隔不够"）。
 * 段切换条自己是个 .row，前后都没有外边距，于是紧贴着上一张卡的下边缘、
 * 又被下一张卡的 margin 顶住 —— 视觉上像是卡的一部分。给上下各留 18px。 */
#usSegTabs, .egr-sub + .card { margin: 18px 0; }

/* 用量分析：一页 6 主卡（v0.40.38）。
   改版前是「6 摘要卡 + 5 个二级页」，看全要点 6 次；现在按主卡分组一页到底，
   每个 .mcard 里装着原来的若干 .card。为了不出现"卡里套卡"的双层边框，
   组内的 .card 去掉外壳，用虚分隔线代替。 */
/* 主卡锚点条（v0.40.44）：吸顶 + 随滚动高亮。
   一页 6 主卡在手机上 13 屏、桌面 7900px，没有它就得靠"滚回顶部点摘要卡"换位置。
   标签刻意用两字（筛选/按日/维度/失败/请求/性能）—— 手机上一行放得下，不必换行也不必横滚
   （之前二级页切换条用长标签，单行横滚会把选中项推出屏幕，已回退过一次）。 */
#uaAnchors {
  position: sticky; top: 0; z-index: 8;
  background: var(--bg);
  padding: 8px 0 9px; margin: 0 0 12px;
  border-bottom: 1px solid var(--border);
  gap: 6px; flex-wrap: wrap;
}
#uaAnchors .chip { font-size: 12px; padding: 5px 11px; }
/* 跳到主卡时要留出吸顶条的高度，否则标题会被压在条下面 */
.pane[data-pane="report"] .mcard { scroll-margin-top: 58px; }
/* 组内的"小主卡"（原 .mcard：按维度汇总/逐笔、请求记录）不再画第二层卡框，
   否则一页看起来像 9 张卡而不是 6 张。 */
.pane[data-pane="report"] .msub { min-width: 0; }
.pane[data-pane="report"] .msub > .mtitle {
  font-size: 13px; color: var(--muted); font-weight: 600; margin: 14px 0 8px;
  padding-top: 12px; border-top: 1px dashed var(--border);
}
.pane[data-pane="report"] .mcard > .card {
  border: 0; padding: 0; margin: 0; background: transparent;
}
.pane[data-pane="report"] .mcard > .card + .card,
.pane[data-pane="report"] .mcard > .card + .section-head,
.pane[data-pane="report"] .mcard > .section-head + .card,
.pane[data-pane="report"] .mcard > .section-head + .sub,
.pane[data-pane="report"] .mcard > .sub + .card {
  margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--border);
}
/* 组内第二个起的 h2 降一级：主卡标题是 .mtitle，组内分块标题不该和它一样重 */
.pane[data-pane="report"] .mcard > .card > .section-head h2:not(:first-child) { font-size: 15px; }

/* v0.40.36 给二级页切换条 #uaTabs 做过吸顶；v0.40.38 合并成一页 6 主卡后**该元素已不存在**
   （生产实测 `id="uaTabs"` 0 处），吸顶职责由 #uaAnchors 承担。
   规则已删 —— 留着死选择器比没有更糟：下次有人 grep "吸顶不生效" 会先命中它，白查一轮。
   保留这一行是为了让 grep 到的结果能直接说明"它为什么不在"。 */
#usSegTabs + [data-uview] { margin-top: 0; }

/* v0.40（用户 #14）：充值/提现两块并排但要收紧 —— 门禁要求它们仍在同一个 .live-grid 里
 * （两张卡并排是这条页面的既有语义，改成一张卡的格子会丢掉它），
 * 所以这里不动结构，只压内边距与间隙，把"留空窗太多"收掉。 */
.live-grid { gap: 12px; }
.live-grid > .card { padding: 14px; margin-bottom: 0; }
.live-grid > .card .kpis { gap: 10px; }

.muted { color: var(--muted); }
.ok { color: var(--ok); } .warn { color: var(--warn); } .err { color: var(--err); }
.mono { font-family: ui-monospace, Consolas, monospace; }
.hidden { display: none !important; }
.right { text-align: right; }

.tag { font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); display: inline-block; white-space: nowrap; }
.tag.ok { color: var(--ok); border-color: transparent; background: var(--ok-tint); }
.tag.warn { color: var(--warn); border-color: transparent; background: var(--warn-tint); }
.tag.err { color: var(--err); border-color: transparent; background: var(--err-tint); }
.tag.accent { color: var(--accent-ink); border-color: transparent; background: var(--tint); }

code, .code {
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 1px 6px; font-family: ui-monospace, Consolas, monospace; font-size: 12px;
}
pre.code { display: block; padding: 12px; overflow-x: auto; line-height: 1.55; }

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 500; font-size: 12px; }

/* ---------- 应用外壳：侧边栏 + 主区 ---------- */
.app { display: flex; min-height: 100vh; align-items: stretch; }
.sidebar {
  flex: 0 0 224px; width: 224px;
  background: var(--card); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 18px 16px 14px; }
.brand .logo {
  width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--on-accent); display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 12px; letter-spacing: .5px;
}
.brand b { display: block; font-size: 13px; line-height: 1.3; }
.brand span { font-size: 11px; color: var(--muted); }
.nav { display: flex; flex-direction: column; gap: 2px; padding: 4px 10px 12px; flex: 1; }
.nav-group { font-size: 10.5px; color: var(--muted); letter-spacing: .06em; padding: 12px 12px 4px; text-transform: uppercase; }
.nav-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: 9px; cursor: pointer;
  border: 0; background: transparent; color: var(--text);
  font-size: 13px; text-align: left; font-family: inherit;
}
/* hidden 必须显式写一遍：.nav-item 设了 display:flex，会盖掉浏览器默认的
   [hidden]{display:none}（UA 样式优先级最低）。不写这条，`it.hidden = true`
   在界面上毫无效果 —— 这是「属性设了、看着没变」的典型来源（v0.31.198）。 */
.nav-item[hidden], .nav-group[hidden] { display: none; }
.nav-item .ico { width: 18px; text-align: center; opacity: .85; font-size: 14px; }
.nav-item:hover { background: var(--tint); }
.nav-item.active { background: var(--accent); color: var(--on-accent); }
.nav-item.active .ico { opacity: 1; }
.nav-item .badge { margin-left: auto; font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--tint); color: var(--accent-ink); }
.nav-item.active .badge { background: rgba(255,255,255,.22); color: var(--on-accent); }
.sidebar-foot { border-top: 1px solid var(--border); padding: 12px; }
.sidebar-foot .whoami { font-size: 12px; color: var(--muted); margin-bottom: 8px; word-break: break-all; line-height: 1.4; }
.sidebar-foot .foot-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.sidebar-foot .foot-actions a { font-size: 12px; padding: 5px 10px; border: 1px solid var(--border); border-radius: 8px; color: var(--text); }

.main { flex: 1; min-width: 0; padding: 22px 26px 64px; }
.topbar { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.topbar .sub { margin-top: 2px; }
.topbar .actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ---------- KPI 统计卡 ---------- */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 12px; }

/* 概览左主右辅（v0.40.47，方案 A）：曲线占 2 份，健康/健康度竖排占 1 份。
   ≤1100px 直接单列 —— 两栏在半宽下会把曲线压扁，柱状图反而更难读。 */
.ov-split { display: grid; grid-template-columns: 1.7fr 1fr; gap: 16px; align-items: start; }
.ov-side { display: grid; gap: 14px; }
.ov-side > div > h2 { margin: 0 0 8px; }
@media (max-width: 1100px) { .ov-split { grid-template-columns: 1fr; } }

/* 代挂审核的账号卡（v0.40.49）：原来走 .cards 的 auto-fit 1fr，宽屏会把每张卡
   拉伸到 300~400px 以上，卡片里只有几行信息，整页看着很空且错位。
   这里给列宽设上限，宽屏多列排、窄屏自动塌成单列。 */
#hostCards { grid-template-columns: repeat(auto-fill, minmax(300px, 340px)); justify-content: start; }

/* 号池洞察四卡等高（v0.40.48）：四张卡原本按内容高度各排，一眼看不出哪张更重要。
   统一行高 + 卡片撑满，四张卡的底边就对齐了。 */
#poolInsight .live-grid { grid-auto-rows: 1fr; align-items: stretch; }
#poolInsight .card { height: 100%; }

/* 用户控制台两张卡高度不齐（v0.40.52）：短卡不拉伸，避免卡内出现大片空白；
   两卡各自按内容高度排，底边不齐但不会有"半空的盒子"。 */
.live-grid { align-items: start; }

/* 请求曲线空态（v0.40.53）：低峰期没有请求时，柱子区高度为 0，左栏塌成一片空白，
   而右栏（健康/健康度）仍有内容，整行看起来像"加载失败"。给一个最小高度兜住。 */
#ovBars { min-height: 120px; }

/* 设置页表单「左标签 / 右控件」两列（v0.40.56，方案 B 第一步：仅「计费与注册」）。
   只作用于该卡的直属行，不动 DOM、不动字段绑定 —— 改错也不会把值存到别的字段。 */
/* 设置页保存按钮统一（v0.40.57）：各卡的行高不一致，按钮有的贴顶、有的居中，
   看起来像"同一页两套控件"。统一行内对齐 + 按钮高度。 */
/* 设置页「主分类条收纳」（v0.40.58 方案 A′）：**已由对端在 v0.40.59 撤回**，本块规则连同
   #navToggle 一并删除。撤回原因记录在此，避免有人再踩同样的坑 ——
   把全站侧栏 `nav.nav` 整块 display:none 之后，宽屏下 `.nav-item[data-nav]` 不可点：
   verify_all 实测 siteprobe 4 红（1440/1180 × 深浅色，click 超时 7s）+ cspsmoke 1 红
   （click 超时 4s），两处都卡在"点侧栏入口"这个基础契约上。
   要继续做这个交互，必须同时适配 siteprobe 的切页与 cspsmoke 的 20 个侧栏入口。
   另：给 body 加「当前页」属性时名字不能撞 `[data-pane="x"]` 前缀（详见 admin/core.js 同段注释）。 */

section[data-pane="settings"] .mcard .row { align-items: center; }
section[data-pane="settings"] .mcard .row > button { min-height: 34px; }

/* 方案 B 推进（v0.40.60）：把同样的「左标签 / 右控件」应用到另外两张表单卡。
   仍只改 CSS —— 字段 id、顺序、绑定一律不动，出错面仅限观感。 */
#set-sec-share .mcell > .row,
#set-sec-limit .mcell > .row { display: grid; grid-template-columns: 200px minmax(0, 1fr);
  align-items: center; gap: 12px; }
#set-sec-share .mcell > .row > label,
#set-sec-limit .mcell > .row > label { margin: 0; }
@media (max-width: 720px) {
  #set-sec-share .mcell > .row,
  #set-sec-limit .mcell > .row { grid-template-columns: 1fr; gap: 4px; }
}

/* 六域状态条（v0.40.74）：一屏顶部的"体检表"。
   6 格等宽，窄屏 2 列；状态灯只有三色，且必须配文字（副行已有口径说明）。 */
/* 概览扁平化（第二轮，v0.40.79）：三层 → 一层。
   改版前是 mcard（卡）→ mgrid（12 栏）→ mcell（格）：同一条信息画了两次边框、
   留了三套间距，用户的原话是"卡片功能重复、留空窗太多"。
   现在 .ov-grid 自己承载 12 栏，.ovc 本身就是卡 —— 没有包裹层，也没有"卡里套卡"。

   同行等高靠 grid 的 align-items:stretch（同一行的块被拉成同一高度，底边对齐）。
   ⚠️ **不要**给 .ov-grid 加 grid-auto-rows:1fr：那会让**所有行**都等于最高行，
   正是 v0.40.13 记下的"684 高废 528"那种大片空白。等高只该在同一行内成立。 */
.ov-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 14px 18px; align-items: stretch; }
.ovc {
  grid-column: span 4; min-width: 0;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 13px 15px 15px;
  display: flex; flex-direction: column; height: 100%;
}
.ovc.span6 { grid-column: span 6; }
.ovc.span8 { grid-column: span 8; }
.ovc.span12 { grid-column: span 12; }
.ovc > h2, .ovc > .section-head { margin: 0 0 8px; }
/* 块内的"可长"区域：事件流吃掉块高 —— 同行等高时空白落在列表内，而不是块底。
   高度上限仍是 340px（"只保留最近 40 条"原本就靠这个封顶，去掉会把整页拉长 1000px）。 */
.ovc-feed { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ovc-feed .feed { max-height: 340px; height: 100%; flex: 1 1 auto; min-height: 120px; }

/* 块内自适应（v0.40.80）：这一条是"同行等高"能成立的前提。
 *
 * 等高只保证底边对齐 —— 如果块里最矮的那块内容顶在上沿，对齐之后多出来的高度
 * 就堆成块底的一条白（用户上一版看到的就是这个）。所以每块都要指定**谁来吃这个高度**：
 * 曲线、环形图、条形榜、通道表、事件流各自撑满，短内容在块内上下均分。
 * 没有这一步，等高就是"把页面留白搬进卡片"。
 */
/* ⚠️ 这里**不能**写 height:auto：柱子的高度是容器高度的百分比，
   容器一旦落到 auto，百分比失去参照、柱子全部塌成 0（窄屏单列时最明显）。
   保留 `.bars` 原本的 96px 作为 flex-basis，有富余高度时再 grow。 */
.ovc > .bars { flex: 1 1 auto; min-height: 96px; }
.ovc > .bars + .bars-axis { flex: 0 0 auto; }
.ovc > .donut-row,
.ovc > #ovSplit,
.ovc > #poolServed,
.ovc > #poolTopCredit,
.ovc > #ovChannels,
.ovc > #poolInsight { flex: 1 1 auto; min-height: 0; }
/* 账号健康度（v0.40.102）：环形图放大 + 每段一条比例条 + 底部可用率。
   原来只有「9 · 75%」三行文字，三段的相对大小要靠心算；
   加条之后"这一段占多少"直接画出来，和右侧卡片的视觉密度也对得上。 */
#poolHealth .donut-svg { width: 132px; height: 132px; }
.kv-bar { height: 4px; border-radius: 2px; background: rgba(127,127,127,.16); overflow: hidden; margin: 3px 0 7px; }
.kv-bar i { display: block; height: 100%; border-radius: 2px; }
.kv-foot { font-size: 11px; color: var(--muted); line-height: 1.5; }
.kv-foot b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* ============ v0.40.103：桌面托管页三段式 ============
 * 态势(12) → 申请(12) → 账号(6)+镜像(6) → 版本与交付(12，默认收起)。
 * 与概览同一套 12 栏语言，但这是**独立页**（不是 .ovc），所以单独一组 .dk-* ——
 * 混用会让"概览改一处、桌面跟着变"，两页的节奏本来就不该绑死。
 */
.dk-grid { display: grid; grid-template-columns: repeat(12, minmax(0,1fr)); gap: 14px 18px; align-items: stretch; }
.dk {
  grid-column: span 4; min-width: 0;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 13px 15px 15px;
  display: flex; flex-direction: column; height: 100%;
}
.dk.span6 { grid-column: span 6; }
.dk.span12 { grid-column: span 12; }
.dk > .section-head { margin: 0 0 8px; }

/* 态势里的四块可视化：宽屏四列，≤1100 两列，≤760 单列 */
.dk-viz { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; margin-top: 12px; }
.dk-viz-c { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--bg); min-width: 0; }
.dk-viz-h { font-size: 11.5px; color: var(--muted); margin-bottom: 8px; }
/* 四列时每格只有 ~260px：环图缩到 88px，否则「环 + 两行文字」会挤成换行 */
#desktopOnlineBox .donut-svg { width: 88px; height: 88px; }
#desktopOnlineBox .kv-list { flex: 1 1 90px; }

/* 堆叠条：一条分两段（平台/代挂、已托管/未托管、启用/停用），图例在条上方 */
.dk-stack { margin-top: 9px; }
.dk-stack:first-child { margin-top: 0; }
.dk-stack-h { display: flex; justify-content: space-between; gap: 8px; font-size: 11px; color: var(--muted); margin-bottom: 3px; }
.dk-stack-h b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.dk-stack-bar { display: flex; height: 7px; border-radius: 4px; overflow: hidden; background: rgba(127,127,127,.16); }
.dk-stack-bar i { display: block; height: 100%; }
.dk-stack-bar i + i { border-left: 1px solid var(--card); }

/* 申请漏斗：每段一行，条宽按该段占最大值的比例 */
.dk-funnel-row { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--muted); margin-top: 6px; }
.dk-funnel-row:first-child { margin-top: 0; }
.dk-funnel-row .n { width: 26px; text-align: right; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.dk-funnel-row .t { width: 62px; }
.dk-funnel-row .b { flex: 1 1 auto; height: 7px; border-radius: 4px; background: rgba(127,127,127,.16); overflow: hidden; }
.dk-funnel-row .b i { display: block; height: 100%; border-radius: 4px; }

/* 版本覆盖：每版一行；低于最低版本的整行标红（"有几台会被 426 拦住"） */
.dk-ver-row { display: flex; align-items: center; gap: 8px; font-size: 11.5px; margin-top: 5px; }
.dk-ver-row .v { width: 56px; color: var(--muted); font-family: ui-monospace, Consolas, monospace; }
.dk-ver-row .b { flex: 1 1 auto; height: 7px; border-radius: 4px; background: rgba(127,127,127,.16); overflow: hidden; }
.dk-ver-row .b i { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
.dk-ver-row .n { width: 30px; text-align: right; color: var(--text); font-variant-numeric: tabular-nums; }
.dk-ver-row.below .v, .dk-ver-row.below .n { color: var(--err); }
.dk-ver-row.below .b i { background: var(--err); }
.dk-note { font-size: 11px; color: var(--muted); margin-top: 8px; line-height: 1.5; }
.dk-note.warn { color: var(--warn); }
.dk-empty { font-size: 11.5px; color: var(--muted); }

@media (max-width: 1100px) {
  .dk, .dk.span6 { grid-column: span 12; }
  .dk-viz { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 760px) { .dk-viz { grid-template-columns: 1fr; } }

/* 号池健康卡里的比例条（v0.40.100）：四张 KPI 只给「42 / 50」读不出离满员多远，
   加一条 5px 的条 + 百分比，把"差多少"变成一眼可见。 */
.ovmini { height: 5px; border-radius: 3px; background: rgba(127,127,127,.18); overflow: hidden; margin-top: 7px; }
.ovmini i { display: block; height: 100%; border-radius: 3px; }
/* 块内的 KPI 网格吃满块高：同行等高后，「号池仪表盘」这类块常剩下半截空白，
   让网格的行在块内均匀分布（space-evenly），KPI 卡本身仍按行等高。 */
.ovc > .kpis { flex: 1 1 auto; align-content: space-evenly; }
/* 内容比可用高度少时，空白上下均分（而不是全堆在底部） */
.ovc > .donut-row { align-items: center; }
.ovc > #ovSplit,
.ovc > #poolServed,
.ovc > #poolTopCredit,
.ovc > #ovChannels { display: flex; flex-direction: column; justify-content: center; }
/* ============ v0.40.96：消耗归属改数据卡 + 健康度走势改面积折线 ============ */

/* 消耗归属（.ovs-cards）：原来是"标签 + 一条 6px 细条"，只能表达占比 ——
   次数/扣费/积分无处可放，整块看着"数据很薄"。两张卡各自带占比大字、迷你条、
   三行明细：同样一块面积，可见数字从 4 个涨到 8 个，不必再靠悬停。 */
.ovs-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.ovs-card { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--bg); min-width: 0; }
.ovs-h { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ovs-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.ovs-sub { font-size: 10.5px; opacity: .8; }
.ovs-v { font-size: 21px; font-weight: 650; margin-top: 3px; font-variant-numeric: tabular-nums; line-height: 1.1; }
.ovs-v .u { font-size: 12px; font-weight: 400; color: var(--muted); margin-left: 2px; }
.ovs-bar { height: 6px; border-radius: 3px; background: rgba(127,127,127,.18); overflow: hidden; margin: 7px 0 8px; }
.ovs-bar i { display: block; height: 100%; border-radius: 3px; }
.ovs-kv { display: flex; justify-content: space-between; gap: 8px; font-size: 11.5px; color: var(--muted); margin-top: 3px; }
.ovs-kv b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.ovs-total { margin-top: 10px; font-size: 11.5px; color: var(--muted); text-align: center; }
@media (max-width: 1100px) { .ovs-cards { grid-template-columns: 1fr; } }

/* 健康度走势（.trend）：面积折线 + 纵轴刻度 + 峰值/谷值/最新标注。
   ⚠️ SVG 只画网格与 path（preserveAspectRatio="none" 横向铺满），**文字与圆点走 HTML
   绝对定位** —— 放进 SVG 里会被容器宽高比拉扁，字和点都会变形。 */
.trend { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.trend-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin: 0 0 6px; }
.trend-now { font-size: 21px; font-weight: 650; font-variant-numeric: tabular-nums; line-height: 1.1; }
.trend-now span { font-size: 11px; font-weight: 400; color: var(--muted); margin-left: 5px; }
.trend-facts { font-size: 11px; color: var(--muted); line-height: 1.5; }
/* 左侧留出 34px 给纵轴刻度：plot 用 margin 让位，刻度用负偏移回到这块留白里 */
.trend-plot { position: relative; flex: 1 1 auto; min-height: 130px; margin: 2px 0 2px 34px; }
.trend-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.trend-grid { stroke: var(--border); stroke-width: 1; stroke-dasharray: 3 4; vector-effect: non-scaling-stroke; }
.trend-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.trend-line2 { fill: none; stroke: var(--ok); stroke-width: 1.4; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; opacity: .85; }
.trend-area { fill: url(#trendGrad); stroke: none; }
.trend-yt { position: absolute; left: -34px; width: 30px; text-align: right; font-size: 10px; color: var(--muted); transform: translateY(-50%); }
.trend-dot { position: absolute; width: 7px; height: 7px; border-radius: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 0 1.5px var(--card); }
.trend-dot.hi { background: var(--accent); }
.trend-dot.lo { background: var(--err); }
.trend-dot.now { background: var(--accent); width: 9px; height: 9px; }
.trend-xt { display: flex; justify-content: space-between; font-size: 10px; color: var(--muted); margin: 4px 0 0 34px; }
.trend-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11px; color: var(--muted); margin-top: 6px; }
.trend-legend i { display: inline-block; width: 14px; height: 0; border-top: 2px solid var(--accent); margin-right: 5px; vertical-align: 2px; }
.trend-legend i.dash { border-top: 2px dashed var(--ok); }

/* 折叠区（冷却 / 被拒 / 版本 / 快照）各占一行，用上分隔线分组 */
.ov-fold + .ov-fold { border-top: 1px solid var(--border); margin-top: 10px; padding-top: 10px; }
.ov-fold > summary { font-size: 12.5px; margin: 0; }
.ov-fold[open] > summary { margin-bottom: 10px; }
section[data-pane="overview"] .mgrid { gap: 12px; }
section[data-pane="overview"] .mcell { min-width: 0; }
section[data-pane="overview"] .mcell > h2 { margin: 0 0 8px; }
section[data-pane="overview"] .section-head { margin: 0 0 8px; }
@media (max-width: 1100px) {
  .ovc, .ovc.span6 { grid-column: span 6; }
  .ovc.span8, .ovc.span12 { grid-column: span 12; }
}
@media (max-width: 760px) {
  .ovc, .ovc.span6, .ovc.span8, .ovc.span12 { grid-column: span 12; }
}

.ov-domains { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; margin: 0 0 12px; }
.od { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--card); min-width: 0; }
.od-h { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.od-h .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: none; }
.od.warn .od-h .dot { background: var(--warn); }
.od.bad .od-h .dot { background: var(--err); }
.od-v { font-size: 20px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }
.od.warn .od-v { color: var(--warn); }
.od.bad .od-v { color: var(--err); }
.od-d { font-size: 11.5px; color: var(--muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 1100px) { .ov-domains { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .ov-domains { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* 设置页排版收紧（v0.40.68）：原来 11 个半宽块两两配行，
   奇数个必然留半行空白，行高又由最高块决定 —— 整体看着"到处是空的"。
   这里统一整行 + 压缩纵向间距，每个设置块都占满宽度。 */
section[data-pane="settings"] .mgrid > .mcell { grid-column: 1 / -1; }
section[data-pane="settings"] .mgrid { gap: 10px; }
section[data-pane="settings"] .mcell > h2 { margin: 0 0 8px; }
section[data-pane="settings"] .mcard { margin-bottom: 12px; }

/* RateGate 配置块（v0.40.63）：该块已有 id=rateGateConfigCard，直接复用，不动 HTML。 */
#rateGateConfigCard > .row { display: grid; grid-template-columns: 150px minmax(0, 1fr);
  align-items: center; gap: 12px; }
#rateGateConfigCard > .row > label { margin: 0; }
@media (max-width: 720px) { #rateGateConfigCard > .row { grid-template-columns: 1fr; gap: 4px; } }

/* 风控卡首块（v0.40.61）：只对「反代选号 · 防封」应用两列。
   该块含开关与内嵌表格，先单块验证，稳定后再推广。 */
#risk-antiproxy > .row { display: grid; grid-template-columns: 150px minmax(0, 1fr);
  align-items: center; gap: 12px; }
#risk-antiproxy > .row > label { margin: 0; }
@media (max-width: 720px) { #risk-antiproxy > .row { grid-template-columns: 1fr; gap: 4px; } }

#set-sec-billing .mcell > .row { display: grid; grid-template-columns: 220px minmax(0, 1fr);
  align-items: center; gap: 12px; }
#set-sec-billing .mcell > .row > label { margin: 0; }
@media (max-width: 720px) {
  #set-sec-billing .mcell > .row { grid-template-columns: 1fr; gap: 4px; }
}

/* 概览状态色（v0.40.27）：只在阈值越界时上色，正常保持主色 —— 满屏红黄反而看不出重点。 */
.kpi-card .v.ov-bad { color: var(--err); }
.kpi-card .v.ov-warn { color: var(--warn); }
/* v0.40（用户 #6）：摘要卡等高 + 等宽。
 * 实测（1440/1180 两档）本来就等宽（274 / 209px），但那是"内容不撑"的情况下；
 * 一旦某个数值特别长（大额消费、超长 token 数），`.kpi-card` 会按内容撑宽，
 * 一排卡就参差不齐 —— 这是 #6 描述的"宽度不一致"最可能的成因。
 * 加 min-width:0 让 1fr 真正生效，行高统一到 1fr 让卡顶对齐。 */
.kpis > .kpi-card { min-width: 0; }
.kpis { grid-auto-rows: 1fr; }
.kpi-card {
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 13px 15px; min-width: 0;
}
.kpi-card .k { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.kpi-card .v { font-size: 22px; font-weight: 650; font-family: ui-monospace, Consolas, monospace; margin-top: 3px; line-height: 1.15; }
.kpi-card .v .u { font-size: 12px; font-weight: 400; color: var(--muted); margin-left: 3px; font-family: inherit; }
.kpi-card .d { font-size: 11.5px; color: var(--muted); margin-top: 3px; }
/* 窄屏下 KPI 数字缩一号（v0.31.185，审查 UI-2）。
   来由：`repeat(auto-fit, minmax(148px,1fr))` 在 820px 视口下每张卡只剩约 117px 内容宽，
   而「近 60 分钟扣费」写的是 `812,345.679 点`（22px 等宽要 133px）—— 数字直接溢出卡片。
   三种做法里只能选这一种：
     · 省略号 → 用户看不到末几位，是**错的数据**；
     · 折行   → 数字断成两行，更难读；
     · 缩字号 → 仍是完整数字，只是小一号（表格断点同样是 1100px，口径一致）。 */
@media (max-width: 1100px) {
  .kpi-card .v { font-size: 18px; }
  .kpi-card .v .u { font-size: 11px; }
}
.kpi-card.hl { border-color: transparent; background: linear-gradient(135deg, var(--tint), transparent); }
.kpi-card.ok { border-color: transparent; background: var(--ok-tint); }
.kpi-card.warn { border-color: transparent; background: var(--warn-tint); }
.kpi-card.err { border-color: transparent; background: var(--err-tint); }

/* ---------- 进度条 ---------- */
.progress { height: 7px; background: var(--bg); border: 1px solid var(--border); border-radius: 999px; overflow: hidden; }
.progress > i { display: block; height: 100%; background: var(--accent); transition: width .3s; }
.progress.warn > i { background: var(--warn); }
.progress.err > i { background: var(--err); }
.progress + .pg-label { font-size: 11px; color: var(--muted); margin-top: 4px; display: flex; justify-content: space-between; gap: 8px; }

/* ---------- 开关 ---------- */
.switch { position: relative; display: inline-block; width: 38px; height: 21px; flex: 0 0 auto; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch i { position: absolute; inset: 0; background: var(--border); border-radius: 999px; transition: background .15s; }
.switch i::after { content: ''; position: absolute; top: 2px; left: 2px; width: 17px; height: 17px; border-radius: 50%; background: #fff; transition: transform .15s; box-shadow: 0 1px 2px rgba(0,0,0,.25); }
.switch input:checked + i { background: var(--accent); }
.switch input:checked + i::after { transform: translateX(17px); }
.switch input:disabled + i { opacity: .5; }

/* ---------- 实体卡片（账号 / 用户 / 兑换码 / 台账共用外壳） ---------- */
.cards {
  display: grid;
  /* auto-fit 而不是 auto-fill（v0.40.10）：一格里只有 1~2 张卡时，
     auto-fill 仍会按最小宽度铺满轨道，空轨道照样占位 —— 表现就是
     「卡片只有 285px，右边空出大半屏」。auto-fit 让空轨道塌缩、卡片撑满。 */
  grid-template-columns: repeat(auto-fit, minmax(min(285px, 100%), 1fr));
  gap: 14px;
}

/* v0.40（用户 #2/#16）：卡片与区块间距自适应 —— 窄屏更紧凑，宽屏不飘。
   ⚠️ 必须写在上面那条基础规则**之后**：同一选择器、同优先级，源顺序在后者胜出。
   2026-11-01 之前这段被放在基础规则前（第 269 行 vs 392 行），整段被覆盖 → 断点从未生效，
   表现为"901–1280 仍是 3~4 列窄卡、右侧留大片空白"。 */
.cards { gap: 12px; }
@media (max-width: 900px) { .cards { grid-template-columns: 1fr; } }
@media (min-width: 901px) and (max-width: 1280px) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* 管理端「用户」页的卡片**固定宽度**（v0.40.24，用户要求）：这一页是台账式浏览，
   卡片被拉宽只会让每张卡里塞更多空白，反而更难扫。auto-fit 的"撑满"留在这里不适用。 */
/* v0.40.32：改回自适应 —— 原「固定 285px」在宽屏会把最后一列空出来，
   用户反馈「卡片没有自适应」。用 auto-fit + min(285px,100%)：宽屏撑满、
   窄屏塌成单列，两端口径一致。 */
#userCards { grid-template-columns: repeat(auto-fit, minmax(min(285px, 100%), 1fr)); }
.ecard {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
}
.ecard > header { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--border); background: var(--tint); }
.ecard > .body { padding: 12px 14px; font-size: 12.5px; color: var(--muted); flex: 1; }
.ecard > footer { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 10px 14px; border-top: 1px solid var(--border); margin-top: auto; }
/* 用户卡底部 7 个动作（详情/加余额/限额/封禁/记录/组/角色）在 285~340px 的卡里放不进一行。
   flex 换行的结果是「6 + 1」—— 最后一个「角色」孤零零挂在第二行，看着像多出来的按钮。
   改成 4 列网格：4 + 3，每格等宽，读起来是一块动作区而不是"排不下"（v0.39.83）。 */
#userCards footer { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
#userCards footer button { padding: 5px 6px; font-size: 12px; white-space: nowrap; }
.ecard > footer button { padding: 5px 10px; font-size: 12px; }
.ecard > footer select { padding: 5px 8px; font-size: 12px; min-width: 0; flex: 1; }
.ecard.dim { opacity: .62; }
.ecard .avatar {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 16px; color: var(--on-accent); background: var(--accent);
}
.ecard .avatar.a1 { background: #4a6cf7; } .ecard .avatar.a2 { background: #10b981; }
.ecard .avatar.a3 { background: #8b5cf6; } .ecard .avatar.a4 { background: #f59e0b; }
.ecard .avatar.a5 { background: #ec4899; } .ecard .avatar.a6 { background: #14b8a6; }
.ecard .who { min-width: 0; flex: 1; }
.ecard .name { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ecard .ident { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ecard .chips { display: flex; flex-wrap: wrap; gap: 5px; padding: 10px 14px 0; }
.ecard .line { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; }
.ecard .line b { color: var(--text); font-family: ui-monospace, Consolas, monospace; }
.ecard .mini-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 0 14px 12px; }
.ecard .mini-kpis .mk { min-width: 0; }
.ecard .mini-kpis .mk .k { font-size: 11px; color: var(--muted); }
.ecard .mini-kpis .mk .v { font-size: 14px; font-weight: 600; font-family: ui-monospace, Consolas, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* .mk-d 副行（v0.17.7）：承载第二口径（点下方的「≈ ¥」）。两处网格都要有，
   否则管理端卡片（.mini-kpis）的副行会继承主值字重，两个口径视觉上分不开。 */
.ecard .mini-kpis .mk .mk-d { font-size: 11px; color: var(--muted); font-family: inherit; font-weight: 400; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 分组筛选条 */
.chipbar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 14px; }
.chip {
  padding: 4px 12px; border-radius: 999px; cursor: pointer; font-size: 12px;
  border: 1px solid var(--border); background: transparent; color: var(--text);
}
.chip:hover { background: var(--tint); }
.chip.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

/* ---------- 设置卡 ---------- */
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; }
.section-head h2 { margin: 0; }
.setting-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(258px, 100%), 1fr)); gap: 12px; }
.setting-card {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 13px; background: var(--bg);
  display: flex; flex-direction: column; gap: 7px;
}
.setting-card > label { font-size: 12.5px; color: var(--text); font-weight: 500; }
.setting-card input, .setting-card select { width: 100%; min-width: 0; }
.setting-card .hint { font-size: 11px; color: var(--muted); line-height: 1.5; }
.setting-card .inline { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

/* v0.40（用户 #12）：公告页的规则说明折叠 + 表单控件风格统一。
 * 原来 datetime-local 用的是浏览器默认高度（比文本框高 2~4px）、圆角也不一致，
 * 一排排开时高低不齐，看着就不像同一套控件。 */
.note-fold { margin: 0 0 12px; }
.note-fold > summary {
  cursor: pointer; font-size: 12.5px; color: var(--muted);
  padding: 6px 0; user-select: none;
}
.note-fold > summary:hover { color: var(--text); }
input[type="datetime-local"], input[type="date"], input[type="time"] { min-height: 33px; }
.setting-card input[type="checkbox"] { width: auto; min-width: 0; }
/* v0.40：`.setting-card input{width:100%}` 会让**放在 .row 里**的输入框也撑满整行，
 * 于是"开始 / 结束"两个日期被压成上下三行（公告页实测：比改之前更占屏）。
 * 行内的输入框应当按 flex 分配，按钮不参与拉伸。 */
.setting-card .row input:not([type="checkbox"]),
.setting-card .row select { width: auto; flex: 1 1 200px; min-width: 0; }
.setting-card .row button { flex: 0 0 auto; }

/* ---------- 任务卡 ---------- */
.task-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(238px, 100%), 1fr)); gap: 12px; }
.task-card {
  border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 8px; background: var(--bg);
}
.task-card .tname { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-weight: 600; font-size: 13px; }
.task-card .hint { font-size: 11px; color: var(--muted); }
.task-card.off { opacity: .58; }
.action-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }
.action-card {
  border: 1px solid var(--border); border-radius: 12px; padding: 12px 13px;
  cursor: pointer; background: var(--bg); text-align: left; font-family: inherit;
  color: var(--text); display: flex; flex-direction: column; gap: 3px;
}
.action-card:hover { border-color: var(--accent); background: var(--tint); }
.action-card b { font-size: 13px; }
.action-card span { font-size: 11px; color: var(--muted); }

/* ---------- 台账 / 运行卡 ---------- */
.run-card { border: 1px solid var(--border); border-radius: 12px; padding: 11px 14px; display: flex; flex-direction: column; gap: 8px; background: var(--card); }
.run-card .rhead { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.run-card .rhead b { font-size: 13px; }
.pills { display: flex; gap: 6px; flex-wrap: wrap; }
.pill { font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); }
.pill b { color: var(--text); font-family: ui-monospace, Consolas, monospace; }
.pill.ok { color: var(--ok); background: var(--ok-tint); border-color: transparent; }
.pill.bad { color: var(--err); background: var(--err-tint); border-color: transparent; }

/* ---------- 实时仪表盘 ---------- */
.live-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 16px;
  /* v0.40.13：同 .mgrid 一并回退（stretch 会造出大片底部空白）。 */
  align-items: start; }
/* 概览页的「配置与状态快照」四张卡被折进 details 后，原来的两列网格会剩一张卡、右边空半边
   （v0.39.85）。只剩一个孩子时退回单列 —— 空白比"看起来少了一半"更容易被读成坏页面。 */
.live-grid.single { grid-template-columns: minmax(0, 1fr); }
/* 两张卡都被折走时，那个 .live-grid 会剩个空壳：margin-top:16px + 栅格 gap 白占一条。
   用 :empty 收掉 —— 比在 JS 里逐个 hide 更可靠（JS 忘了就等于留个洞）。 */
.live-grid:empty { display: none; }
/* details 里的卡片是纵向堆叠的，原本靠 .live-grid 的 gap 拉开的间距要补回来。 */
details.ov-snap > .card + .card { margin-top: 12px; }
@media (max-width: 1020px) { .live-grid { grid-template-columns: minmax(0, 1fr); } }
.live-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); margin-right: 6px; vertical-align: middle; }
.live-dot.on { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-tint); animation: pulse 1.8s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
/* .pulse 单独可用的呼吸效果（「执行中」标签与运行中的卡片）：上一条是给 .live-dot
   复合选择器用的，这里让 .pulse 本身也能直接挂到任意标签上。 */
.pulse { animation: pulse 1.8s infinite; }
/* 运行中的任务：左侧加一条强调色边，扫一眼就能从台账列表里挑出「正在跑的那个」。 */
.run-card.running { border-left: 2px solid var(--warn); }
.action-card.running { opacity: .75; cursor: default; border-color: var(--warn); }

.bars { display: flex; align-items: flex-end; gap: 2px; height: 96px; margin: 8px 0 4px; justify-content: space-between; }
/* max-width（v0.39.73）：满一小时是 60 根，每根约 1/60 宽；但接口只回少数分钟时
   （新账号刚开始用 / 夹具），flex:1 会让每根撑成"大色块"，看着不像"逐分钟"。
   配上容器的 space-between，少量柱会沿时间轴散开，仍然读得出"哪几分钟有事"。 */
.bars .bar { position: relative; overflow: hidden; flex: 1 1 0; max-width: 16px; min-width: 2px; background: var(--accent); border-radius: 2px 2px 0 0; opacity: .85; align-self: flex-end; transition: height .3s; }
/* 柱内失败段（v0.30.0）：高度 = 失败数 / 该分钟总数，贴底显示。
   取代了此前"整根柱子染红"的表达 —— 那种表达看不出失败占比（1 失败 + 9 成功
   与 10 成功一样高），而失败段能让"这一分钟基本全挂了"一眼可见。 */
.bars .bar > i { position: absolute; left: 0; right: 0; bottom: 0; background: var(--err); }
.bars .bar.empty { background: var(--border); }
.bars-axis { display: flex; justify-content: space-between; font-size: 10.5px; color: var(--muted); }
.bars-legend { display: flex; gap: 14px; font-size: 11.5px; color: var(--muted); margin-top: 6px; flex-wrap: wrap; }
.bars-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }

.feed { display: flex; flex-direction: column; gap: 6px; max-height: 340px; overflow-y: auto; }
.feed .ev { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 9px; border: 1px solid var(--border); border-radius: 9px; }
.feed .ev .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: 0 0 auto; }
.feed .ev.bad .dot { background: var(--err); }
.feed .ev .t { color: var(--muted); font-family: ui-monospace, Consolas, monospace; flex: 0 0 auto; font-size: 11px; }
.feed .ev .who {
  font-weight: 600;
  /* v0.40（有数据实测 · 390 档）：模型名没有省略号规则 → 长名（claude-3-5-sonnet-…）
     把一行撑成三行，还把右侧金额挤成 `2. –`、状态码直接顶出去。
     单行省略 + 允许收缩（min-width:0 是 flex 子项能省略的前提）。 */
  min-width: 0; flex: 1 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed .ev .msg { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* 状态码 / tokens 是**判断"这笔成没成"的唯一依据**，不能被模型名挤没：
     实测 390 档它只拿到 10px、只剩一个"…"。
     但也不能**完全不收缩** —— 成功行的 tokens 文案很长，`flex:0 0 auto` 会把整行顶出
     25px 横向溢出（layoutprobe 在 390 档抓到）。折中：可收缩 + 44px 下限（够显示 "200"），
     超出的 tokens 文案自己省略。 */
  flex: 0 1 auto; min-width: 44px; }
.feed .ev .amt { margin-left: auto; font-family: ui-monospace, Consolas, monospace; flex: 0 0 auto; }
.feed-empty { color: var(--muted); font-size: 12px; padding: 10px 2px; }

/* ---------- 表单/网格辅助 ---------- */
.fields { display: grid; gap: 10px; }
.field { display: grid; grid-template-columns: 190px 1fr; align-items: center; gap: 12px; }
.field > label { font-size: 13px; color: var(--text); }
.field .hint { font-size: 11px; color: var(--muted); }
@media (max-width: 560px) { .field { grid-template-columns: 1fr; gap: 4px; } }
.hourinput { min-width: 0; width: 100%; max-width: 220px; }

/* ---------- 登录页（窄容器 + 顶部 tabs） ---------- */
.narrow { max-width: 400px; margin: 8vh auto; padding: 0 16px; }
.tabs { display: flex; gap: 4px; margin-bottom: 16px; flex-wrap: wrap; }
.tab {
  padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 13px;
  border: 1px solid var(--border); background: transparent; color: var(--text);
}
.tab.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.pane { display: none; }
.pane.active { display: block; }

/* ---------- Toast（v0.26.6 起改为右上角堆叠卡片，样式见文件末尾 UI 套件段） ---------- */

/* ---------- 账号任务完成情况（今日签到 / 猫猫旅行） ---------- */
/* 卡片上直接显示「是否完成」，与开关区相邻，避免只看开关不知结果。 */
.tstatus { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 14px 10px; }
.tstatus .tag { font-size: 11.5px; }

/* ---------- 标签行（冷却 / 停用账号明细等「无数值可比」的清单） ---------- */
/* 用标签流而不是横向条：停用没有「剩多久」这种可比较数值，硬画成条只会得到
   一排全为 0 的无意义图形。 */
.chipline { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 4px; }

/* 检测结果卡的一键动作行（启用 / 复活，v0.23.4）：与明细之间留一点间距，
   免得按钮贴着「已处置」那行显得像是它的一部分。 */
.action-row { margin-top: 10px; }

/* ---------- 积分卡片（大总额 + N 个积分包 + 逐包进度条/到期） ---------- */
.credit { margin: 10px 0 2px; }
.credit-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.credit-total { display: flex; align-items: baseline; gap: 4px; }
.credit-total b { font-size: 24px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.credit-total .u { font-size: 12px; color: var(--muted); }
.credit-packs { font-size: 12px; color: var(--muted); }
.credit-upd { margin-left: auto; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.credit-sub { font-size: 11px; font-weight: 600; color: var(--muted); margin: 10px 0 6px; }
.credit-err { display: flex; align-items: center; gap: 6px; color: var(--err); font-size: 12.5px; padding: 6px 0; }
/* 点开「查积分明细 / 刷新明细」是文字链接样式，但 12px 字只有 16px 高的点击区 ——
   手机上很难点中（v0.31.190 布局探针量出来的）。加 4px 内边距把目标拉到 24px，
   看起来仍然是文字链接（无底色/无边框）。 */
.credit-more { margin-top: 4px; background: transparent; border: 0; color: var(--accent-ink); font-size: 12px; cursor: pointer; padding: 4px 0; min-height: 24px; }
.credit-more:hover { text-decoration: underline; }

.pack { margin-bottom: 8px; }
.pack:last-child { margin-bottom: 0; }
.pack-top { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; font-size: 12px; }
.pack-amt { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 1px 7px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pack-name { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pack-exp { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pack-exp.warn { color: var(--warn); }
.pack-exp.err { color: var(--err); }
.pack-bar { height: 5px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border); overflow: hidden; margin-top: 5px; }
.pack-bar i { display: block; height: 100%; background: var(--accent); }
.pack-bar.warn i { background: var(--warn); }
.pack-bar.err i { background: var(--err); }

/* ---------- 弹窗（全部积分包） ---------- */
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,.45); display: flex; align-items: center; justify-content: center; z-index: 120; padding: 20px; }
.modal { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); width: 100%; max-width: 440px; max-height: 76vh; overflow: auto; }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 6px; }
.modal-head b { font-size: 15px; }
.modal-x { background: transparent; border: 0; color: var(--muted); font-size: 20px; line-height: 1; cursor: pointer; padding: 0 4px; }
.modal-x:hover { color: var(--text); }
.modal-sub { font-size: 12px; color: var(--muted); padding: 0 16px 8px; }
.modal-body { padding: 4px 16px 16px; }

/* ---------- 代挂（hosting）标签与审批 ---------- */
.qbadge { font-size: 10.5px; padding: 1px 7px; border-radius: 999px; background: var(--accent); color: var(--on-accent); margin-left: 6px; }
.pending-list { display: flex; flex-direction: column; gap: 8px; }
.pending-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }
.pending-row .grow { flex: 1 1 200px; min-width: 0; }
.pending-row .who { font-size: 13px; }
.pending-row .meta { font-size: 11.5px; color: var(--muted); }
.pending-row .acts { display: flex; gap: 6px; margin-left: auto; }

/* ---------- 响应式：侧边栏折叠为顶部横排 ---------- */
@media (max-width: 860px) {
  .app { flex-direction: column; }
  .sidebar { position: static; height: auto; width: auto; flex: 0 0 auto; border-right: 0; border-bottom: 1px solid var(--border); }
  .brand { padding: 14px 14px 8px; }
  .nav { flex-direction: row; overflow-x: auto; padding: 4px 10px 10px; gap: 4px; }
  .nav-group { display: none; }
  .nav-item { width: auto; white-space: nowrap; }
  .nav-item .badge { display: none; }
  .sidebar-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
  .sidebar-foot .whoami { margin: 0; }
  .main { padding: 16px 14px 48px; }
}

/* ===================================================================
   v0.11.0 · 对齐参考项目（WorkBuddy-Switch2api / shdcn）的组件基元
   图标按钮（刷新类带旋转 loading）、药丸开关、分段控件、StatCard、
   配置行、分组框、零依赖 SVG 图表容器
   =================================================================== */

/* ---------- 图标按钮 ---------- */
.icon-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent; color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 11px; font-size: 12.5px; cursor: pointer; font-family: inherit;
  transition: background .15s, border-color .15s, color .15s;
}
.icon-btn:hover { background: var(--tint); border-color: var(--accent); }
.icon-btn:disabled { opacity: .55; cursor: progress; }
.icon-btn .ic { display: block; flex: 0 0 auto; }
.icon-btn.spin .ic { animation: wb-spin .9s linear infinite; }
.icon-btn.danger { color: var(--err); }
.icon-btn.danger:hover { background: var(--err-tint); border-color: var(--err); }
.icon-btn.solid { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.icon-btn.solid:hover { filter: brightness(1.06); background: var(--accent); }
.icon-btn.copied { color: var(--ok); border-color: var(--ok); background: var(--ok-tint); }
.icon-btn .cb-ic { display: inline-flex; }
@keyframes wb-spin { to { transform: rotate(360deg); } }

/* ---------- 刷新占位（挂载 iconBtn） ---------- */
.slot-refresh { display: inline-flex; align-items: center; }

/* ---------- 三段式统计行（概览 / 报表用） ---------- */
.mini-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
.mini-grid .mk { min-width: 0; border: 1px solid var(--border); border-radius: 10px; padding: 9px 11px; background: var(--bg); }
.mini-grid .mk .k { font-size: 11px; color: var(--muted); }
.mini-grid .mk .v { font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* .mk-d 副行（v0.17.7）：承载第二口径（如点下方的「≈ ¥」），小一号、弱化、不换行省略。 */
.mini-grid .mk .mk-d { font-size: 11px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mini-grid .mk.ok { background: var(--ok-tint); border-color: transparent; }
.mini-grid .mk.warn { background: var(--warn-tint); border-color: transparent; }
.mini-grid .mk.err { background: var(--err-tint); border-color: transparent; }

/* ---------- 账单卡（三类消耗） ---------- */
.bk-card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); padding: 14px 15px; display: flex; flex-direction: column; gap: 10px; box-shadow: var(--shadow); }
.bk-card .bk-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.bk-card .bk-title { font-size: 14px; font-weight: 650; }
.bk-card .bk-sub { font-size: 11.5px; color: var(--muted); line-height: 1.55; }
.bk-card .bk-amt { margin-left: auto; font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.bk-card .bk-amt .u { font-size: 11px; color: var(--muted); font-weight: 400; margin-left: 3px; }
.bk-bars { display: grid; gap: 7px; }
.bk-bar { display: grid; grid-template-columns: 74px 1fr 76px; align-items: center; gap: 8px; font-size: 11.5px; }
/* v0.40（逐页实测）：标签列 62px 装不下「号池积分消耗」六个汉字，会折成两行，
   让同一张卡里三行的数值列基线错开。加宽到 74px + 不折行（实测 62px 时
   scrollWidth/行数都对不上，"折行"才是真相，不是被截断）。 */
.bk-bar .bl { color: var(--muted); white-space: nowrap; }
.bk-bar .bv { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.bk-track { height: 7px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border); overflow: hidden; }
.bk-track > i { display: block; height: 100%; border-radius: 999px; background: var(--accent); transition: width .3s; }

/* ---------- 提现状态 ---------- */
.wd-status { font-size: 11px; padding: 2px 8px; border-radius: 999px; }
.wd-status.pending { background: var(--warn-tint); color: var(--warn); }
.wd-status.approved { background: var(--ok-tint); color: var(--ok); }
.wd-status.rejected { background: var(--err-tint); color: var(--err); }

/* ---------- 药丸开关（行内） ---------- */
/* 通道卡头部（v0.31.141）：两行结构 —— 第一行「名称 + 状态标签 + 右侧操作」，
   第二行「base_url · key · 优先级」等宽等距。
   修的是"列表显示不齐整"这个现场：原来 9 个异构元素（标题/tag/裸 base_url/key/
   优先级/开关/按钮）挤在同一个 flex 行里换行，每张卡的换行位置都不一样，
   眼睛无法按列扫读；操作按钮还会随名称长度左右漂。 */
.ch-head { display: flex; flex-direction: column; gap: 6px; }
.ch-head-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 标识（logo / 通道名 / 状态 / 影响面）必须在开关的**左边**（v0.39.83）。
   这层容器是必需的：`.ch-ops{margin-left:auto}` 只对"它之前的兄弟"生效，
   原来把 ops 当第一个子节点，后面 append 的名字就被顶到了开关右边。 */
.ch-head-left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
/* 供应商图标（v0.39.16）。放在通道名前面：扫列表时先靠图形分辨"这是哪家"，
   再读文字确认。没有图标时显示首字母方块 —— 空白位置会让人以为"图没加载出来"。 */
.ch-logo {
  width: 22px; height: 22px; border-radius: 5px; object-fit: contain;
  background: var(--bg-2); border: 1px solid var(--border); flex: none;
}
.ch-logo-ph {
  width: 22px; height: 22px; border-radius: 5px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-2); border: 1px solid var(--border);
  font-size: 12px; font-weight: 600; color: var(--muted); cursor: pointer;
}
.ch-logo-ph:hover { border-color: var(--accent); color: var(--accent); }

/* 模型图标（v0.39.19）。与 .ch-logo 分开而不是复用：那个是**通道卡**里的 22px
   方形（可点换图），这个是**模型名旁边**的 16px 小标（只显示、不可点），
   混用会让"点它一下会打开文件选择框"这个约定失效。 */
.mi-wrap { display: inline-flex; align-items: center; flex: none; vertical-align: -3px; }
.mi-name { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.mi-name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mi {
  width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex: none;
  background: var(--bg-2); border: 1px solid var(--border);
}
.mi-ph {
  width: 16px; height: 16px; border-radius: 4px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  /* 品牌色由 JS 以**淡底 + 同色边框**给出（VENDOR_COLORS），文字一律用 --text。
     不用"品牌色底 + 白字"：多数厂商色（#10a37f / #ff7000 / #4285f4）压白字只有
     2.5~3.6:1，够不上 AA —— 而这块里的字是要被读的，不是装饰。 */
  background: var(--bg-2); color: var(--text); border: 1px solid var(--border);
  font-size: 10px; font-weight: 700; line-height: 1; letter-spacing: 0;
}
.ch-ops { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.ch-meta {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-family: ui-monospace, Consolas, monospace; font-size: 11.5px; color: var(--muted);
}
.ch-meta .u { max-width: 44ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.row-switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.row-switch .switch { cursor: pointer; }

/* ---------- 用户面板维护页（v0.39.23） ----------
   整页替换，不是遮罩：维护期间页面上的按钮必须**真的不存在**，
   否则 Tab 键与屏幕阅读器都能穿过去。 */
.maint-page {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; margin: 0; padding: 24px 0; background: var(--bg);
}
.maint-box {
  width: calc(100% - 40px); max-width: 520px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 28px 26px;
}
.maint-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.maint-head h1 { font-size: 18px; margin: 0; }
/* 一个静止的圆点，不用动画：维护页上任何"还在动"的东西都会被读成"正在恢复"。 */
.maint-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: var(--warn); box-shadow: 0 0 0 3px var(--warn-tint);
}
.maint-box .note { font-size: 13.5px; line-height: 1.75; white-space: pre-wrap; }
.maint-box .meta { margin-top: 12px; font-size: 12px; color: var(--muted); line-height: 1.7; }
.maint-box .acts { margin-top: 20px; display: flex; gap: 10px; flex-wrap: wrap; }

/* ---------- 分段控件 ---------- */
.segmented { display: inline-flex; flex-wrap: wrap; max-width: 100%; gap: 3px; padding: 3px; background: var(--bg); border: 1px solid var(--border); border-radius: 10px; }
.segmented .seg {
  background: transparent; color: var(--muted); border: 0; border-radius: 7px;
  padding: 5px 12px; font-size: 12.5px; cursor: pointer; font-family: inherit;
}
.segmented .seg:hover { color: var(--text); }
.segmented .seg.active { background: var(--card); color: var(--text); box-shadow: var(--shadow); font-weight: 600; }

/* ---------- StatCard ---------- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.stat-card { border: 1px solid var(--border); border-radius: 12px; background: var(--card); padding: 13px 15px; min-width: 0; }
.stat-card .k { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.stat-card .v { font-size: 21px; font-weight: 650; font-variant-numeric: tabular-nums; margin-top: 4px; line-height: 1.15; }
.stat-card .v .u { font-size: 12px; font-weight: 400; color: var(--muted); margin-left: 3px; }
.stat-card .hint { font-size: 11px; color: var(--muted); margin-top: 3px; }
.stat-card.accent { background: linear-gradient(135deg, var(--tint), transparent); border-color: transparent; }
.stat-card.ok { background: var(--ok-tint); border-color: transparent; }
.stat-card.warn { background: var(--warn-tint); border-color: transparent; }
.stat-card.err { background: var(--err-tint); border-color: transparent; }

/* ---------- 配置行（左文案 / 右控件） ---------- */
.cfg-list { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.cfg-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 11px 14px; border-bottom: 1px solid var(--border); }
.cfg-row:last-child { border-bottom: 0; }
.cfg-row .cfg-txt { min-width: 0; flex: 1; }
.cfg-row .cfg-txt b { font-size: 13px; font-weight: 600; display: block; }
.cfg-row .cfg-txt .hint { font-size: 11.5px; color: var(--muted); margin-top: 2px; line-height: 1.5; }
.cfg-row .cfg-ctl { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }

/* ---------- 分组框（账号分组 + 逐行开关） ---------- */
.group-box { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 10px; }
.group-box > .gb-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 12px; background: var(--bg); border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--muted); font-weight: 500;
}
.group-box > .gb-head .cnt { font-size: 11.5px; color: var(--muted); opacity: .8; }
.group-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--border); }
.group-row:last-child { border-bottom: 0; }
.group-row .gr-txt { min-width: 0; flex: 1; }
.group-row .gr-name { font-size: 13px; display: flex; align-items: center; gap: 6px; }
.group-row .gr-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }

/* ---------- 零依赖 SVG 图表 ---------- */
.chart-svg { display: block; width: 100%; height: 96px; }
.donut-svg { display: block; width: 128px; height: 128px; flex: 0 0 auto; }
/* 环形图行：环形 + 右侧分项图例左右并排，窄屏自动换行（避免挤压把环形压变形）。
   .kv-list / .kv-row 的通用样式在下方已定义，这里只补「作为环形图右栏」时的伸缩。 */
.donut-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.donut-row > .donut-svg { flex: 0 0 auto; }
.donut-row > .kv-list { flex: 1 1 150px; min-width: 0; }
.hbars { display: flex; flex-direction: column; gap: 10px; }
.hbar-row { display: flex; flex-direction: column; gap: 4px; }
.hbar-top { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
.hbar-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.hbar-val { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.hbar-track { height: 7px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border); overflow: hidden; }
.hbar-track > i { display: block; height: 100%; background: var(--accent); border-radius: 999px; transition: width .3s; }
.chart-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11.5px; color: var(--muted); margin-top: 8px; }
.chart-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }

/* ---------- 键值清单 ---------- */
.kv-list { display: flex; flex-direction: column; gap: 6px; }
/* 用户详情弹窗的小节（v0.21.0）：标题 + 键值列表，节与节之间留白。 */
.detail-sec { margin: 0 0 14px; }
.detail-sec > h4 { margin: 0 0 6px; font-size: 12px; color: var(--muted); font-weight: 600;
  letter-spacing: .02em; padding-bottom: 4px; border-bottom: 1px solid var(--line, rgba(128,128,128,.18)); }
.kv-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12.5px; }
.kv-row .k { color: var(--muted); }
.kv-row .v { font-variant-numeric: tabular-nums; font-weight: 600; }

/* ---------- 键盘可达：跳过导航（v0.31.182，审查 UI-6） ----------
   为什么用"跳转链接"而不是只靠 Tab 顺序：后台左侧导航有几十个入口，
   键盘用户每次进页面都要按几十次 Tab 才能到正文。这是最便宜也最有效的一条。
   视觉上默认移出屏幕，**获得焦点时才出现**——不能给它 display:none（那就不可聚焦了）。 */
.skip-link {
  position: fixed; left: 10px; top: -48px; z-index: 9999;
  padding: 8px 14px; border-radius: 10px;
  background: var(--accent); color: var(--on-accent); font-size: 13px; font-weight: 600;
  text-decoration: none; transition: top .15s;
}
.skip-link:focus { top: 10px; }

/* ---------- 空态 / 提示条 ---------- */
.hintbar { border: 1px dashed var(--border); border-radius: 12px; padding: 16px; text-align: center; color: var(--muted); font-size: 12.5px; }
/* 统一空/错误态（v0.31.183，审查 UI-1）：hintbar 是容器，下面两种变体说明"为什么空"。
   错误态用实线 + 左侧色条：与"只是没有数据"必须一眼可分，否则"取不到"会被读成"没有"。 */
.hintbar.err { border-style: solid; border-left: 4px solid var(--err); background: var(--err-tint); color: var(--text); text-align: left; }
.hintbar .hint { margin-top: 6px; font-size: 12px; color: var(--muted); }
.hintbar button { margin-top: 10px; }

/* alertbar 顶部提醒条（v0.31.139）：管理后台与用户控制台共用同一套观感。
   与 .hintbar 的区别要守住：hintbar 是"说明性提示"（虚线、居中、灰字），
   alertbar 是"需要你处理的事"（实线、左侧色条、标题 + 动作）。
   两者混用会让"必须处理"和"看看就好"长得一样 —— 那就没有提醒的意义了。 */
.alertbar {
  display: flex; gap: 12px; align-items: flex-start;
  border: 1px solid var(--border); border-left-width: 4px;
  border-radius: 12px; padding: 10px 14px; font-size: 12.5px;
  background: var(--card);
}
.alertbar.warn { border-left-color: var(--warn); background: var(--warn-tint); }
.alertbar.err { border-left-color: var(--err); background: var(--err-tint); }
.alertbar .ab-icon { font-size: 14px; line-height: 1.4; }
.alertbar .ab-body { flex: 1; min-width: 0; }
.alertbar .ab-title { font-weight: 600; margin-bottom: 2px; }
.alertbar .ab-sub { color: var(--muted); line-height: 1.55; }
.alertbar .ab-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.alertbar button { padding: 4px 10px; font-size: 12px; }
.notice { display: flex; align-items: flex-start; gap: 8px; border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; font-size: 12.5px; line-height: 1.6; background: var(--bg); }
.notice.ok { border-color: transparent; background: var(--ok-tint); color: var(--ok); }
.notice.err { border-color: transparent; background: var(--err-tint); color: var(--err); }
.notice.warn { border-color: transparent; background: var(--warn-tint); color: var(--warn); }

/* ---------- 扫码授权弹窗（v0.12.0，需求 4/5） ---------- */
.oauth-auth { margin-top: 12px; }
.oauth-auth .notice { display: block; }
.oauth-auth .notice .row { margin-bottom: 6px; }
.oauth-open { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--accent-ink); text-decoration: none; }
.oauth-open:hover { text-decoration: underline; }
.oauth-url { margin: 4px 0 2px; font-size: 11.5px; word-break: break-all; line-height: 1.5; }
.oauth-url a { color: var(--muted); }
.oauth-url a:hover { color: var(--accent-ink); }
.oauth-btns { gap: 10px; }
.oauth-btns button { display: inline-flex; align-items: center; gap: 6px; }

/* ---------- 兑换码：单条复制（v0.12.0，需求 3） ---------- */
.ecard .code-name { display: inline-flex; align-items: center; gap: 8px; overflow: visible; }
.icon-btn.sm { padding: 3px 7px; font-size: 11.5px; gap: 4px; }
.icon-btn.sm .ic { width: 12px; height: 12px; }
.code-gen-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-height: 260px; overflow: auto; }
.code-gen-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); }
.code-gen-row .mono { font-size: 12.5px; }
.code-name .mono { font-size: 13px; }

/* ---------- 出码：方式切换 + 预览（v0.31.173） ----------
   三张生成卡合并成一张后，参数区靠 .hidden 切换显隐（.hidden 是全局类）。
   预览用**虚线框**而不是实线卡：它在语义上是"摘要"，不是又一个表单区 ——
   实线框会让人以为这里也要填东西。 */
.gen-preview {
  margin-top: 14px; padding: 10px 12px;
  border: 1px dashed var(--border); border-radius: 10px; background: var(--bg);
}
.gen-preview .gp-main { font-size: 13px; font-weight: 600; color: var(--text); line-height: 1.5; }
.gen-preview .gp-sub { margin-top: 4px; font-size: 12px; color: var(--muted); line-height: 1.6; }
/* 参数明显不对时整块转红：预览是"点之前最后一眼"，红一下比小字提示有效 */
.gen-preview.warn { border-color: var(--err); background: var(--err-tint); }
.gen-preview.warn .gp-main { color: var(--err); }

/* ---------- 兑换码台账（v0.31.171）：三类码合并成一张紧凑表 ----------
   改版前两类码各是一份卡片列表，每条码一张卡（卡里 4~5 行）—— 200 张码就是
   200 张卡、几千像素，而且两份列表的字段集还不一样。合并成表之后：
   · overflow:auto 是刻意的：列有最小宽，窄屏整表横向滚动，而不是把列压到折行
     （折行会让「码值」和它的复制按钮分到两行，看起来像坏图）；
   · 表头 position:sticky + 表体滚动（max-height）：码再多也不把页面拉长；
   · 每个格子 min-width:0 + 省略号：grid 子项默认 min-width:auto，
     不置 0 的话长码值会把整列撑开、把后面七列一起推歪。 */
.code-table {
  border: 1px solid var(--border); border-radius: 12px; background: var(--panel);
  overflow: auto; max-height: min(60vh, 560px);
}
.code-row {
  display: grid; align-items: center; gap: 6px; padding: 0 12px;
  /* 固定行高（v0.31.172）：复制按钮 / 启停按钮 / 状态标签三种行内控件的行盒
     高度本来不同（实测 38.59px 与 37.59px），每行独立取最大值就会参差 1px。
     固定高度后所有行严格等高；窄屏下面板改成块状时 height 重置为 auto。 */
  height: 34px;
  border-bottom: 1px solid var(--border); font-size: 12.5px;
}
#codeRows > .code-row:last-child { border-bottom: 0; }
.code-head {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel); color: var(--muted);
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
}
.code-row > * { min-width: 0; }
.code-row .mono { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.code-cell-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.code-cell-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.code-cell-code { display: flex; align-items: center; gap: 6px; overflow: hidden; }
.code-cell-code .mono { flex: 1 1 auto; min-width: 0; }
.code-cell-code .icon-btn { flex: 0 0 auto; }
.code-cell-ops { display: flex; align-items: center; justify-content: flex-end; }
.code-cell-ops button { padding: 3px 8px; font-size: 11.5px; white-space: nowrap; }

/* 台账里的勾选框（v0.31.175）：活动码可批量停用。
   一次性码给一个**禁用**的占位框，而不是干脆不渲染 —— 否则同一列里
   "有的行有框、有的行没框"，看上去像渲染错位。 */
.code-row .code-cb { width: 14px; height: 14px; margin: 0; flex: 0 0 auto; cursor: pointer; }
.code-row .code-cb:disabled { cursor: not-allowed; opacity: .45; }
.code-head-code { display: flex; align-items: center; gap: 6px; }
/* 选中行给底色（不是边框）：边框会改变盒子尺寸，行高会抖。 */
.code-row.sel { background: var(--tint); }

/* 窄屏（≤1100px）：一行八列的最小宽合计约 776px（含 224px 侧栏与主区留白），
   视口再窄就只能横向滚动 —— 与其把八列压成省略号森林，
   不如把每条码改成**五行**：码值+操作 / 类型+状态 / 规则 / 面额+用量 / 时间。
   断点取 1100 而不是常见的 768：这里的瓶颈是"八列的最小宽"，不是"手指宽度"，
   平板竖屏（820）同样排不下 —— 按设备宽度断点会让 iPad 上出现横滚条。
   用 grid-template-areas 而不是 :nth-child 定位：将来插一列时不会静默错位。
   ⚠️ 列宽是 JS 写在行内（CODE_COLS），所以这里必须 !important 才能覆盖 ——
   否则手机上的窄屏布局会被行内那一套八列模板顶掉。 */
@media (max-width: 1100px) {
  .code-table { max-height: 68vh; }
  .code-head { display: none; }  /* 表头对应的是"一行八列"，换布局后它只会误导 */
  .code-row {
    height: auto;  /* 块状排列，高度由内容决定 */
    /* 每个视觉行固定 24px：真机量出活动码行（含启停按钮）141px、其余 142px ——
       行内控件的行盒高度差 1px，块状布局下同样会让行高参差。
       24px 是所有行内控件的上限（状态标签 23.6px / 按钮 23px），装得下才不会溢出。 */
    grid-auto-rows: 24px;
    grid-template-columns: 1fr auto !important;
    grid-template-areas:
      "code   ops"
      "kind   status"
      "rule   rule"
      "num    usage"
      "time   time";
    row-gap: 4px; padding: 9px 12px;
  }
  .code-cell-code { grid-area: code; }
  .code-cell-ops { grid-area: ops; }
  .code-cell-kind { grid-area: kind; justify-self: start; }
  .code-cell-status { grid-area: status; justify-self: end; }
  .code-cell-rule { grid-area: rule; }
  .code-cell-num { grid-area: num; text-align: left; }
  .code-cell-usage { grid-area: usage; justify-self: end; }
  .code-cell-time { grid-area: time; }
}
/* 活动码的类型标签用紫色系：和「充值」的强调色区分开，但仍是"信息"而不是"警告" */
.tag.kind-promo { color: #7c3aed; border-color: transparent; background: rgba(124, 58, 237, .12); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .tag.kind-promo { color: #b9a2ff; background: rgba(124, 58, 237, .22); }
}
[data-theme="dark"] .tag.kind-promo { color: #b9a2ff; background: rgba(124, 58, 237, .22); }

/* ---------- 模型倍率表（v0.31.74，需求 4：列必须逐行对齐） ----------
   .code-gen-row 是 flex + space-between，每列宽度由该行内容决定 ——
   模型名长短、标签有无都会让下一行的列位置横向漂移，所以倍率表不能用它。
   这里用固定 grid 模板：表头与数据行**共用同一个类**，天然对齐；
   数字列右对齐 + tabular-nums（0.05 与 1.2345 宽度不跳）。 */
/* 字重工具类（v0.31.187）：表头 / 行内主名字要 600，但**不写行内样式** ——
   行内 fontWeight 与"手工拼表头"的指纹撞在一起，静态闸门分不清"表头抄了一份"
   还是"某个格子加粗"。一个类既让闸门能判定，也让字重只有一个来源。 */
.w600 { font-weight: 600; }

.ratio-row {
  display: grid; align-items: center; gap: 8px;
  padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
  grid-template-columns: minmax(150px, 1.7fr) 88px 84px 52px 104px 96px 188px;
}
.ratio-row .mono { font-size: 12.5px; }
/* 官方倍率、实测积分：数字列右对齐 */
.ratio-row > span:nth-child(2),
.ratio-row > span:nth-child(5) { text-align: right; font-variant-numeric: tabular-nums; }
/* 模型名超长：省略号 + title 看全名（hover） */
.ratio-row > .cell-model { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* v0.40（有数据逐页实测）：操作审计的「操作人」列没有省略号规则 → 长昵称被硬切。
 * 这一行所有格统一「单行 + 省略号」，避免出现半截字（详情格也带 title 兜底）。 */
.al-row > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 单元格里的**文字**一律单行省略（v0.31.184，审查 UI-2）。
   为什么必须加：长用户名 / 长分组名会折成两三行，把**整行**撑高 ——
   layoutprobe 在四视口里都量到了同一张表里 36px 与 59px 两种行高，
   看上去就是错版（比"名字被省略"难看得多，而且没人猜得到是名字长度造成的）。
   只加 white-space / overflow / text-overflow，不改 display：
   Key 名 + 掩码、拒绝原因 + 占比条这类**复合格子**仍是块级堆叠，只是文本不再折行。
   min-width:0 也必须有：grid 子项默认 min-width:auto，不置 0 时长名会把列撑开。 */
.ratio-row > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 计费倍率操作区：input 固定宽，按钮不收缩 */
.ratio-row > .cell-ops { display: flex; gap: 4px; justify-content: flex-end; align-items: center; }
.ratio-row > .cell-ops > button { flex: 0 0 auto; }
/* ⚠️ 输入框宽度必须**显式钉死**（v0.39.82）：inline 写了 width:68px，但 flex 子项的自动最小宽度取
   **内容最小宽**（number 输入默认 size=20 → ≈160px），压不下去就按 justify-content:flex-end
   从**左侧**溢出，把「状态」列的标签压在下面 —— 看着像标签被裁，实际是隔壁列越界。
   钉在 96px 而不是 68px：68 装不下 placeholder「0=全局」（会被截成「0=全」），
   而 placeholder 正是"0 表示回落到全局倍率"的唯一提示。 */
.ratio-row > .cell-ops > input { flex: 0 0 96px; min-width: 0; }
/* 多种业务表共享 ratio-row，内联列模板必须保持表头/数据列一致。
   由表容器内部横滚，不能只把个别数据格跨列而让表头仍用桌面列宽。
   同一容器下的行 stretch 到共同 max-content 宽度，避免逐行宽度漂移。 */
:where(div):has(> .ratio-row) { display: grid; grid-template-columns: minmax(100%, max-content); overflow-x: auto; max-width: 100%; }

/* ---------- 登录页（面板分离：用户 / 管理两套） ---------- */
.login-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; }
.login-panel { width: 100%; max-width: 400px; }
.login-panel .login-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.login-panel .login-brand .logo {
  width: 44px; height: 44px; border-radius: 12px; flex: 0 0 auto;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--on-accent); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; letter-spacing: .5px;
}
.login-panel .login-brand.admin .logo { background: linear-gradient(135deg, #1f2937, #4b5563); }
.login-panel .login-brand b { display: block; font-size: 16px; }
.login-panel .login-brand span { font-size: 12px; color: var(--muted); }
.role-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; padding: 2px 8px; border-radius: 999px; background: var(--tint); color: var(--accent-ink); }
.role-badge.admin { background: rgba(75, 85, 99, .15); color: var(--muted); }


/* ---------- 批量导入 JSON 弹窗（v0.15.3） ---------- */
.import-ta {
  width: 100%; min-height: 180px; box-sizing: border-box;
  font-family: var(--mono); font-size: 12px; line-height: 1.55;
  padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  resize: vertical;
}
.import-out { margin-top: 10px; font-size: 12px; line-height: 1.7; }
.import-out.ok { color: var(--fg); }
.import-out.err { color: var(--danger, #d9534f); }
.import-out .muted { margin-left: 6px; }
.import-out .err { color: var(--danger, #d9534f); margin-top: 6px; }
.import-adv { margin-top: 12px; border-top: 1px dashed var(--border); padding-top: 10px; }
.import-adv summary { cursor: pointer; font-size: 12px; color: var(--muted); }
.import-adv .row { gap: 8px; }

/* ---------- 账号池批量选择（v0.15.4） ---------- */
.pick { display: inline-flex; align-items: center; margin-right: 8px; cursor: pointer; }
.pick input, .pick-all input { width: 15px; height: 15px; cursor: pointer; accent-color: var(--accent); }
.pick-all { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); cursor: pointer; }
/* 选中描边：旧写法只覆盖 .acc-card（管理后台账号池），但勾选卡片不止那里
   ——用户控制台「成长任务」页同样用 .ecard + .pick 勾选，故放宽到所有 .ecard。 */
.ecard.sel { outline: 1.5px solid var(--accent); outline-offset: -1.5px; }

/* ===================================================================
   v0.17.9 · 代理页（出口代理 / 账号代理 / 去哪弄代理）
   子区用纯显隐切换（.egr-sub），不重排 DOM —— 三个子区共用同一份已拉到的数据，
   切换零请求。帮助子区里的表格/清单样式也只在这里用，故就地定义。
   =================================================================== */
.egr-sub > .card:first-child { margin-top: 0; }
/* 子区里的卡片沿用全局 .card 间距；这里只补「子区之间」的呼吸感。 */
.egr-sub .section-head { margin-top: 18px; }
.egr-sub .card + .card { margin-top: 14px; }

.egr-help-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
/* 原生表格也在自身容器内滚动，保留完整列内容与关联表头。 */
:where(div):has(> table) { max-width: 100%; min-width: 0; overflow-x: auto; }
.egr-help-table th, .egr-help-table td {
  text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border);
  vertical-align: top; line-height: 1.6;
}
.egr-help-table thead th { color: var(--muted); font-weight: 600; white-space: nowrap; }
.egr-help-table tbody tr:last-child td { border-bottom: 0; }
.egr-help-table td:first-child { white-space: nowrap; }

.egr-help-list { margin: 0; padding-left: 20px; font-size: 12.5px; line-height: 1.85; color: var(--text); }
.egr-help-list li { margin-bottom: 4px; }
.egr-help-list code {
  background: var(--tint); border: 1px solid var(--border); border-radius: 5px;
  padding: 0 4px; font-family: ui-monospace, Consolas, monospace; font-size: 12px;
}

/* ===================================================================
   v0.26.6 · 共享 UI 套件样式
   （管理后台与用户控制台共用；对照 workbuddy-manager 的交互调研结论）
   ------------------------------------------------------------------
   设计取向：沿用本项目既有的设计令牌（--card / --border / --muted …），
   不引入新的色板。动效一律走 transform/opacity，且 @media
   (prefers-reduced-motion) 下全部关掉 —— 动画是锦上添花，不该成为障碍。
   =================================================================== */

/* ---------- ① 通知：右上角堆叠卡片 ---------- */
.toast-host {
  position: fixed; top: 16px; right: 16px; z-index: 200;
  display: flex; flex-direction: column; gap: 8px;
  width: min(360px, calc(100vw - 32px));
  pointer-events: none; /* 容器不吃点击，只有卡片吃 */
}
.toast-card {
  position: relative; overflow: hidden; pointer-events: auto;
  display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 10px 12px 12px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow);
  font-size: 12.5px; line-height: 1.55; color: var(--text);
  animation: toast-in .22s cubic-bezier(.2,.9,.3,1.2);
}
.toast-card.out { animation: toast-out .2s ease forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateX(14px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateX(14px) scale(.97); } }
.toast-ico {
  flex: 0 0 auto; width: 16px; height: 16px; border-radius: 50%; margin-top: 1px;
  display: grid; place-items: center; font-size: 10px; font-weight: 700;
  background: var(--tint); color: var(--accent-ink);
}
.toast-card.ok .toast-ico { background: var(--ok-tint); color: var(--ok); }
.toast-card.err .toast-ico { background: var(--err-tint); color: var(--err); }
.toast-card.warn .toast-ico { background: var(--warn-tint); color: var(--warn); }
.toast-msg { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.toast-x {
  flex: 0 0 auto; border: 0; background: transparent; color: var(--muted);
  font-size: 15px; line-height: 1; padding: 0 2px; border-radius: 6px;
}
.toast-x:hover { background: var(--tint); color: var(--text); }
/* 底部倒计时线：一眼看出"它还会待多久"，比毫无征兆地消失友好。 */
.toast-bar {
  position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  background: currentColor; opacity: .28; transform-origin: left center;
  animation: toast-bar linear forwards;
}
.toast-card.ok .toast-bar { color: var(--ok); }
.toast-card.err .toast-bar { color: var(--err); }
.toast-card.warn .toast-bar { color: var(--warn); }
@keyframes toast-bar { from { transform: scaleX(1); } to { transform: scaleX(0); } }

/* ---------- ② 确认对话框 ---------- */
.dlg-back {
  position: fixed; inset: 0; z-index: 210;
  background: rgba(15, 23, 42, .42);
  display: grid; place-items: center; padding: 20px;
  animation: fade-in .16s ease;
}
.dlg {
  width: min(440px, 100%); background: var(--card);
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: var(--shadow); padding: 18px;
  animation: dlg-in .2s cubic-bezier(.2,.9,.3,1.15);
}
@keyframes fade-in { from { opacity: 0; } }
@keyframes dlg-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
.dlg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.dlg-title { font-size: 15px; font-weight: 650; }
.dlg.danger .dlg-title { color: var(--err); }
.dlg-msg { font-size: 13px; line-height: 1.65; color: var(--text); }
.dlg-detail {
  margin-top: 8px; padding: 9px 11px; border-radius: 10px;
  background: var(--tint); font-size: 12px; line-height: 1.6; color: var(--muted);
  overflow-wrap: anywhere;
}
.dlg-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.dlg-foot button { min-width: 78px; }

/* ---------- ③ 命令面板 ---------- */
.pal-trigger { gap: 6px; }
.pal-trigger-ico { font-size: 13px; }
.pal-kbd {
  font-family: inherit; font-size: 10.5px; line-height: 1.5;
  padding: 0 5px; border-radius: 5px; border: 1px solid var(--border);
  background: var(--bg); color: var(--muted);
}
.pal-bk {
  position: fixed; inset: 0; z-index: 220;
  background: rgba(15, 23, 42, .38);
  display: flex; justify-content: center; align-items: flex-start;
  padding: 10vh 16px 16px; animation: fade-in .14s ease;
}
.pal {
  width: min(560px, 100%); max-height: 74vh; display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 16px; box-shadow: var(--shadow); overflow: hidden;
  animation: dlg-in .18s cubic-bezier(.2,.9,.3,1.15);
}
.pal-row {
  display: flex; align-items: center; gap: 9px;
  padding: 0 14px; border-bottom: 1px solid var(--border);
}
.pal-ico { color: var(--muted); font-size: 15px; }
.pal-input {
  flex: 1 1 auto; height: 46px; border: 0; background: transparent;
  font-size: 14px; color: var(--text); outline: none;
}
.pal-list { overflow-y: auto; padding: 4px 0; }
.pal-item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 14px; font-size: 13px; cursor: pointer; color: var(--text);
}
.pal-item.on { background: var(--tint); }
.pal-lb { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 右侧归属：光看名字「用户」不知道是页面还是数据行，分组列把这个歧义消掉。 */
.pal-gp { flex: 0 0 auto; font-size: 11px; color: var(--muted); }
.pal-empty { padding: 26px 16px; text-align: center; }
.pal-empty-t { font-size: 13px; }
.pal-empty-d { margin-top: 4px; font-size: 11.5px; color: var(--muted); }
.pal-foot {
  display: flex; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--border); padding: 7px 14px;
  font-size: 11px; color: var(--muted);
}
.pal-count { font-variant-numeric: tabular-nums; }

/* ---------- ④ 常驻失败横幅 ---------- */
.load-banner {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 12px; padding: 8px 10px;
  border: 1px solid transparent; border-radius: 12px;
  background: var(--warn-tint); font-size: 12px; line-height: 1.55;
}
.load-banner-ico {
  flex: 0 0 auto; width: 15px; height: 15px; border-radius: 50%;
  display: grid; place-items: center; font-size: 10px; font-weight: 700;
  background: var(--warn); color: var(--on-accent);
}
.load-banner-msg { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.load-banner button { flex: 0 0 auto; }

/* ---------- ⑤ 骨架屏 ---------- */
.skel {
  display: block; height: 10px; border-radius: 6px;
  background: linear-gradient(90deg, var(--border) 25%, var(--tint) 37%, var(--border) 63%);
  background-size: 400% 100%;
  animation: skel-shimmer 1.25s ease-in-out infinite;
}
@keyframes skel-shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
.skel-line { margin: 6px 0; }
.skel-line.short { width: 45%; }
.skel-row td { padding: 10px; }
.skel-card {
  border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; background: var(--card);
}

/* ---------- ⑥ 侧边栏过滤 ---------- */
.nav-filter-wrap {
  position: relative; display: flex; align-items: center; gap: 6px;
  margin: 0 10px 8px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg);
}
.nav-filter-ico { color: var(--muted); font-size: 12px; }
.nav-filter {
  flex: 1 1 auto; min-width: 0; border: 0; background: transparent;
  height: 30px; font-size: 12.5px; color: var(--text); outline: none;
}
.nav-filter-x {
  border: 0; background: transparent; color: var(--muted);
  font-size: 14px; line-height: 1; padding: 0 2px; border-radius: 6px;
}
.nav-filter-x:hover { background: var(--tint); color: var(--text); }
.nav-filter-empty { margin: 6px 12px; font-size: 12px; color: var(--muted); }

/* ---------- ⑦ 表格：粘性表头 + 行悬停 ---------- */
.egr-help-table thead th { position: sticky; top: 0; background: var(--card); z-index: 1; }
.egr-help-table tbody tr:hover td { background: var(--tint); }

/* ---------- ⑧ 无障碍：键盘焦点必须看得见 ---------- */
/* 鼠标点击不该出现焦点环，键盘导航必须出现 —— `:focus-visible` 正是这个语义。 */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.nav-item:focus-visible, .seg:focus-visible { outline-offset: -2px; }

/* ---------- ⑨ 仅屏幕阅读器可见 ---------- */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .toast-card, .toast-card.out, .dlg-back, .dlg, .pal-bk, .pal { animation: none !important; }
  .skel { animation: none; background: var(--border); }
  .toast-bar { animation: none; opacity: 0; }
}

/* ---------- 移动端主导航：可发现的折叠菜单 ---------- */
.mobile-nav-toggle { display: none; }
@media (max-width: 860px) {
  .mobile-nav-toggle {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    margin: 2px 14px 8px;
  }
  .sidebar:not(.nav-open) .nav,
  .sidebar:not(.nav-open) .nav-filter-wrap { display: none; }
  .sidebar.nav-open .nav-filter-wrap { display: flex; }
  .sidebar.nav-open .nav {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    max-height: min(56vh, 520px);
    overflow-x: hidden;
    overflow-y: auto;
    padding: 4px 10px 10px;
  }
  .sidebar.nav-open .nav-group { display: block; }
  .sidebar.nav-open .nav-item { width: 100%; white-space: normal; }
  .sidebar.nav-open .nav-item .badge { display: inline-block; }
}

/* ---------- 更新简介与产物状态（v0.26.7） ---------- */
/* 版本说明按行渲染的容器：左侧竖线让"这是引用的清单内容"一眼可辨 ——
   清单是外部文件，与页面上我们自己写死的文案应当有视觉区分。 */
.upd-notes {
  margin: 8px 0 4px;
  padding: 8px 12px;
  border-left: 3px solid var(--accent);
  background: var(--tint);
  border-radius: 0 8px 8px 0;
}
.upd-line {
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--text);
}
.upd-line + .upd-line { margin-top: 3px; }

/* 产物状态行：徽标 + 体积 + 校验信息横排，窄屏自动换行 */
.upd-dl {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 10px;
}

/* 应用指引：需要被完整读、且要能复制，所以给独立区块 + 允许断行。
   用虚线边而不是实线：它是"待执行的操作"，与已完成的结论区分开。 */
.upd-hint {
  flex-basis: 100%;
  font-size: 12px;
  line-height: 1.75;
  color: var(--muted);
  background: var(--card);
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  word-break: break-all;
  user-select: text;
}

/* ---------- 输入对话框字段（v0.27.0，promptDialog） ---------- */
/* 每个字段独立成块（标签在输入框上方）：这些表单往往有 2 个字段
   （数值 + 原因 / 日限额 + 月限额），横排会在窄屏挤成一团。 */
.dlg-body { margin-top: 10px; }
.dlg-field + .dlg-field { margin-top: 10px; }
.dlg-label {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 4px;
}
.dlg-input {
  width: 100%;
  box-sizing: border-box;
  padding: 7px 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.dlg-input:focus {
  outline: none;
  border-color: var(--accent);
}
textarea.dlg-input { resize: vertical; line-height: 1.6; }
.dlg-hint {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--muted);
}

/* ---- 密钥模式两张卡（v0.31.194，用户控制台「生成密钥」）----

   为什么不用分段控件：这两个选项的差别是"扣不扣余额、用谁的账号"，
   四个字装不下；而它决定了这把 Key 走平台号池还是你自己的号，是**计费口径**。
   选中态同时给边框、底色、勾标与 aria-checked —— 只靠颜色的话，色弱用户看不出来。 */
.key-modes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px;
}
.key-mode {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding: 10px 12px;
  font: inherit;
  text-align: left;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.key-mode:hover { border-color: var(--accent); }
.key-mode.active {
  border-color: var(--accent);
  background: var(--tint);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.key-mode.active::after {
  content: '✓';
  position: absolute;
  top: 9px;
  right: 11px;
  font-weight: 700;
  color: var(--accent-ink);
}
.key-mode-head { display: flex; align-items: center; gap: 8px; padding-right: 18px; }
.key-mode-desc { margin: 2px 0 0; font-size: 12px; line-height: 1.5; color: var(--muted); }
.key-mode-fit { font-size: 11.5px; line-height: 1.45; color: var(--muted); }

/* ---- 计费分组卡片（v0.31.195）----

   生成密钥页与「切换分组」弹窗共用这一套（JS 侧唯一实现 keyGroupCards）。
   以前建键页是 <select>：一列文字塞不下"几个模型 / 按什么倍率 / 是否整组免费"，
   而这三项恰恰是挑组时唯一要看的东西 —— 挪进 title 等于没有。
   最小宽写 `min(240px, 100%)`：不然窄屏下 240 会撑出页面横滚。 */
.gcards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 8px;
}
.gcard {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 9px 11px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.gcard:hover { border-color: var(--accent); }
.gcard.on {
  border-color: var(--accent);
  background: var(--tint);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* 禁用（空组 / 代挂自用 / 列表不可改）：只降透明度不够 —— 读屏靠 aria-disabled/disabled，
   视觉靠这层灰。两样都要有，因为它们是给两类人看的。 */
.gcard.off { opacity: .55; cursor: not-allowed; }
.gcard.off:hover { border-color: var(--border); }
.gcard input { margin: 2px 0 0; flex: none; }
.gcard-t { flex: 1; min-width: 0; }
.gcard-title {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 13px;
  line-height: 1.35;
}
.gcard-meta { margin-top: 2px; font-size: 12px; line-height: 1.45; color: var(--muted); }

/* 计费规则说明块：选组 = 选一整套模型的价，这段必须跟候选卡片在同屏。 */
.gnote {
  margin-top: 8px;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--muted);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.gnote b { color: var(--text); }

/* ---- 计费分组下拉（v0.31.196）：收起一行、展开是卡片 ----
   为什么自己做而不是 <select>：选项里要带「几个模型 / 按什么倍率 / 整组免费 / 空组」，
   一行文字放不下，塞进 title 等于没有。触发器只负责"我选了谁 + 它多少钱"，
   展开面板里每个选项就是「切换分组」弹窗里那张卡（同一份 keyGroupCard）。 */
.combo { position: relative; width: 100%; max-width: 420px; }
.combo-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.combo-btn:hover:not(:disabled) { border-color: var(--accent); }
.combo-btn[aria-expanded="true"] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.combo-btn:disabled { opacity: .6; cursor: not-allowed; }
.combo-val {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.combo-sub { margin-left: 6px; font-size: 12px; color: var(--muted); }
.combo-caret {
  flex: none;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--muted);
  transition: transform .12s ease;
}
.combo-btn[aria-expanded="true"] .combo-caret { transform: rotate(180deg); }
.combo-panel {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  max-height: 320px;
  overflow: auto;
  padding: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .18);
}
/* ⚠️ 必须显式写：上面的 display:grid 会盖掉浏览器默认的 [hidden]{display:none}，
   少了这条"收起的面板"会一直显示（.nav-item[hidden] 就是这么栽过的）。 */
.combo-panel[hidden] { display: none; }
/* 展开面板里的卡片：键盘用 focus() 移动，必须看得见焦点在哪一行。 */
.combo-panel .gcard:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ---------- 公告分级色带（v0.39.52） ----------
   为什么要色带而不只是右上角一个标签：公告卡片一屏可能三四张，
   小标签在扫视时几乎读不出来；左侧一条 3px 的色带是"余光就能分辨"的位置。
   urgent 再加一层底色与呼吸点 —— 它是唯一要求用户手动确认的档位。 */
.ecard.lv-info { border-left: 3px solid var(--accent); }
.ecard.lv-warn { border-left: 3px solid var(--warn); }
.ecard.lv-important { border-left: 3px solid var(--err); }
.ecard.lv-urgent {
  border-left: 3px solid var(--err);
  background: linear-gradient(90deg, var(--err-tint), transparent 42%), var(--card);
}
.ecard.lv-urgent > header { background: var(--err-tint); }
.tag.urgent {
  color: #fff; background: var(--err); border-color: transparent; font-weight: 600;
}
/* 未读点：与已读区分开，避免"这条我到底看过没有" */
.ann-unread-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--err); margin-right: 6px; vertical-align: middle;
}
/* 紧急档的"必须确认"提示（弹窗内） */
.ann-must-ack {
  margin-top: 8px; font-size: 12px; color: var(--err);
}

/* ---------- 插件卡：开关恒在右上、动作恒在左下（v0.39.52） ----------
   用户报"插件按钮和开关位置不统一"。根因是它们和说明文字混在同一个 flex 行里，
   行内元素一多，开关就被推到不同位置；一列卡片看下来是锯齿状的。
   现在两端的开关都固定在这个位置（管理端与用户端共用 pluginCard）。 */
.plugin-ops {
  margin-left: auto;
  display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
}
.plugin-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  /* v0.40（用户 #11）：footer 顶到卡片底部 —— 卡片等高后，按钮才会落在同一条水平线上。
     `margin-top:auto` 是这套做法的关键：它把"描述长短不一"的差异吸收在卡片中部。 */
  margin-top: auto; padding-top: 10px;
  /* 主按钮靠右、说明靠左：与全站「动作在右」的惯例一致，也让一排卡片的按钮对齐。 */
  justify-content: space-between;
}
.plugin-foot > button { order: 2; margin-left: auto; }
/* ⚠️ 只靠 footer 的 `justify-content:space-between` 不够：卡片没有副说明时
   footer 只有一个子元素，space-between 会把它放**左边**（截图实测「提示词预设」
   那张就是这样），一排卡片里按钮一左一右看着就乱。靠 `margin-left:auto` 钉死右对齐。 */
.plugin-foot > .muted { order: 1; }

/* v0.40（用户 #11）：插件卡等高（.cards 网格自带 gap，卡片不再自加外边距）。 */
#pluginCards { align-items: stretch; }
#pluginCards > .plugin-card { display: flex; flex-direction: column; margin-bottom: 0; }
#pluginCards > .plugin-card > .sub { flex: 0 0 auto; }

/* ---------- 抽屉形态弹窗（v0.39.56） ----------
   为什么用户详情要抽屉而不是居中弹窗：那一屏要看"这个人"和"他做过什么"，
   内容比一般弹窗长得多，居中弹窗只能给 440px 宽 + 50vh 高，一直在滚。
   抽屉贴右、占满视口高度，左侧仍能看到列表用于对照。
   只对加了 .drawer 的弹窗生效，其余弹窗行为不变。 */
.modal.drawer {
  margin: 0 0 0 auto;
  max-width: 620px;
  width: min(620px, 96vw);
  height: 100vh;
  max-height: 100vh;
  border-radius: 0;
  display: flex;
  flex-direction: column;
}
.modal.drawer .modal-body { flex: 1 1 auto; overflow-y: auto; }
.modal-mask.drawer-mask { align-items: stretch; padding: 0; }

/* ---------- 居中大弹窗 + 详情卡片网格（v0.39.63） ----------
   用户详情从"抽屉 + 逐行键值"改成"居中大弹窗 + 摘要卡 + 主题卡"：
   · 抽屉在宽屏上把内容推到最右，看数字时视线一直贴边；
   · 逐行键值一屏只能塞 8~10 项，而这个人有 30+ 项，得滚很久。
   3 列卡片能在同一屏里把"钱 / 用量 / 关系 / 资产"分开摆清楚。 */
.modal.wide {
  width: 1120px;
  max-width: 96vw;
  max-height: 86vh;
}
.modal.wide .modal-body { overflow-y: auto; }

.detail-kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px; margin: 0 0 14px;
}
.detail-kpi {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 12px; background: var(--bg);
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.detail-kpi .k { font-size: 11.5px; color: var(--muted); }
.detail-kpi .v { font-size: 19px; font-weight: 650; font-variant-numeric: tabular-nums; }
.detail-kpi .d { font-size: 11.5px; color: var(--muted); }
.detail-kpi.accent .v { color: var(--accent-ink); }
.detail-kpi.ok .v { color: var(--ok); }

.detail-grid {
  /* 300px 下限 = 弹窗内可用宽 1096 时正好 3 列（260 会挤出 4 列，实测过）。
     窄屏自动降 2 列 / 1 列，不必写 media query。 */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 12px; align-items: start;
}
.detail-card {
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--card); padding: 12px 13px;
  display: flex; flex-direction: column; gap: 7px; min-width: 0;
}
.detail-card > h4 { margin: 0 0 2px; font-size: 12.5px; font-weight: 600; color: var(--muted); }
/* 跨满整行：用于代挂账号、操作记录这类条目数不定的段 */
.detail-card.full { grid-column: 1 / -1; }
.detail-card .kv-list { display: flex; flex-direction: column; gap: 5px; }

/* 余额分桶占比条：可提现 / 不可提现一眼看出比例，而不是读两个数字自己算 */
.bucket-bar { display: flex; height: 9px; border-radius: 999px; overflow: hidden; background: var(--border); margin: 2px 0 6px; }
.bucket-bar > i { display: block; height: 100%; }
.bucket-bar > i.wd { background: var(--ok); }
.bucket-bar > i.nowd { background: var(--warn); }
.bucket-legend { display: flex; gap: 12px; font-size: 11.5px; color: var(--muted); flex-wrap: wrap; }
.bucket-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 4px; }

/* 限额进度条（v0.39.64）：放在「今日消费」KPI 卡里。
   设了日限额才显示 —— 0 = 不限，画一根满条会让人以为"快用完了"。 */
.quota-bar {
  height: 5px; border-radius: 999px; background: var(--border);
  overflow: hidden; margin-top: 6px;
}
.quota-bar > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .3s; }

/* ---------- 用量分析：摘要卡 + 二级页（v0.39.68） ----------
   二级页不是新 pane，而是同一个 pane 里按 data-uview 显隐的卡片组 ——
   这样刷新/切页不必重拉数据（各块本来就有自己的 loader）。
   home（摘要卡）由 #uaHome 单独控制。 */
/* v0.40.38：二级页已改成一页 6 主卡，页面里不再有 [data-uview]；
   #uaHome（6 张摘要卡）改为**常显** —— 它原来靠 .uv-on 显示，切页逻辑删掉后
   会整块 display:none（实测首屏直接看不到 6 张卡，只剩筛选与口径）。 */
.pane[data-pane="report"] #uaHome { display: block; }

/* 摘要卡：整张卡可点（点"查看详情"和点卡身是同一件事，少一个精确点击目标） */
/* v0.40.13（用户第 6 条）：6 张「用量分析 · 概览」卡里的**迷你趋势条与「查看详情」必须对齐**。
 *
 * 实测（1440 档）：6 张卡等高 221px，但 `.ua-spark` 的起点分别是 **92 / 136 / 147 / …** —— 
 * 因为 `.ua-sub` 有的占一行、有的占两行，把趋势条挤到了不同高度，
 * 一排卡看上去像"柱形图高度参差不齐"。这是"页内对齐"问题，不是图表数据问题。
 *
 * 修法：卡内改成 flex 列，趋势条与「查看详情」一起**沉到底部**（margin-top:auto），
 * 于是无论副文字几行，趋势条的上沿与按钮都在同一水平线。 */
.ua-card { cursor: pointer; transition: border-color .15s, transform .15s;
  display: flex; flex-direction: column; }
.ua-card:hover { border-color: var(--accent); }
/* margin-top:auto 让「查看详情」贴住卡片底部；有趋势条时由下面的
   `.ua-spark + .ua-more` 覆盖成固定 8px，保证两者挨着。 */
.ua-card .ua-more { font-size: 11.5px; color: var(--accent-ink); margin-top: auto; }
/* v0.40.13（用户第 6 条）：趋势条**沉底**而不是固定 8px 上边距 ——
   实测原来 6 张卡的趋势条上沿分别是 92/136/147…，因为副文字行数不同把它们挤歪了。 */
.ua-card .ua-spark { margin-top: auto; height: 34px; display: flex; align-items: flex-end; gap: 2px; }
.ua-card .ua-spark + .ua-more { margin-top: 8px; }
.ua-card .ua-spark > i { flex: 1 1 0; max-width: 14px; background: var(--accent); opacity: .55; border-radius: 2px 2px 0 0; min-height: 2px; }
/* max-width 是必要的：只有 1 个数据点时（新模型第一天），flex:1 会让它撑满整条，
   看起来像一根颜色块而不是"趋势"。限制单柱宽度后，1 根就是一根。 */
.ua-card .ua-sub { font-size: 11.5px; color: var(--muted); margin-top: 4px; }

/* 折线图 + y 轴刻度（v0.39.74）：刻度是 HTML 而不是 <text>，
   因为图本身是 preserveAspectRatio:none（横向拉伸），文字会被拉扁。 */
.chart-axis-wrap { display: flex; gap: 6px; align-items: stretch; }
.chart-axis-wrap > .ca-plot { flex: 1 1 auto; min-width: 0; display: flex; }
.chart-axis-wrap > .ca-plot > svg { width: 100%; height: 100%; display: block; }
.chart-axis-labels {
  flex: 0 0 44px; display: flex; flex-direction: column; justify-content: space-between;
  font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums;
  text-align: right; line-height: 1; padding: 1px 0;
}

/* 用量账单的三段（v0.39.74）：按 data-uview 显隐块。
   ⚠️ .live-grid 必须单独还原成 grid —— 只写 display:block 会把两列变一列，
   而"变一列"看起来只是布局松了，不会报错。 */
.pane[data-pane="usage"] [data-uview] { display: none; }
.pane[data-pane="usage"] [data-uview].uv-on { display: block; }
.pane[data-pane="usage"] .live-grid[data-uview].uv-on { display: grid; }

/* ---------- v0.40.13：桌面托管的两张账号列表改为「区内滚动」 ----------
 * 用户第 8 条原话：「桌面托管的界面功能布局混乱，展示顺序以及占用界面太多，需要调整或者收纳」。
 * 实测（20 个账号的本地库）：整页 5526px，其中「桌面账号镜像」一个格就 3732px 高
 * —— 平铺 80+ 账号时会长到看不见底，把同排另一个格和内层筛选条全拖走。
 * 收纳而不是删减：给列表一个视口相关的高度上限，超出在**区内**滚动，
 * 筛选条与标题始终可见（这是"能翻多久"和"能不能一眼看完"的分界）。
 * 用 min(58vh, 560px) 而不是固定 px：手机/矮窗口下不会憋成一条缝。 */
#desktopPoolAccounts,
#desktopMirrors {
  max-height: min(58vh, 560px);
  overflow-y: auto;
  /* 滚动条会吃掉 8~12px，补在右侧，避免卡片被挤窄后文字重新折行 */
  padding-right: 4px;
  /* 有滚动条时给一点视觉间隔，否则最后一排卡贴着滚动条 */
  padding-bottom: 2px;
}

/* ---------- v0.40.13：间距随视口自适应（用户第 2 条） ----------
 * 原话：「请求曲线卡片，号池走势卡片的间距自适应」。
 * 现状：`.mgrid` 固定 `gap:14px 18px`、`.mcard` 固定 `padding:14px 16px 16px` —— 
 * 宽屏下这些值是合适的，但到 1100 / 760 断点后列数已经塌成 2 列 / 1 列，
 * 间距却一点没变，于是"内容变窄、缝没变窄"，卡片之间显得格外空；
 * 手机档更是两边的 16px 内边距吃掉近 1/10 屏宽。
 * 按断点等比收紧，与已有的塌列规则（1100 / 760）对齐，避免"列变了缝没变"。 */
@media (max-width: 1100px) {
  .mgrid { gap: 12px 14px; }
  .kpis { gap: 10px; }
  .live-grid { gap: 12px; }
  .mcard { padding: 12px 14px 14px; margin-bottom: 12px; }
}
@media (max-width: 760px) {
  .mgrid { gap: 10px; }
  .kpis { gap: 8px; }
  .live-grid { gap: 10px; }
  .mcard { padding: 10px 12px 12px; margin-bottom: 10px; }
  .mtitle { margin-bottom: 8px; }
}

/* ===================================================================
   移动端（≤760）适配收口（v0.40.28）
   ---------------------------------------------------------------
   起因：390px 实测（tools/mobileaudit.mjs + tools/pageshots.mjs 存档）
   发现三类问题，都不是"感觉挤"，而是能量出来的：

   ① 长串把整列顶出屏幕。「定时任务」的排程小时数是 `0/1/2/…/23` 这种
      **没有断点**的串，min-content 就等于整串宽度 → 实测把 .pane 撑到 438px、
      整页横滚 62px。`overflow-wrap: anywhere` 才会影响 min-content
      （`break-word` 不会），所以必须用它，不是 break-all。
   ② 触控目标太小。账号池一屏量到 147 个可点元素 < 32px（chip 只有 24px 高），
      手指点不准 —— 这是"明明有按钮却点不到"的典型来源。
   ③ 内容卡在 390 下仍是两栏（用量分析 6 卡每张 175px），长模型名要折 5 行，
      数字和趋势图挤在一列里看不出可比性 → 这类"带长文案的卡"单列；
      纯数字的 KPI（.kpis 里的短数值）保持两栏，否则一屏只剩两个数字。
   =================================================================== */
/* 触控目标（≤860，与抽屉导航同一个断点）：平板也是手指操作。
   实测 768×1024 下用量分析每页还有 6~12 个按钮/下拉只有 28~31px 高 —— 手机上
   （≤760）改了，平板却漏了。单独成一个 media 块，是为了不和"布局塌列"共用阈值：
   760 决定几列，860 决定"手指还是鼠标"。 */
@media (max-width: 860px) {
  button, .chip, .icon-btn, .nav-item, .tabs button, label.switch,
  button.credit-more, button.seg, select { min-height: 36px; }
}

@media (max-width: 760px) {
  /* ① 无断点长串：排程小时列表（`0/1/2/…/23` 这种）、长 token、长模型名。
     排程那串实际渲染在 .chip 里（chipWithTitle），所以 .chip 也必须能断 —— 只给
     .run-card 加规则时实测仍横滚 45px（.row.tight 被 chip 的 min-content 撑到 435px）。 */
  .run-card, .run-card b, .run-card .pills, .run-card .pills b,
  .kpi-card .v, .ecard .sub, .muted.mono,
  .chip { overflow-wrap: anywhere; }
  .chip { max-width: 100%; }
  /* 栅格子项的 min-width 默认是 auto —— 不写这条，上面那条对整列宽度仍然无效 */
  #runCards > *, .cards > *, .kpis > *, .mgrid > * { min-width: 0; }

  /* ② 触控目标的 min-height 在 ≤860 那个块里统一给（平板也要能用手指点）。
     这里只补手机档特有的内边距：chip 24px 高在 390 下几乎点不准。 */
  .chip { padding: 7px 12px; }
  .nav-item { min-height: 40px; }
  /* .tag 是 nowrap 的（桌面上就该是一颗颗小药丸），但它也承载"自动排程 · 0/1/…/23 点"
     这种长串 —— 手机上必须允许折行，否则整列宽度由这串决定。 */
  .tag { white-space: normal; overflow-wrap: anywhere; max-width: 100%; }
  /* 手机档：用量分析的首屏几乎全是「说明 + 工具栏 + 筛选控件」，一条数据都看不到。
     实测 390px「请求明细」首屏 844px 里：页头 180px、二级页说明 4 行、筛选 6 行。
     两处收口：
     ① 页级说明最多两行（信息不删，改成截断，想看可横向选中）；
     ② 筛选控件的 inline min-width（110~200px）在手机上必须让位，改两列。 */
  /* 用 max-height 而不是 -webkit-line-clamp：实测 line-clamp 在这几个 .sub 上没生效
     （它们带 inline style，display 被后面的规则覆盖）。max-height 只按高度截，稳。 */
  .pane[data-pane="report"] > .sub { max-height: 3.4em; overflow: hidden; }
  .pane[data-pane="report"] .row > select,
  .pane[data-pane="report"] .row > input[type="date"],
  .pane[data-pane="report"] .row > input[type="number"] {
    flex: 1 1 calc(50% - 6px); min-width: 0 !important; max-width: none;
  }
  /* 页头里的跳转链接（代理页「↓ 跳到批量分配」）原本是 19px 高的内联文字，
     手机上点不中。给可点高度，视觉仍是文字。 */
  .section-head a { display: inline-flex; align-items: center; min-height: 32px; padding: 0 2px; }
  /* 句子里的内联链接（余额卡的「去充值 →」）：热区要够大，但不能把行撑开。
     用负 margin 抵掉 padding —— 视觉行高不变，可点高度从 19px 变 31px。 */
  #balanceLow a { display: inline-block; padding: 7px 4px; margin: -7px 0; }
  /* select 的宽度由**最长选项文本**决定（不是 CSS 宽度），320px 下
     「不静默换出口（默认）· 账号暂停服务，等人处理」这个选项把它撑到 334px，
     整页横滚 14px。max-width 截断显示，点开下拉仍能看全。 */
  select { max-width: 100%; min-width: 0; }

  /* ③ 带长文案的卡单列（纯数字 KPI 不动） */
  #uaCards { grid-template-columns: minmax(0, 1fr); }
}

/* 用量分析首页那 6 张摘要卡（#uaCards）单独定列宽（v0.40.33）。
   它挂在 .kpis 上，而 .kpis 是 minmax(148px,1fr) —— 1440 下摊成 6 列、每张 175px，
   长模型名「gemini-2.5-pro-preview-06-05-very-long-model-identifier-0123456789」要折四行，
   卡片里那行 "Top: …" 直接被挤到贴边。摘要卡的正文比概览 KPI 长得多，最小宽不同：
   320px 起（1440 三列 / 平板两列 / 手机一列）。 */
#uaCards { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }

/* ≤430（主流手机竖屏）：只收紧外边距，**不让 .kpis 变单列** ——
   概览的 KPI 数值都很短（"0"、"13 / 13"），两栏更紧凑；单列会把首屏撑到 4 屏高，
   那正是之前被投诉的"留空窗太多/占屏"。真需要单列的是带长文案的卡（见上面 #uaCards）。 */
@media (max-width: 430px) {
  .main { padding: 14px 12px 44px; }
  .kpi-card .v { font-size: 19px; }
}



/* ===================================================================
   公告配图（v0.40.59）
   卡片限高 360px + 底部渐隐；弹窗与全屏预览按原始比例完整展示。
   移动端一律 width:100% + max-width:100%，不产生横向溢出。
   =================================================================== */
.ann-imgs { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.ann-img-box {
  position: relative; display: block; width: 100%; max-width: 100%;
  padding: 0; border: 0; background: transparent;
  border-radius: 8px; overflow: hidden; cursor: zoom-in;
}
.ann-img-box img { display: block; width: 100%; height: auto; max-width: 100%; }
.ann-imgs.clip .ann-img-box { max-height: 360px; }
.ann-imgs.clip .ann-img-box img { max-height: 360px; object-fit: cover; object-position: top; }
.ann-imgs.clip .ann-img-box::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 64px;
  background: linear-gradient(to bottom, transparent, var(--card));
  pointer-events: none;
}

.ann-lightbox {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .88); padding: 24px;
}
.ann-lightbox img { max-width: 92vw; max-height: 86vh; object-fit: contain; border-radius: 6px; }
.ann-lightbox-cap {
  position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
  color: #fff; font-size: 13px; opacity: .8;
}
.ann-lightbox-close {
  position: absolute; top: 14px; right: 16px; width: 40px; height: 40px;
  border-radius: 50%; border: 0; background: rgba(255, 255, 255, .16);
  color: #fff; font-size: 22px; line-height: 1; cursor: pointer;
}
.ann-lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 50%; border: 0;
  background: rgba(255, 255, 255, .16); color: #fff; font-size: 26px; line-height: 1; cursor: pointer;
}
.ann-lightbox-nav.prev { left: 16px; }
.ann-lightbox-nav.next { right: 16px; }
.ann-lightbox-close:focus-visible, .ann-lightbox-nav:focus-visible {
  outline: 2px solid #fff; outline-offset: 2px;
}

/* 管理端：配图编辑区 */
.ann-img-edit { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.ann-img-item {
  position: relative; width: 120px; max-width: 100%;
  border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
.ann-img-item img { display: block; width: 100%; height: 80px; object-fit: cover; }
.ann-img-idx {
  position: absolute; top: 4px; left: 4px;
  background: rgba(0, 0, 0, .55); color: #fff;
  font-size: 11px; padding: 1px 5px; border-radius: 4px;
}
.ann-img-ops { display: flex; gap: 4px; padding: 4px; justify-content: center; }
.ann-img-ops button { min-width: 32px; min-height: 32px; padding: 0 6px; }

@media (max-width: 860px) {
  /* 手指操作：操作按钮不小于 32px（与全站触控规则一致）。 */
  .ann-img-ops button { min-width: 36px; min-height: 36px; }
  .ann-img-item { width: calc(50% - 5px); }
}
