/* ═══════════════════════════════════════════════════════════════════════
   Task Console v2 — 「仪表，不是仪表盘模板」

   设计权威：artifacts/console-redesign-v1.html（已审核通过）。
   刻意提交单一深色世界：这是长时间盯着看的运维仪表，不是营销页。
   颜色是证据，不是装饰：一个交互色（信号琥珀），三个语义色（玉/砖/岩灰），
   互不越界。「不知道」是一等状态，用空心点 + 斜纹 + 只描边表达，
   形态上就不可能被误读成一次测量。
   ═══════════════════════════════════════════════════════════════════════ */

/* ══ 字体：自托管 latin 子集 ═════════════════════════════════════════
   设计稿 §04 定了三个字体角色，但构建里只写了字族名——既没有 @font-face 也没有
   <link>，而大多数机器都没装 Barlow / Plex Mono。于是生产上一直跑的是回落栈
   （Segoe UI + 微软雅黑 + Consolas），设计稿的字体一条都没生效。这是「看每一屏都
   差一点」的最大单一原因：差的不是某个面板，是所有数字和标识符。

   自托管而不是 <link> Google Fonts：生产在 142、访问者在国内，而且一个带登录态的
   运维页面每次加载都往第三方发请求不合适——设计稿 §09 处理厂商图标用的正是这个
   理由。字体太大不适合内联成 data URI，所以做成同源文件。

   只取 latin 子集（9 个字面共 175 KB）：中文本来就由系统字体承载——Barlow 没有 CJK
   字形，浏览器会继续回落到微软雅黑 / PingFang，这是设计稿的原意。latin 覆盖的是
   全部数字、邮箱、令牌、ID、eyebrow 与英文标签，也正是这次要修的部分。

   font-display: swap 配合 index.html 里四个最常用字面的 preload：同源小文件通常在
   首帧前就到位；真没到位时先显示回落字形，而不是像 block 那样让读数空一段。 */
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./fonts/barlow-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('./fonts/barlow-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('./fonts/barlow-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('./fonts/barlow-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('./fonts/barlow-condensed-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('./fonts/barlow-condensed-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./fonts/ibm-plex-mono-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('./fonts/ibm-plex-mono-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('./fonts/ibm-plex-mono-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 深色是这套设计的本体，定义在裸 :root 上。主题切换仍可用（见文末 light 覆盖），
   但 light 只是功能性映射，dark 才是设计。 */
:root {
  /* 地：石墨带极轻的绿偏，像阴影里的阳极氧化铝。刻意避开 SaaS 深蓝。 */
  --ground:      #0C100F;
  --panel:       #141918;
  --raised:      #1C2220;
  --sunk:        #080B0A;

  /* 唯一交互强调色：信号琥珀。按钮、焦点、选中、链接。绿/红全留给语义。 */
  --signal:      #F0A63C;
  --signal-hi:   #FFB84D;
  --signal-dim:  #A9742A;
  --signal-wash: rgba(240, 166, 60, .10);

  /* 语义三态 —— 整套设计的骨架 */
  --jade:        #4FB286;   /* 已确认可用 */
  --jade-wash:   rgba(79, 178, 134, .12);
  --brick:       #E0604E;   /* 已确认失败 */
  --brick-wash:  rgba(224, 96, 78, .12);
  --slate:       #71838F;   /* 未知 / 未测 —— 刻意去饱和 */
  --slate-wash:  rgba(113, 131, 143, .10);

  --fg:          #E8EDEB;
  --fg-dim:      #93A09C;
  /* 暗色下 #5E6B67 对面板只有 3.19:1；#7C8A85 是 4.9:1。 */
  --fg-faint:    #7C8A85;
  --on-signal:   #171104;   /* 琥珀实心块上的字 */
  --on-brick:    #2A0E09;   /* 砖红实心块上的字 */

  /* 设计稿说「靠细线区分层级，不靠阴影」——成立的前提是线看得见。α=.09 时暗色下面板
     边界几乎不存在（面板对底只有 1.08:1，全靠这根线撑）。 */
  --line:        rgba(232, 237, 235, .13);
  --line-strong: rgba(232, 237, 235, .26);

  --r:           2px;   /* 方角。圆角是 SaaS 语汇，仪表不用。 */
  --ease:        cubic-bezier(.2, .7, .3, 1);
  --poll:        15s;   /* 状态条扫光周期。main.js 可用配置的轮询周期覆写它。 */

  --font-ui:     Barlow, ui-sans-serif, system-ui, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  --font-num:    "Barlow Condensed", Barlow, ui-sans-serif, system-ui, sans-serif;
  --font-mono:   "IBM Plex Mono", ui-monospace, "Cascadia Code", Consolas, monospace;

  /* 字号阶梯。实测原来有 22 个不同字面值、最小 8.5px（这台屏 DPR=1，就是 8.5 个物理
     像素）——设计稿定了三个字体角色却没定阶梯，于是每个组件各取所需。现在收成 9 档，
     每个字面值就近归并：
       t0 脚注 / 日期 / eyebrow / 表头     t1 等宽标识 / 分页
       t2 表格正文 / 行内说明              t3 界面正文 / 导航
       t4 面板标题                         t5 小节标题 / 对话框标题
       t6 段落大数                         t7 概览读数
       t8 产能结论（全站唯一一处，见下）
     8.5 与 9.5 直接删掉：eyebrow、表头、活动图日期一律上到 t0。
     t8 保留 40px 不并入 t7：产能面板那个「今晚至多 N」是整页唯一的结论数，它比四个
     读数大一档是刻意的层级。审查稿里列的是 8 档，那份清单漏了它——并进去等于让页面
     失去唯一的最大数，所以这里留成 9 档。 */
  --t0: 10.5px;  --t1: 11.5px;  --t2: 12.5px;  --t3: 13.5px;
  --t4: 15px;    --t5: 18px;    --t6: 22px;    --t7: 32px;   --t8: 40px;

  /* 兼容旧动态类名的语义别名（main.js 仍会发这些类，接入组件层前先让它们视觉正确） */
  --amber:       var(--signal);
  --red:         var(--brick);
  --green:       var(--jade);
}

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--fg);
  font-family: var(--font-ui);
  font-size: var(--t3);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.01em; }
a { color: var(--signal); text-decoration: none; }
a:hover { text-decoration: underline; }
[hidden] { display: none !important; }

/* 凡是可以被复制粘贴的内容都用等宽：邮箱、令牌、ID、时间、数量。 */
.mono, .cell-mail, .cell-num { font-family: var(--font-mono); }
/* 等宽数字：刷新只换数字时不重排宽度，页面不跳。 */
.num, .tnum { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.muted { color: var(--fg-dim); }
.eyebrow {
  font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .2em;
  text-transform: uppercase; color: var(--fg-faint); display: block;
}

/* 滚动条：细、去饱和，别抢注意力 */
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 0; }
*::-webkit-scrollbar-track { background: transparent; }

/* 阶梯的兜底。UA 样式表自带两个不在阶梯上的字号，靠 grep 字面量的守卫抓不到：
   <small> 是 0.8333em——`.status-strip small` 只改颜色，10.5px 一缩就成 8.75px，
   于是全站最小的字反而是状态条上的时钟；表单按钮默认 13.3333px，
   .icon-button 没写字号就落到那里。阶梯之外的字号一律继承，要小就显式写令牌。 */
small { font-size: inherit; }
button, input, select, textarea { font-family: inherit; font-size: inherit; }

/* ══════════════════════════════════════════════════════════════════════
   按钮
   ══════════════════════════════════════════════════════════════════════ */
.btn, .primary-button, .secondary-button {
  font-family: var(--font-ui); font-size: var(--t3); font-weight: 600;
  padding: 8px 15px; border-radius: var(--r); cursor: pointer;
  border: 1px solid transparent; transition: background .16s var(--ease), border-color .16s var(--ease), transform .16s var(--ease), color .16s var(--ease);
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  line-height: 1.2; white-space: nowrap;
}
.btn-primary, .primary-button { background: var(--signal); color: var(--on-signal); border-color: var(--signal); }
.btn-primary:hover, .primary-button:hover { background: var(--signal-hi); border-color: var(--signal-hi); }
.btn-ghost, .secondary-button { background: transparent; color: var(--fg); border-color: var(--line-strong); }
.btn-ghost:hover, .secondary-button:hover { background: var(--raised); border-color: var(--fg-faint); }
.btn-danger { background: transparent; color: var(--brick); border-color: rgba(224, 96, 78, .4); }
.btn-danger:hover { background: var(--brick-wash); }
.btn:disabled, .btn[aria-disabled="true"],
.primary-button:disabled, .secondary-button:disabled {
  opacity: .38; cursor: not-allowed; transform: none;
}
.btn:focus-visible, .primary-button:focus-visible, .secondary-button:focus-visible,
.text-button:focus-visible, .icon-button:focus-visible, .nav-item:focus-visible {
  outline: 2px solid var(--signal); outline-offset: 2px;
}
/* 行内文字按钮：唯一交互色 */
/* 行内中性动作：正文色 + 悬停下划线。**不是琥珀**——琥珀只给每屏唯一的主动作、
   焦点环和选中态（拍板 D5 给稿 §03「唯一交互色」补的例外）。原来「详情」
   「全选匹配」「移除」「新建任务」全是同一个琥珀，破坏性动作和主动作长得一样。 */
.text-button {
  background: none; border: none; padding: 2px 2px; margin: 0;
  color: var(--fg); font-family: var(--font-ui); font-size: var(--t2); font-weight: 600;
  cursor: pointer; border-radius: var(--r);
}
.text-button:hover { color: var(--fg); text-decoration: underline; }
/* 强调型行内动作（每屏至多一个，例如「全部任务 →」）才用琥珀。 */
.text-button.accent { color: var(--signal-dim); }
.text-button.accent:hover { color: var(--signal-hi); text-decoration: underline; }
.text-button:disabled { color: var(--fg-faint); cursor: not-allowed; text-decoration: none; }
.icon-button {
  background: transparent; border: 1px solid transparent; color: var(--fg-dim);
  font-size: var(--t3);
  width: 30px; height: 30px; min-width: 30px; padding: 0; border-radius: var(--r);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: background .14s var(--ease), color .14s var(--ease), border-color .14s var(--ease);
}
.icon-button:hover { background: var(--raised); color: var(--fg); border-color: var(--line-strong); }

/* ══════════════════════════════════════════════════════════════════════
   输入 —— 下沉底 + 细线，焦点用信号色描边 + 光晕（不位移）
   这里的 :not([type=...]) 链是为了让 checkbox/radio 不吃这套盒模型，不是为了
   打特异性。曾经有一版把装饰图标塞进输入框内部，覆盖规则得复刻同一条链才压得住
   base（0,2,1 vs 0,1,1），文字否则压在图标下；登录页重写把图标去掉之后那套覆盖
   一并删了。input-padding.test.mjs 守着它别回来。

   排除 checkbox/radio 用 :where() 包住。裸写 :not():not() 会把这条基础规则的
   特异性抬到 (0,3,1)，于是任何想给某类输入框定宽的类选择器（.filter-input 是
   (0,1,0)）都压不住它——筛选条那五个 320px 的框实测仍是 1,332px，就是这么来的。
   :where() 不贡献特异性，规则回到 (0,0,1)，排除效果不变。
   ══════════════════════════════════════════════════════════════════════ */
input:where(:not([type="checkbox"]):not([type="radio"])),
select,
textarea {
  width: 100%;
  height: 35px;
  border-radius: var(--r);
  border: 1px solid var(--line-strong);
  background: var(--sunk);
  color: var(--fg);
  padding: 0 11px;
  outline: none;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
  font-family: var(--font-ui);
  font-size: var(--t3);
}
textarea { height: auto; padding: 9px 11px; line-height: 1.5; }
input:where(:not([type="checkbox"]):not([type="radio"])):focus,
select:focus,
textarea:focus {
  border-color: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-wash);
}
input::placeholder, textarea::placeholder { color: var(--fg-faint); }
select {
  appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-faint) 50%), linear-gradient(135deg, var(--fg-faint) 50%, transparent 50%);
  background-position: calc(100% - 15px) 15px, calc(100% - 10px) 15px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
}
.inp.mono, input.mono { font-family: var(--font-mono); font-size: var(--t2); }
input[type="checkbox"], input[type="radio"] { width: 14px; height: 14px; accent-color: var(--signal); cursor: pointer; }

.field { display: flex; flex-direction: column; gap: 6px; }
.field-label, .setting-field > span { font-size: var(--t2); color: var(--fg-dim); }
.field-label-row { display: flex; align-items: center; justify-content: space-between; }

/* ══════════════════════════════════════════════════════════════════════
   三态标记 —— 设计的签名动作
   实心点 = 已确认；空心点 + 斜纹 + 只描边 = 未知。
   收到 false 与收到 null 必须渲染成形态不同的东西。
   ══════════════════════════════════════════════════════════════════════ */
.state {
  font-family: var(--font-mono); font-size: var(--t1); font-weight: 500;
  padding: 3px 8px; border-radius: var(--r);
  display: inline-flex; align-items: center; gap: 6px;
  letter-spacing: .02em; white-space: nowrap; line-height: 1.3;
}
.state::before { content: ""; width: 6px; height: 6px; border-radius: 50%; flex: none; }
.state-ok   { color: var(--jade);  background: var(--jade-wash); }
.state-ok::before { background: var(--jade); }
.state-bad  { color: var(--brick); background: var(--brick-wash); }
.state-bad::before { background: var(--brick); }
/* 未知：只有描边、点空心、底纹斜线 —— 从形态上就不可能被误读成一次测量。 */
.state-unknown {
  color: var(--slate);
  background: repeating-linear-gradient(135deg, var(--slate-wash) 0 4px, transparent 4px 8px);
  box-shadow: inset 0 0 0 1px rgba(113, 131, 143, .35);
}
.state-unknown::before { background: transparent; box-shadow: inset 0 0 0 1.5px var(--slate); }
/* 吊销：我们失去了访问权，但账号可能还活着 —— 砖红描边 + 空心点，不是判死。 */
.state-revoked {
  color: var(--brick);
  background: transparent;
  box-shadow: inset 0 0 0 1px rgba(224, 96, 78, .45);
}
.state-revoked::before { background: transparent; box-shadow: inset 0 0 0 1.5px var(--brick); }
/* 封停：唯一的权威判死。实心块，不留余地。 */
.state-banned { color: var(--on-brick); background: var(--brick); font-weight: 600; }
.state-banned::before { background: var(--on-brick); }
.state-run { color: var(--signal); background: var(--signal-wash); }
.state-run::before { background: var(--signal); animation: pulse 1.6s var(--ease) infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.8); } }

/* 判定 + 来源：结论旁标出凭什么得出。弱证据加下划虚线。 */
.verdict-cell { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.src { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); letter-spacing: .04em; }
.src.weak { border-bottom: 1px dashed var(--line-strong); }

/* ── 旧动态类名 → 三态形态映射（接入组件层前的安全网） ────────────────
   main.js 目前仍发 .status-badge / .alive-badge / .credential-badge /
   .eligibility-badge。让它们此刻就用正确的三态形态渲染，
   于是即便还没接组件层，7,543 个「未检测」也不会被涂成红色「失效」。 */
.status-badge, .alive-badge, .credential-badge {
  font-family: var(--font-mono); font-size: var(--t1); font-weight: 500;
  padding: 3px 8px; border-radius: var(--r);
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; line-height: 1.3;
}
.status-badge::before, .alive-badge::before, .credential-badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; flex: none;
}
.status-badge.success, .alive-badge.alive, .credential-badge.complete { color: var(--jade); background: var(--jade-wash); }
.status-badge.success::before, .alive-badge.alive::before, .credential-badge.complete::before { background: var(--jade); }
.status-badge.failed, .alive-badge.dead, .credential-badge.missing { color: var(--brick); background: var(--brick-wash); }
.status-badge.failed::before, .alive-badge.dead::before, .credential-badge.missing::before { background: var(--brick); }
.status-badge.running { color: var(--signal); background: var(--signal-wash); }
.status-badge.running::before { background: var(--signal); animation: pulse 1.6s var(--ease) infinite; }
.status-badge.warn { color: var(--signal); background: var(--signal-wash); }
.status-badge.warn::before { background: var(--signal); }
/* 中性 / 未知：空心 + 斜纹，绝不复用红或绿 */
.status-badge.neutral, .alive-badge.unknown, .credential-badge.unknown {
  color: var(--slate);
  background: repeating-linear-gradient(135deg, var(--slate-wash) 0 4px, transparent 4px 8px);
  box-shadow: inset 0 0 0 1px rgba(113, 131, 143, .35);
}
.status-badge.neutral::before, .alive-badge.unknown::before, .credential-badge.unknown::before {
  background: transparent; box-shadow: inset 0 0 0 1.5px var(--slate);
}

/* 类型 / 出口 / 套餐标签 */
.type-badge, .tag-kind, .country-badge {
  font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-dim);
  border: 1px solid var(--line-strong); padding: 1px 7px; border-radius: var(--r); white-space: nowrap;
}
.country-cell { white-space: nowrap; }
/* 任务表的类型标签允许折行。生产的真实标签是「更换全套凭据（密码/动态口令）」
   （实测需 198px），而列宽 124px —— badge 默认 nowrap，于是被截掉一半，
   而类型正是识别一条任务的关键信息。折行比截断好：行高变一点，信息不丢。
   （stub 里我用的是「更换全套凭据」6 个字，短了，所以本地量不出来。） */
.jobs-table .type-badge { white-space: normal; }
.dash { color: var(--fg-faint); }

/* 优惠资格 —— 三态：命中上色，未检测斜纹，已测无命中去饱和 */
.eligibility-cell { min-width: 0; }
.eligibility-badges { display: inline-flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.eligibility-badge {
  font-family: var(--font-mono); font-size: var(--t0); padding: 1px 6px; border-radius: var(--r);
  color: var(--jade); border: 1px solid var(--jade); background: var(--jade-wash);
}
.eligibility-badges small { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.eligibility-badges .muted, .eligibility-cell .muted {
  font-family: var(--font-mono); font-size: var(--t1); color: var(--slate);
  background: repeating-linear-gradient(135deg, var(--slate-wash) 0 4px, transparent 4px 8px);
  box-shadow: inset 0 0 0 1px rgba(113, 131, 143, .35);
  padding: 3px 8px; border-radius: var(--r);
}

/* 套餐单元 —— 带时间戳的测量（值 + 多久之前；AT 过期时「多久之前」套斜纹） */
.plan { display: inline-flex; align-items: baseline; gap: 7px; white-space: nowrap; }
.plan .v { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg); }
.plan .v.paid { color: var(--jade); }
.plan .v.none { color: var(--fg-faint); }
.plan .asof { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); white-space: nowrap; }
.plan .asof.stale {
  color: var(--slate); padding: 0 4px; border-radius: var(--r);
  background: repeating-linear-gradient(135deg, var(--slate-wash) 0 4px, transparent 4px 8px);
  box-shadow: inset 0 0 0 1px rgba(113, 131, 143, .3);
}

/* ══════════════════════════════════════════════════════════════════════
   登录 —— 仪表风：细线、方角、无发光渐变
   ══════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════
   登录 —— 仪器前面板
   门面按这套设计已确立的隐喻做足：阳极氧化铝的前面板，而不是一张卡片。
   底纹用产品自己的图形母题（单元格阵列 = 一格一次运行），四角刻定位标，
   面板上沿走一道 hairline 高光。一个交互色只出现在焦点环与主按钮两处。
   ══════════════════════════════════════════════════════════════════════ */
.login-screen {
  position: relative;
  min-height: 100vh; min-height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: 32px 24px;
  background: var(--ground);
  overflow: hidden;
}

/* 单元格底纹。两层 repeating-gradient 交叠出错行的小方块阵列，像打孔卡走纸；
   极低对比度，只在余光里成立。四周用 mask 淡出，避免出现一条生硬的裁切边。 */
.login-field {
  position: absolute; inset: -10%;
  /* 格距 30×26、笔画 8×5：格子要大到一眼能认出是「单元格」这个母题，
     而不是一片细密的方格纸。上一版是 13×12 的格距，密到成了噪点，盯着难受。 */
  background:
    repeating-linear-gradient(90deg, var(--line) 0 8px, transparent 8px 30px),
    repeating-linear-gradient(0deg, var(--line) 0 5px, transparent 5px 26px);
  opacity: .34;
  -webkit-mask-image: radial-gradient(115% 90% at 50% 45%, #000 30%, transparent 78%);
  mask-image: radial-gradient(115% 90% at 50% 45%, #000 30%, transparent 78%);
  pointer-events: none;
}

.login-panel {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  width: 100%; max-width: 1040px;
  border: 1px solid var(--line-strong);
  background: var(--panel);
  /* 面板浮在底纹之上，靠一层深阴影拉开而不是靠圆角。 */
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .55);
}
/* 上沿 hairline 高光：金属面板受光的那道边。仅此一处光效。 */
.login-panel::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong) 18%, var(--line-strong) 82%, transparent);
}
/* 四角定位标（registration marks）—— 仪器与制版的语汇，不是装饰边框。 */
.login-panel::after {
  content: ""; position: absolute; inset: 9px; pointer-events: none;
  background:
    linear-gradient(var(--line-strong), var(--line-strong)) left top / 9px 1px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) left top / 1px 9px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) right top / 9px 1px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) right top / 1px 9px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) left bottom / 9px 1px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) left bottom / 1px 9px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) right bottom / 9px 1px no-repeat,
    linear-gradient(var(--line-strong), var(--line-strong)) right bottom / 1px 9px no-repeat;
}

/* ── 左栏：铭牌 ────────────────────────────────────────────────────── */
.login-plate {
  display: flex; flex-direction: column; gap: 26px;
  padding: 30px 30px 26px;
  border-right: 1px solid var(--line);
  background: var(--sunk);
}
.plate-top { display: flex; align-items: flex-start; justify-content: space-between; }
.brand-mark {
  width: 32px; height: 32px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: var(--t2); font-weight: 600;
  color: var(--signal); background: var(--signal-wash);
  border: 1px solid var(--signal-dim);
}
.brand-mark.small { width: 26px; height: 26px; font-size: var(--t1); }

.plate-title { margin-top: auto; }
/* 压缩字重排大，字距收紧：铭牌上刻的字，不是营销标题。 */
.plate-title h1 {
  font-family: var(--font-num);
  font-size: clamp(38px, 5.4vw, 58px);
  line-height: .92;
  font-weight: 600;
  letter-spacing: -.02em;
  color: var(--fg);
  margin: 0;
}
.plate-sub {
  margin: 12px 0 0;
  font-size: var(--t2); color: var(--fg-dim); max-width: 30ch;
}

/* 规格表：像仪器背面那块铭牌。四项都能核对，没有一项是断言。 */
.plate-spec {
  display: grid; gap: 0; margin: 0;
  border-top: 1px solid var(--line);
  font-family: var(--font-mono); font-size: var(--t1);
}
.plate-spec > div {
  display: grid; grid-template-columns: 74px minmax(0, 1fr);
  gap: 10px; align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.plate-spec dt { color: var(--fg-faint); letter-spacing: .06em; }
.plate-spec dd { margin: 0; color: var(--fg-dim); overflow-wrap: anywhere; }

/* 服务指示灯：三态，探到什么显示什么，绝不默认亮绿。 */
.lamp { display: inline-flex; align-items: center; gap: 7px; }
.lamp > i { width: 7px; height: 7px; flex: none; border-radius: 50%; }
.lamp-ok > i { background: var(--jade); box-shadow: 0 0 0 3px var(--jade-wash); }
.lamp-ok > span { color: var(--jade); }
.lamp-down > i { background: var(--brick); box-shadow: 0 0 0 3px var(--brick-wash); }
.lamp-down > span { color: var(--brick); }
/* 未知是空心 + 描边：形态上就不可能被误读成一次测量。 */
.lamp-probing > i { background: transparent; border: 1px solid var(--slate); }
.lamp-probing > span { color: var(--slate); }

/* ── 右栏：认证 ────────────────────────────────────────────────────── */
.login-auth { display: flex; align-items: center; padding: 30px 30px 26px; }
.login-card { display: flex; flex-direction: column; gap: 15px; width: 100%; }
.login-header { display: flex; flex-direction: column; gap: 5px; margin-bottom: 3px; }
.login-header h2 { font-size: var(--t5); font-weight: 600; }
/* 「显示」是文字按钮而不是眼睛图标：图标要塞进输入框内部就得打一场特异性
   官司（见本文件输入那一节的注释），而且图标式的密码开关永远有"它表示当前
   状态还是表示点了会变成什么"的歧义。 */
.reveal-toggle { font-family: var(--font-mono); font-size: var(--t1); }
.login-submit { width: 100%; height: 38px; margin-top: 3px; }
.login-note {
  margin: 2px 0 0; font-family: var(--font-mono); font-size: var(--t0);
  color: var(--fg-faint); line-height: 1.5;
}

@media (max-width: 720px) {
  .login-panel { grid-template-columns: minmax(0, 1fr); max-width: 420px; }
  .login-plate { border-right: 0; border-bottom: 1px solid var(--line); gap: 20px; }
  .plate-title h1 { font-size: var(--t8); }
  .plate-sub { max-width: none; }
}
.meta-item { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--jade); flex: none; }
.status-dot.pulsing { animation: pulse 1.8s var(--ease) infinite; }
/* 侧栏那盏灯的另外两态。红点不加脉冲是有意的：还在动的红点会被读成
   「正在恢复中」，而它的意思恰恰相反。未知态用去饱和的 slate，和账号池
   「未测」同一套色，不借绿也不借红。 */
.status-dot.dot-bad { background: var(--brick); animation: none; }
.status-dot.dot-unknown { background: transparent; border: 1px solid var(--slate); animation: none; }
.service-status[data-state="down"] .service-name { color: var(--brick); }
.service-status[data-state="probing"] .service-name { color: var(--fg-faint); }

/* ══════════════════════════════════════════════════════════════════════
   App 外壳 —— 一个页面只有一条滚动
   外壳吃满视口、overflow:hidden；.content-scroll 是唯一滚动容器；
   表格自然流动、表头吸顶，绝不再给它套第二个带 max-height 的滚动框。
   ══════════════════════════════════════════════════════════════════════ */
.app-shell {
  height: 100vh; height: 100dvh;
  overflow: hidden;
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  background: var(--ground);
}

/* ── 侧轨导航 ──────────────────────────────────────────────────────── */
.sidebar {
  background: var(--sunk); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; height: 100vh; height: 100dvh;
  min-height: 0; overflow-y: auto; z-index: 40;
}
.sidebar-header { padding: 16px 16px 12px; border-bottom: 1px solid var(--line); }
.sidebar-brand { display: flex; align-items: center; gap: 10px; }
.brand-info { display: flex; flex-direction: column; line-height: 1.25; }
.brand-info strong { font-size: var(--t3); font-weight: 600; }

.nav-groups { flex: 1; padding: 8px 0 12px; display: flex; flex-direction: column; }
.nav-caption {
  padding: 14px 16px 5px; font-family: var(--font-mono); font-size: var(--t0);
  letter-spacing: .18em; text-transform: uppercase; color: var(--fg-faint);
}
.nav-item {
  display: flex; align-items: center; gap: 10px; margin: 1px 6px;
  padding: 8px 10px; border-radius: var(--r); color: var(--fg-dim);
  font-size: var(--t3); cursor: pointer; position: relative; text-align: left;
  background: none; border: none; width: calc(100% - 12px); font-family: var(--font-ui);
  transition: background .14s var(--ease), color .14s var(--ease);
}
.nav-item b { font-weight: 500; }
.nav-icon { font-family: var(--font-mono); font-size: var(--t2); width: 14px; text-align: center; opacity: .75; flex: none; }
.nav-item:hover { background: var(--panel); color: var(--fg); }
.nav-item.active { background: var(--signal-wash); color: var(--fg); }
.nav-item.active b { font-weight: 600; }
/* 选中态用左侧信号条，而不是整块染色 */
.nav-item.active::before { content: ""; position: absolute; left: -6px; top: 6px; bottom: 6px; width: 2px; background: var(--signal); }
.nav-counter {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--t0); font-style: normal;
  color: var(--fg-faint); background: var(--panel); padding: 1px 6px; border-radius: var(--r);
}
.sidebar-bottom { border-top: 1px solid var(--line); padding: 12px 12px; display: flex; flex-direction: column; gap: 10px; }
.service-status { display: flex; align-items: center; gap: 8px; font-size: var(--t2); color: var(--fg-dim); }
.service-status small { margin-left: auto; font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.sidebar-user { display: flex; align-items: center; gap: 9px; }
.user-avatar {
  width: 28px; height: 28px; border: 1px solid var(--line-strong); color: var(--fg-dim);
  display: grid; place-items: center; font-size: var(--t3); flex: none; font-weight: 600;
}
.user-info { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.user-info b { font-size: var(--t2); font-weight: 600; }
.user-info small { font-size: var(--t0); color: var(--fg-faint); }
.logout-btn { margin-left: auto; }

/* ── 主区 + 常驻状态条 ───────────────────────────────────────────────
   三个永不移动的事实 + 轮询指示。「刚刷新过」由一道 1px 扫光表达，页面不动。 */
.main-shell { display: flex; flex-direction: column; min-width: 0; height: 100vh; height: 100dvh; overflow: hidden; }
.topbar {
  display: flex; align-items: center; gap: 16px;
  padding: 0 20px; min-height: 48px; height: 48px;
  border-bottom: 1px solid var(--line); background: var(--panel);
  position: relative; overflow: hidden; flex: none;
}
/* 取数成功时一道 1px 扫光。**不是节拍器**：原来是
   animation: sweep var(--poll) infinite，按轮询周期无限循环，和取数是否真的
   发生、是否成功毫无关系——后端断了它照样每 15 秒扫一次。稿 §07 专门批过这种
   「看起来像状态、其实是装饰」的元件。现在由 loadOverview 的成功回调加
   .did-sync 触发一次，失败不扫。 */
.topbar::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: linear-gradient(90deg, transparent, var(--signal), transparent);
  transform: translateX(-100%);
}
.topbar.did-sync::after { animation: sweep 700ms var(--ease) 1; }
@keyframes sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .topbar.did-sync::after { animation: none; } }
/* 上次取数失败：状态条第一段变砖红，说清是「同步失败」而不是服务挂了。 */
.status-strip .seg.sync-bad { color: var(--brick); }
.status-strip .seg.sync-bad .status-dot { background: var(--brick); animation: none; }
.topbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-title { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.breadcrumb { font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .08em; color: var(--fg-faint); text-transform: uppercase; }
.breadcrumb-separator { opacity: .5; margin: 0 2px; }
#page-title { font-size: var(--t4); font-weight: 600; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.api-state-pill {
  display: inline-flex; align-items: center; gap: 7px; padding: 5px 11px;
  border: 1px solid var(--line); background: var(--sunk); border-radius: var(--r);
  font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-dim);
}
.api-state-pill .state-divider { color: var(--fg-faint); }
.api-state-pill small { color: var(--fg-faint); }

/* 状态条 —— 四段真值，分隔用竖线不用色块。
   .num 只在有失败时才染砖红（由 main.js 加 bad 类），因为 0 次失败是好消息，
   把它也染成红色会让人天天看到一个红数字，然后学会忽略它。 */
.status-strip {
  display: flex; align-items: center; gap: 0;
  height: 30px; padding: 0 2px;
  border: 1px solid var(--line); background: var(--sunk);
  font-size: var(--t2); color: var(--fg-dim);
}
.status-strip .seg {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0 11px; height: 100%;
  border-right: 1px solid var(--line);
}
.status-strip .seg:last-child { border-right: 0; }
.status-strip .seg.mono { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.status-strip .k { font-style: normal; color: var(--fg-faint); font-size: var(--t1); }
.status-strip b { font-family: var(--font-num); font-size: var(--t3); font-weight: 600; color: var(--fg); }
.status-strip b.bad { color: var(--brick); }
.status-strip small { color: var(--fg-faint); }
.refresh-btn { height: 30px; padding: 0 12px; font-size: var(--t2); }
.menu-toggle { display: none; }

/* ── 内容区（唯一滚动容器） ─────────────────────────────────────────── */
.content-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: 22px 24px 40px;
  width: 100%;
}
.page-view {
  display: none; flex-direction: column; gap: 18px;
  /* 内容吃满可用宽度，只在超宽屏收口并居中。
     原来是 max-width:1440px 且左对齐：2560px 屏上内容区有 2340px，页面只画到
     1440px，右侧空掉 876px——那不是留白，是看起来只渲染了一半。收口值给到
     2200px，是为了在 3440px 这类超宽屏上不让 11 列的表格横跨到读不完。 */
  width: 100%;
  max-width: 2200px;
  margin-inline: auto;
}
.page-view.active { display: flex; }

/* 列表页：页头 / 筛选 / 批量条 / 分页钉住，只有表格体滚。
   —— 换页和批量操作永远点得到，不用先滚回顶部。

   链条上每一层都要 min-height: 0，否则 flex 子项的默认 min-height:auto 会让
   它撑到内容高度，滚动条又跑回外层去。高度全部由 flex 从真实剩余空间算出，
   没有任何 max-height 魔数（设计稿第 03 条点名的那种）。

   总览、设置、文件与产物三页不带 list-view：它们是卡片流不是数据表，
   仍由 .content-scroll 整体滚。 */
.page-view.list-view { height: 100%; min-height: 0; }
.page-view.list-view > .page-toolbar,
.page-view.list-view > .filter-bar { flex: none; }
.page-view.list-view > .table-panel {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
}
.page-view.list-view .bulk-bar,
.page-view.list-view .pagination { flex: none; }
.page-view.list-view .split-workspace { flex: 1; min-height: 0; }
.page-view.list-view .table-scroll { min-height: 0; overflow-y: auto; }
/* .split-workspace 是 grid，网格项同样要显式允许收缩，否则表格把行高撑满。 */
.page-view.list-view .split-workspace > * { min-height: 0; }

.page-toolbar { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.page-toolbar h2 { font-size: var(--t5); margin-top: 2px; }
.page-toolbar .muted { font-size: var(--t3); margin: 3px 0 0; }
.toolbar-actions, .hero-actions { display: flex; gap: 9px; flex-wrap: wrap; }
.hero-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.hero-row h2 { font-size: var(--t6); margin-top: 3px; }
.hero-row .muted { font-size: var(--t3); margin: 4px 0 0; }

/* ══════════════════════════════════════════════════════════════════════
   读数 —— 固定宽高槽位，刷新只换文本不换尺寸
   ══════════════════════════════════════════════════════════════════════ */
.readouts, .metric-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  background: var(--line); border: 1px solid var(--line);
}
.ro, .metric-card {
  background: var(--panel); padding: 14px 16px 15px;
  display: flex; flex-direction: column; gap: 4px;
  min-height: 86px; justify-content: center;
}
.ro .k, .metric-label {
  font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .14em;
  text-transform: uppercase; color: var(--fg-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 大数字：等宽数字 + 固定行高 + 固定高度槽位 —— 轮询换值不改高度，页面不跳。 */
.ro .v, .metric-value {
  font-family: var(--font-num); font-size: var(--t7); font-weight: 700; line-height: 1;
  /* 等宽数字。通用排版建议是「独立大数用比例数字」，但这里有两个更强的理由不照它：
     ① 这个数会滚——轮询到新值时用 rAF 从旧值补间，比例数字会让它每帧变宽，
        正是设计原则 02「刷新不得移动任何东西」要禁的；
     ② 这套设计的隐喻是仪表，而仪表的数字本来就是等宽的。 */
  font-variant-numeric: tabular-nums; letter-spacing: -.01em; color: var(--fg);
  min-height: 32px; display: flex; align-items: center;
}
.ro .v.bad, .metric-value.bad { color: var(--brick); }
.ro .v.unk, .metric-value.unk { color: var(--slate); }
.ro .v.ok { color: var(--jade); }
.ro .f, .metric-note { font-size: var(--t1); color: var(--fg-dim); min-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.metric-top { display: flex; align-items: center; justify-content: space-between; }
.metric-icon { font-family: var(--font-mono); color: var(--fg-faint); font-size: var(--t3); }

/* ══════════════════════════════════════════════════════════════════════
   面板 —— 细线 + 标签轨，不是浮动圆角卡片
   ══════════════════════════════════════════════════════════════════════ */
.panel, .pnl { border: 1px solid var(--line); background: var(--panel); display: flex; flex-direction: column; min-width: 0; }
.panel-heading, .pnl > .rail {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 16px; border-bottom: 1px solid var(--line); background: var(--sunk);
}
.panel-heading h3, .pnl > .rail .n { font-size: var(--t4); font-weight: 600; }
.pnl > .rail .t { display: flex; flex-direction: column; gap: 1px; }
.pnl > .rail .k { font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .18em; text-transform: uppercase; color: var(--fg-faint); }
.panel-chip { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.pnl > .bd, .panel > .bd { padding: 16px; }
.panel-description { display: block; color: var(--fg-dim); font-size: var(--t2); margin-top: 4px; font-family: var(--font-ui); }

.overview-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 16px; }
.overview-grid.lower { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* 活动图整宽：一格一次运行，14 天要横向空间；和窄面板分栏会把格子挤成竖条。 */
/* 活动图独占整行，横向拉满。

   中间那一版把面板收到 443px 再拿三块面板去填剩下的宽度——方向错了：要收窄的
   是**柱子**，不是图。柱子原来是 flex:1 1 0 / max-width:84px，于是每一格被拉成
   84x8px 的宽砖；现在一天一组、组内两列，每列封顶 35px，14 组沿整行均分。 */
.overview-grid.activity { grid-template-columns: minmax(0, 1fr); }
/* 图高必须有上限。它由最高那一列撑开，某天跑了 400 次就能把面板顶到一屏高，
   而下面的产能与队列会被推到折叠线以下——概览的意义就是一屏看完。 */
/* 图高由 .unit-grid 定死（见下），这里只保证任何计算失误都出不了面板。
   上一版是 max-height 却没有 overflow —— max-height 不裁剪溢出内容，柱子照样
   画到了上方面板里。 */
.chart-panel .activity-chart { overflow: hidden; }
.chart-panel, .queue-panel { min-height: 0; }
.chart-panel .activity-chart, .chart-panel > .bd { padding: 16px; }

/* ══════════════════════════════════════════════════════════════════════
   活动图：单位图（一格 = 一次运行）
   柱状图只给「那天有几条」一个数。一格一次运行后，同一张图同时给出条数、
   结果构成和分布；4 条运行就是 4 格，不会被画成一根 72% 高的柱子。
   ══════════════════════════════════════════════════════════════════════ */
.unit-chart { display: flex; flex-direction: column; gap: 10px; padding: 16px; }
.unit-legend { display: flex; gap: 15px; margin-left: auto; }
.unit-legend .lg, .chart-legend .legend-item { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-dim); }
.unit-legend .lg i, .legend-dot { width: 8px; height: 8px; display: block; border-radius: 0; }
.unit-legend .lg i.s, .legend-dot.success { background: var(--jade); }
.unit-legend .lg i.f, .legend-dot.failed { background: var(--brick); }
.legend-dot.rate { background: var(--signal); }
.unit-legend .lg i.u {
  background: repeating-linear-gradient(135deg, var(--slate) 0 2px, transparent 2px 4px);
  box-shadow: inset 0 0 0 1px var(--slate);
}
/* 118px = UNIT_CELL_CAP(12) * 格高 8px + 11 * 间距 2px，和设计稿一致。
   改这三个数中的任何一个都必须同时改另外两个，关系由
   main-integration.test.mjs 的「活动图一列画不出面板外」钉住。 */
/* 一天一组，组内「成功 / 失败」两列并排，14 组沿整行均分。

   收窄的是柱子不是图：原来 .unit-col 是 flex:1 1 0 / max-width:84px，每一格被
   拉成 84x8px 的宽砖，而且 14 天铺完只占 1176px，右边空一大片。现在每组占
   1/14 行宽并居中，组内两列各自封顶 35px —— 砖变窄了，图仍然是满的。

   为什么给上限而不是让列跟着行宽长：整行给到 2200px 时，不封顶每列会长到 65px
   以上，8px 高的格子又变回宽砖。封顶之后组间自然留白，也就是「宁大勿密」。 */
.unit-grid { display: flex; align-items: flex-end; gap: 0; height: 118px; }
.unit-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
  justify-content: flex-end; align-items: center; gap: 3px; }
.unit-pair { display: flex; align-items: flex-end; justify-content: center; gap: 6px;
  width: 100%; max-width: 76px; }
.unit-cells { display: flex; flex-direction: column-reverse; gap: 2px; flex: 1 1 0;
  min-width: 0; max-width: 35px; }
.unit-cells > i { display: block; height: 8px; border-radius: 1px; }
/* 有运行、但这一侧是 0：画一条极扁的基线而不是留空。空着看不出是「零」
   还是「没画出来」，一条线明确说了是零。 */
.unit-cells:empty { height: 2px; background: var(--line); opacity: .55; }.unit-cells > i.s { background: var(--jade); }
.unit-cells > i.f { background: var(--brick); }
/* 空白天：画一道斜纹基线，明确表示「这天没有任务」而不是「图坏了」 */
.unit-col .idle { height: 3px; background: repeating-linear-gradient(90deg, var(--slate) 0 2px, transparent 2px 5px); opacity: .5; }
.unit-col .x { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); text-align: center; letter-spacing: -.02em; }
.unit-col.today .x { color: var(--signal); }
.unit-col:hover .unit-cells > i { outline: 1px solid var(--line-strong); }
/* 聚合带：14 天总构成压成一条，给出全局比例 */
/* 聚合条 + 图例同一行。图拉满整行之后，这条 5px 高的条子横贯 2134px 就不像
   一根「成功 : 失败」比例条，只像面板底下多画了一道边框。给它一个定宽，
   图例接在右边。 */
.agg { display: flex; align-items: center; gap: 16px; }
.agg-bar { flex: 0 0 420px; }
.agg-legend { flex: 1 1 auto; }
.agg-bar { display: flex; height: 5px; background: var(--sunk); overflow: hidden; }
.agg-bar > i { display: block; }
.agg-bar > i.s { background: var(--jade); }
.agg-bar > i.f { background: var(--brick); }
.agg-bar > i.idle { background: repeating-linear-gradient(90deg, var(--slate) 0 2px, transparent 2px 5px); opacity: .45; }
.agg-legend { display: flex; flex-wrap: wrap; gap: 15px; font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.agg-legend b { color: var(--fg-dim); font-weight: 500; }

/* 产能面板 —— 四种输入换算成同一个单位（次），排出谁在卡脖子 */
/* 左列是结论（可注册 N），右列是那个数怎么来的：一行说明 + 四行余量。
   三个孩子必须显式占位。只写 grid-template-columns 时它们按 DOM 顺序自动放格，
   结论块和说明各占第一行两列，四行余量就掉进第二行第一列——178px 宽的格子里塞
   一个 92+560+168 的三列行，右边 1152px 空着，条被挤没。生产上就是这个样子。 */
.cap { display: grid; grid-template-columns: 178px 1fr; grid-template-rows: auto 1fr; gap: 0; }
.cap-verdict { grid-column: 1; grid-row: 1 / span 2; padding: 16px 18px; border-right: 1px solid var(--line); display: flex; flex-direction: column; gap: 4px; justify-content: center; background: var(--sunk); }
.cap-verdict .k { font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .15em; text-transform: uppercase; color: var(--fg-faint); }
.cap-verdict .v { font-family: var(--font-num); font-size: var(--t8); font-weight: 700; line-height: .95; font-variant-numeric: tabular-nums; }
.cap-verdict .v.zero { color: var(--brick); }
.cap-verdict .why { font-size: var(--t2); color: var(--fg-dim); }
.cap-verdict .why b { color: var(--brick); font-weight: 600; }
.cap-inputs { grid-column: 2; grid-row: 2; display: flex; flex-direction: column; }
/* 条的最大长度收在 560px：再长不增加任何可读性，只是让「已用 65%」这个判断
   需要横扫整屏。右侧数值列固定宽，于是四行的数字左边缘对齐，能直接上下比。 */
.cap-row { display: grid; grid-template-columns: 92px minmax(0, 560px) 168px; gap: 14px; align-items: center; padding: 9px 18px; }
.cap-row + .cap-row { border-top: 1px solid var(--line); }
.cap-row .n { font-size: var(--t2); color: var(--fg-dim); }
.cap-row .amt { font-family: var(--font-mono); font-size: var(--t1); text-align: right; color: var(--fg-dim); white-space: nowrap; }
.cap-bar { height: 6px; background: var(--sunk); position: relative; overflow: hidden; }
.cap-bar > i { position: absolute; inset: 0 auto 0 0; display: block; }
.cap-bar > i.ok { background: var(--jade); }
.cap-bar > i.tight { background: var(--signal); }
.cap-row.bind .n { color: var(--fg); font-weight: 600; }
.cap-row.bind .amt { color: var(--fg); }
.cap-row.bind .cap-bar { box-shadow: inset 0 0 0 1px var(--signal); }
.bind-tag {
  margin-left: 6px; padding: 0 5px; border: 1px solid var(--signal); border-radius: var(--r);
  font-family: var(--font-mono); font-size: var(--t0); color: var(--signal-dim);
  vertical-align: 1px;
}
/* 读不到 / 为 0 才是砖红：那两种是「这一项坏了」，和「今晚最多跑到这」是两件事。 */
.cap-bar > i.zero { background: var(--brick); }
.cap-row .amt.bad { color: var(--brick); }
.cap-scale {
  grid-column: 2; grid-row: 1;
  padding: 8px 18px; border-top: 1px solid var(--line);
  font-size: var(--t1); color: var(--fg-faint); font-family: var(--font-mono);
}

/* ── 队列小列表（总览） ─────────────────────────────────────────────── */
.queue-list { display: flex; flex-direction: column; }
.queue-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 16px; border-bottom: 1px solid var(--line); background: none; border-left: none; border-right: none; border-top: none;
  cursor: pointer; text-align: left; width: 100%; color: var(--fg); font-family: var(--font-ui);
}
.queue-row:hover { background: var(--raised); }
.queue-row-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.queue-row-main strong { font-size: var(--t3); font-weight: 600; }
.queue-row-main small { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }

/* ── 代理余量 ────────────────────────────────────────────────────────
   读不出的厂商画斜纹空条，不是 0% 实心条 —— 0 会被读成「流量用完了」。 */
.quota-list { display: flex; flex-direction: column; }
.quota-row { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--line); }
.quota-row:last-child { border-bottom: none; }
.quota-row-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.quota-row-main strong { font-size: var(--t3); font-weight: 600; }
.quota-row-main small { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-dim); }
.quota-track { height: 3px; background: var(--sunk); border-radius: inherit; grid-column: 1 / -1; }
.quota-unknown .quota-track { background: repeating-linear-gradient(90deg, var(--line-strong) 0 3px, transparent 3px 6px); }
.quota-fill {
  height: 100%;
  /* Zero, not auto. A block element with no width fills its parent, so a vendor
     whose traffic could not be read rendered a full bar — it looked like 100%
     remaining when the truth was "unknown". The renderer only sets a width when
     it has a figure, so the default is what every expired/unconfigured vendor
     shows. */
  width: 0;
  background: var(--jade);
  border-radius: inherit;
}
.quota-fill.warn { background: var(--signal); }
.quota-fill.low { background: var(--brick); }
.quota-value { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg-dim); text-align: right; align-self: start; }

/* ── 打码续航（按次计） ─────────────────────────────────────────────── */
.solver-list { display: flex; flex-direction: column; }
.solver-row {
  display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center;
  padding: 11px 16px; border-bottom: 1px solid var(--line);
}
.solver-row:last-child { border-bottom: none; }
.solver-row.is-active { background: var(--signal-wash); }
.solver-row-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.solver-row-main strong { font-size: var(--t3); font-weight: 600; }
.solver-row-main small { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.solver-value { font-family: var(--font-num); font-size: var(--t5); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--fg); text-align: right; }

/* 组件层用的续航行（design：以「次」为单位） */
.solver-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.solver-head b { font-size: var(--t3); font-weight: 600; }
.solver-head .unit { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.solver-head .active { font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .1em; color: var(--signal); border: 1px solid rgba(240, 166, 60, .4); padding: 0 5px; border-radius: var(--r); }
.solver-cap { height: 6px; background: var(--sunk); position: relative; }
.solver-cap > i { position: absolute; inset: 0 auto 0 0; background: var(--jade); }
.solver-cap > i.dim { background: var(--jade); opacity: .42; }
.solver-figs { text-align: right; }
.solver-figs .runs { font-family: var(--font-num); font-size: var(--t6); font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; display: block; }
.solver-figs .money { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.solver-total { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--line-strong); }
.solver-total .v { font-family: var(--font-num); font-size: var(--t6); font-weight: 700; }
.solver-total .v small { font-family: var(--font-ui); font-size: var(--t2); font-weight: 500; color: var(--fg-dim); }

/* ══════════════════════════════════════════════════════════════════════
   数据表 —— 产品的主体。表头吸顶（相对唯一滚动容器），行首严重度条。
   ══════════════════════════════════════════════════════════════════════ */
.table-panel { min-width: 0; }
.table-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 11px 16px; border-bottom: 1px solid var(--line); background: var(--sunk); }
.table-head span:first-child { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg); }
.table-head .muted { font-size: var(--t2); }
/* Horizontal only. A vertical cap here nests a second scroller inside the one
   scroller the page already has. */
/* 横向滚动一直在这里；纵向滚动在列表页也归它（见 .page-view.list-view）。
   表头的 position: sticky 会自动相对最近的滚动祖先，所以吸顶跟着一起搬过来，
   top 值不用动。 */
.table-scroll { overflow-x: auto; }
.data-table, table.dt { width: 100%; border-collapse: collapse; font-size: var(--t3); }
.data-table thead th, table.dt thead th {
  position: sticky; top: 0; background: var(--sunk); z-index: 2;
  font-family: var(--font-mono); font-size: var(--t0); font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--fg-faint);
  text-align: left; padding: 9px 14px; border-bottom: 1px solid var(--line-strong); white-space: nowrap;
}
.data-table tbody td, table.dt tbody td { padding: 9px 14px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.data-table tbody tr, table.dt tbody tr { transition: background .12s var(--ease); }
.data-table tbody tr:hover, table.dt tbody tr:hover { background: var(--raised); }
.data-table tbody tr.is-selected { background: var(--signal-wash); }
/* 行首严重度条：状态不再是浮在行中间的色块 */
td.stripe { padding: 0 !important; width: 3px; }
td.stripe i { display: block; width: 3px; height: 100%; min-height: 36px; background: var(--line-strong); }
tr.r-ok td.stripe i { background: var(--jade); }
tr.r-bad td.stripe i { background: var(--brick); }
tr.r-unk td.stripe i { background: repeating-linear-gradient(180deg, var(--slate) 0 3px, transparent 3px 6px); }
tr.r-run td.stripe i { background: var(--signal); }
.cell-mail { font-size: var(--t2); }
/* 等宽数字列不换行：时间被折成两行会让行高参差 */
.cell-num { font-variant-numeric: tabular-nums; font-size: var(--t2); color: var(--fg-dim); white-space: nowrap; }
.data-table td strong { font-weight: 600; font-size: var(--t2); }
.data-table td small.muted { display: block; font-size: var(--t0); margin-top: 1px; }
.row-link { background: none; border: none; padding: 0; color: var(--signal); font-family: var(--font-mono); font-size: var(--t2); cursor: pointer; }
.row-link:hover { text-decoration: underline; }

/* 分页 */
.pagination { display: flex; align-items: center; gap: 16px; padding: 9px 16px; border-top: 1px solid var(--line); font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-faint); }
/* 7,543 条 ÷ 25 = 302 页。原来只有 ‹ ›：翻到第 200 页要点 199 次，也看不出一共几页。 */
.pg-range { margin-right: auto; font-variant-numeric: tabular-nums; color: var(--fg-dim); }
.pg-size { display: inline-flex; align-items: center; gap: 6px; }
.pg-size select { width: auto; min-width: 62px; height: 26px; font-family: var(--font-mono); font-size: var(--t1); padding: 0 22px 0 8px; background-position: calc(100% - 11px) 11px, calc(100% - 7px) 11px; }
.pg-nav { display: inline-flex; align-items: center; gap: 6px; }
.pg-jump { height: 26px; text-align: center; font-family: var(--font-mono); font-size: var(--t1); padding: 0 4px; }
.pg-of { font-variant-numeric: tabular-nums; }
.pagination button { background: transparent; border: 1px solid var(--line-strong); color: var(--fg); width: 26px; height: 26px; padding: 0; border-radius: var(--r); cursor: pointer; font-family: var(--font-mono); }
.pagination button:hover:not(:disabled) { background: var(--raised); border-color: var(--fg-faint); }
.pagination button:disabled { opacity: .35; cursor: not-allowed; }

/* 汇总条 */

/* ══════════════════════════════════════════════════════════════════════
   工具条 / 筛选 / 批量条 —— 分组，不是九个控件挤一行
   ══════════════════════════════════════════════════════════════════════ */
.filter-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* 每个控件按内容定宽，不许为了填满而拉长。六个控件一行 1,180px，在 2,200px 的内容
   宽里留 1,000px 空——这是对的：筛选条不是版面元素。 */
.filter-bar > * { flex: 0 0 auto; }
.filter-input { width: 320px; max-width: 100%; }
.filter-input.compact-filter { width: 260px; }
/* 筛选条上的按钮要和输入框等高，否则基线差 1px——一行里所有控件的底边
   对不齐，看起来像抖了一下。 */
.filter-bar > button, .filter-bar > .btn, .filter-bar > .secondary-button { height: 35px; }
.filter-select { width: auto; min-width: 108px; }
.filter-select { width: auto; min-width: 130px; }
.compact-filter { width: 150px; }
.segmented { display: inline-flex; border: 1px solid var(--line-strong); border-radius: var(--r); overflow: hidden; }
.segmented button { background: transparent; border: none; border-right: 1px solid var(--line); color: var(--fg-dim); padding: 6px 13px; font-size: var(--t2); cursor: pointer; font-family: var(--font-ui); }
.segmented button:last-child { border-right: none; }
.segmented button:hover { background: var(--raised); color: var(--fg); }
.segmented button.selected { background: var(--signal-wash); color: var(--fg); font-weight: 600; }
.bulk-bar { display: flex; align-items: center; gap: 0; flex-wrap: wrap; padding: 8px 16px; border-bottom: 1px solid var(--line); background: var(--sunk); font-size: var(--t2); }
/* 九个控件一排、无分组，读的人要自己找边界。四组：选谁 / 按数量 / 按区间 / 对他们做什么。
   分隔靠 1px 细线，不靠间距——间距在换行后就消失了，线不会。 */
.bulk-group { display: inline-flex; align-items: center; gap: 8px; padding: 0 14px; }
.bulk-group + .bulk-group { border-left: 1px solid var(--line); }
.bulk-group:first-child { padding-left: 0; }
.bulk-group.grow { margin-left: auto; border-left: 0; }
.bulk-bar label { display: inline-flex; align-items: center; gap: 7px; color: var(--fg-dim); }
.bulk-label { color: var(--fg-faint); font-size: var(--t1); }
.bulk-selected { font-family: var(--font-mono); color: var(--signal-dim); font-size: var(--t2); font-variant-numeric: tabular-nums; }
.inline-field { display: inline-flex; align-items: center; gap: 7px; color: var(--fg-dim); }
.inline-field input:not([type="checkbox"]) { width: 74px; height: 30px; }
.action-field { gap: 8px; }
.action-field select { width: auto; min-width: 130px; height: 30px; }
.multi-filter { position: relative; }
.multi-filter summary { list-style: none; cursor: pointer; padding: 0 11px; height: 35px; display: inline-flex; align-items: center; border: 1px solid var(--line-strong); border-radius: var(--r); font-size: var(--t2); color: var(--fg-dim); background: var(--sunk); }
.multi-filter summary::-webkit-details-marker { display: none; }
.multi-filter[open] summary { border-color: var(--signal); }
/* 账号页的两级筛选菜单。

   一级那行原来排着七个控件，再加支付方式（十几种）就溢出了。
   左边分类、右边选项；分类上带「选了几项」的角标，菜单关着也看得见。 */
.filter-menu { position: relative; }
.filter-menu > summary {
  list-style: none; cursor: pointer; user-select: none;
  padding: 0 12px; height: 30px; display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line-strong); background: var(--sunk);
  font-size: var(--t2); color: var(--fg-dim);
}
.filter-menu > summary::-webkit-details-marker { display: none; }
.filter-menu > summary::after { content: "\25be"; color: var(--fg-faint); }
.filter-menu > summary:hover { color: var(--fg); border-color: var(--signal); }
.filter-menu[open] > summary { color: var(--signal); border-color: var(--signal); }
.filter-menu-body {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0;
  display: grid; grid-template-columns: 128px minmax(300px, 1fr);
  background: var(--raised); border: 1px solid var(--line-strong);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .55);
}
.filter-cats {
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px; background: var(--sunk); border-right: 1px solid var(--line);
}
.filter-cat {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: 7px 9px; border: 0; background: transparent; cursor: pointer;
  font-family: inherit; font-size: var(--t2); color: var(--fg-dim); text-align: left;
}
.filter-cat:hover { background: var(--raised); color: var(--fg); }
.filter-cat.on { background: var(--raised); color: var(--signal); }
/* 角标：这一类选了几项。没选就不画 —— 一排 0 比一排空白更难扫。 */
.filter-cat b {
  flex: none; min-width: 16px; padding: 0 4px; border-radius: 8px;
  background: var(--signal); color: var(--ground);
  font-size: var(--t0); line-height: 15px; text-align: center; font-weight: 700;
}
.filter-panes { padding: 8px 10px; max-height: 420px; overflow-y: auto; }
.filter-pane { display: flex; flex-direction: column; gap: 8px; }
.filter-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: var(--t1); letter-spacing: .14em; text-transform: uppercase; color: var(--fg-faint);
}
.filter-head b { letter-spacing: 0; color: var(--fg); font-variant-numeric: tabular-nums; }
.filter-head em {
  font-style: normal; letter-spacing: 0; text-transform: none; color: var(--fg-dim);
}
.filter-opts { display: flex; flex-direction: column; gap: 1px; }
.filter-opt {
  display: flex; align-items: center; gap: 8px; padding: 5px 7px; cursor: pointer;
  font-size: var(--t2); color: var(--fg-dim);
}
.filter-opt:hover { background: var(--signal-wash); color: var(--fg); }
.filter-opt input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.filter-opt .bx {
  width: 13px; height: 13px; flex: none; position: relative;
  border: 1px solid var(--line-strong); background: var(--sunk); border-radius: 2px;
}
.filter-opt .bx.round { border-radius: 50%; }
.filter-opt input:checked ~ .bx { border-color: var(--signal); background: var(--signal); }
.filter-opt input:checked ~ .bx::after {
  content: "\2713"; position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: var(--t0); font-weight: 700; color: var(--ground);
}
.filter-opt input:checked ~ .filter-nm { color: var(--signal); }
.filter-opt input:focus-visible ~ .bx { outline: 2px solid var(--signal); outline-offset: 2px; }
.filter-ico { display: inline-flex; flex: none; }
.filter-nm { flex: 1; min-width: 0; }
.filter-cc {
  flex: none; font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint);
}
.filter-n { flex: none; font-variant-numeric: tabular-nums; color: var(--fg-dim); font-weight: 500; }
.filter-hint { flex: none; font-size: var(--t1); color: var(--fg-faint); }
.filter-empty { margin: 0; font-size: var(--t1); color: var(--fg-faint); }
.filter-menu-foot {
  grid-column: 1 / -1; display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 8px 10px; border-top: 1px solid var(--line); background: var(--sunk);
}
/* 已选筛选的 chips。★ 它不是装饰 ★ 两级菜单把选项藏了起来，
   没有这一排，「为什么只有 3 行」就得逐类点开菜单才能回答。 */
.filter-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 8px 0 0; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 4px 2px 7px;
  border: 1px solid var(--signal); background: var(--signal-wash);
  font-size: var(--t1); color: var(--fg);
}
.filter-chip i { font-style: normal; color: var(--fg-faint); }
.filter-chip-x {
  border: 0; background: transparent; cursor: pointer; padding: 0 3px;
  font-family: inherit; font-size: var(--t2); line-height: 1; color: var(--fg-faint);
}
.filter-chip-x:hover { color: var(--brick); }

.multi-filter-menu { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: 170px; background: var(--raised); border: 1px solid var(--line-strong); box-shadow: 0 14px 34px rgba(0, 0, 0, .55); padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.multi-filter-menu label { display: flex; align-items: center; gap: 8px; padding: 6px 8px; font-size: var(--t2); color: var(--fg-dim); cursor: pointer; }
.multi-filter-menu label:hover { background: var(--signal-wash); color: var(--fg); }
/* 资格筛选每一行右侧的条数。★ 数字替代解释 ★ 0 的那几档压暗但不隐藏：
   隐藏会让人以为「没这一档」，压暗才说得出「有这一档，但库里一个都没有」。 */
.multi-filter-menu .filter-name { flex: 1; }
.multi-filter-menu .filter-count { font-size: var(--t3); color: var(--fg-dim); font-variant-numeric: tabular-nums; }
.multi-filter-menu .filter-count.is-none { color: var(--fg-faint); }

/* 国家选择器（country-picker.js）。
   ★ 2026-09-08 傍晚从「下拉浮层」改成「一枚按钮 + 一扇面板」★
   前一版复用 .multi-filter 的浮层：一列、240px 宽、340px 高。装 5 个国家没问题，
   装 249 个时一次只露出七八行，找一个国家要滚三十屏 —— 用户的原话是
   「你就不会设计吗」。现在表单上只留缩写，挑国家在一扇模态面板里做。
   CSP（style-src 'self'）下的规矩没变：纯 CSS 网格 + 原生 <dialog>，
   不写一行内联样式、不做定位脚本。 */
.country-picker-host { display: block; min-width: 0; }
.country-picker { position: relative; }

/* ① 表单上那枚按钮：已选国家的缩写 */
.country-trigger {
  display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
  width: 100%; min-height: 35px; padding: 4px 10px; text-align: left;
  border: 1px solid var(--line-strong); border-radius: var(--r);
  background: var(--sunk); color: var(--fg); font-size: var(--t2); cursor: pointer;
  transition: border-color .16s var(--ease);
}
.country-trigger:hover { border-color: var(--fg-faint); }
.country-trigger:focus-visible { outline: 2px solid var(--signal); outline-offset: -1px; }
.country-trigger:disabled, .country-trigger.is-blocked { cursor: not-allowed; color: var(--fg-faint); }
.country-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 1px 7px;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--raised);
  font-family: var(--font-mono); font-size: var(--t2); font-weight: 600; letter-spacing: .05em;
}
/* 不在候选里却选中着的那几个：按钮上就得看得出来，否则人要点开面板才知道
   自己手里拿着一个提交必被拒的值。 */
.country-chip.is-absent { border-color: var(--signal); color: var(--signal-dim); }
.country-chip.is-more { color: var(--fg-faint); }
.country-chip-flag { width: 14px; height: 14px; display: block; flex: none; }
.country-trigger-empty { color: var(--fg-faint); }
.country-trigger-warn { color: var(--signal-dim); font-size: var(--t1); }
.country-trigger-cue { margin-left: auto; color: var(--fg-faint); font-size: var(--t1); }

/* ② 面板本身。比普通对话框宽：网格要排到四五列才谈得上「一屏看完」。 */
/* ★ display 只能挂在 [open] 上 ★ 裸写 .country-modal{display:flex} 会**覆盖掉**
   <dialog> 未打开时 UA 自带的 display:none —— 面板于是永远画着，关掉之后它不再
   属于顶层，就以一个普通块元素躺在表单里压着下面的字段。
   09-08 生产截图坐实：脚本报 dialog.open===false，而屏幕上它还在。
   这一条 .modal 基类没踩到，是因为那几个对话框压根没设 display。 */
.country-modal {
  width: min(920px, calc(100vw - 32px));
  padding: 18px 20px 18px;
}
.country-modal[open] { display: flex; flex-direction: column; gap: 11px; }
.country-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.country-modal-head h2 { font-size: var(--t4); }
.country-filter {
  width: 100%; height: 34px; padding: 0 10px; font-size: var(--t2);
  color: var(--fg); background: var(--sunk);
  border: 1px solid var(--line-strong); border-radius: var(--r);
}
.country-filter:focus { outline: 2px solid var(--signal); outline-offset: -1px; }

/* ③ 网格。auto-fill + 200px：宽屏排四列，窄屏自动降到一列，不用媒体查询。
   max-height 让它自己滚，而不是把整扇面板顶出屏幕。 */
.country-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 3px; align-content: start;
  max-height: min(54vh, 460px); overflow-y: auto;
  padding-right: 4px;
}
/* 分组小标题占满一行。★ 249 个平铺是没有落点的 ★ 人要找的不是「所有国家」，
   而是「下一个值得花一次出口的国家」，所以按探测历史分段。 */
.country-group {
  grid-column: 1 / -1; display: flex; align-items: baseline; gap: 8px;
  margin-top: 9px; padding: 3px 2px; border-bottom: 1px solid var(--line);
}
.country-group:first-child { margin-top: 0; }
.country-group b { font-size: var(--t2); font-weight: 600; color: var(--fg-dim); }
.country-group span { font-size: var(--t1); color: var(--fg-faint); font-variant-numeric: tabular-nums; }

/* 一格：国旗 | 两位码 | 名字，说明另起一行横跨后两列。
   ★ 说明不许和名字抢宽度 ★ 原来是 flex + name{flex:1}，遇到长说明会把
   「GB · 英国」挤成三行（用户 09-08 截图实测）。名字是这一项的身份。 */
.country-cell {
  display: grid; grid-template-columns: auto auto 1fr; gap: 0 7px;
  padding: 6px 8px; text-align: left; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: var(--r);
  color: var(--fg-dim); min-width: 0;
}
.country-cell:hover { background: var(--signal-wash); color: var(--fg); }
.country-cell:focus-visible { outline: 2px solid var(--signal); outline-offset: -1px; }
.country-cell.is-on { border-color: var(--signal); background: var(--signal-wash); color: var(--fg); }
.country-cell.is-wide { grid-column: 1 / -1; grid-template-columns: 1fr; }
.country-flag { grid-row: 1 / span 2; align-self: center; width: 18px; height: 18px; display: block; }
.country-code {
  grid-column: 2; grid-row: 1; font-family: var(--font-mono); font-weight: 600;
  font-size: var(--t2); letter-spacing: .06em;
}
.country-name {
  grid-column: 3; grid-row: 1; font-size: var(--t2); color: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.country-cell.is-wide .country-name { grid-column: 1; }
.country-note {
  grid-column: 2 / -1; grid-row: 2; font-size: var(--t1); color: var(--fg-faint); line-height: 1.5;
}
.country-cell.is-wide .country-note { grid-column: 1; }
.country-note:empty { display: none; }
/* 不在候选里但当前选中的那一项：必须看得见，而且要说清它为什么在这儿。
   删掉它等于「输入被静默忽略」——隐藏 input 里那个值还在，照样被发出去。 */
.country-cell.is-absent .country-code, .country-cell.is-absent .country-name { color: var(--signal-dim); }

.country-modal-foot {
  display: flex; align-items: center; gap: 9px; padding-top: 10px; border-top: 1px solid var(--line);
  flex-wrap: wrap;
}
.country-tally { flex: 1; min-width: 0; font-size: var(--t2); color: var(--fg-dim); }
.country-modal-foot .secondary-button, .country-modal-foot .primary-button { padding: 6px 14px; }
/* 「全选」「清空」是文字按钮，「取消」「确定」才是实心的 —— 前两个是面板内的
   编辑动作，后两个才决定这次改动算不算数。长得一样的话，人会把「清空」当成
   和「确定」同一层级的东西。 */
.country-modal-foot .text-button { color: var(--fg-dim); font-size: var(--t2); padding: 4px 6px; }
.country-modal-foot .text-button:hover { color: var(--fg); }
.country-modal-foot .country-all { margin-left: auto; }

/* 还没接通的那一档：画成不可点，并把原因摆在旁边。
   ★ 不是隐藏 ★ 隐藏会让人以为「没这个功能」，而它有，只是还没接通 ——
   两件事的下一步完全不同。 */
.probe-i.is-blocked { opacity: .55; cursor: not-allowed; }
.probe-i.is-blocked input { cursor: not-allowed; }

.country-free, .country-help { margin: 0 2px; font-size: var(--t2); color: var(--fg-faint); line-height: 1.5; }

/* ══════════════════════════════════════════════════════════════════════
   任务日志抽屉
   ══════════════════════════════════════════════════════════════════════ */
/* 任务列表 / 实时日志对半分。

   原来日志抽屉是死宽 340px：整页 2200px 时表格拿 1858、日志拿 340，而生产的日志
   行是结构化键值对，实测最长 136 字符（11.5px 等宽约需 940px）—— 340px 里每一行
   都要折成四行，还会被 word-break 从邮箱中间截断。而左边那张表只有 7 列短值，
   1858px 根本用不满。两边都错，方向还相反。

   对半分之后：日志约 1100px，最长行一次读完；表格约 1100px，7 列仍然宽裕。 */
/* 按需抽屉（F12 · 稿 §15）。没选任务时 .log-drawer 根本不在 DOM 里，
   所以这里默认就是单列满宽；选中后 JS 加 .has-drawer，才切成 50/50。
   生产那版是 340px 死宽（每条日志折四行、还从邮箱中间截断），我中途改的 50/50
   也不对——没选任务时右半屏是一句占位文案。两个错的方向相反，共同的病根是
   「抽屉常驻」。 */
.split-workspace { display: grid; grid-template-columns: minmax(0, 1fr); }
.split-workspace.has-drawer { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.split-workspace.has-drawer .table-scroll { border-right: 1px solid var(--line); }
.log-drawer {
  display: flex; flex-direction: column; background: var(--sunk); min-width: 0;
  animation: drawer-in .18s var(--ease);
}
@keyframes drawer-in { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
.drawer-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--line); }
.drawer-heading h3 { font-size: var(--t3); font-weight: 600; }
.dh-title { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.dh-title b { font-size: var(--t2); font-weight: 600; }
.dh-title .dim { font-size: var(--t1); color: var(--fg-dim); }
.dh-actions { display: flex; align-items: center; gap: 12px; flex: none; }
/* 「跟随尾部」是开关，得看得出开着还是关着——不然按了没反应会以为坏了。 */
#log-follow.is-on { color: var(--signal-dim); }
#log-follow.is-on::before { content: '●'; margin-right: 5px; font-size: var(--t0); }
#log-follow:not(.is-on)::before { content: '○'; margin-right: 5px; font-size: var(--t0); }
/* 停止是破坏性动作（拍板 D5）：砖红文字，不与「复制」同色。 */
.text-button.danger { color: var(--brick); }
.text-button.danger:hover { color: var(--brick); text-decoration: underline; }

/* 日志分级着色。生产是一色到底，ERROR 和 DEBUG 长得一模一样。 */
.lg-line { padding: 1px 0; }
.lg-line.lv-err { color: var(--brick); }
.lg-line.lv-warn { color: var(--signal-dim); }
.lg-line.lv-dbg { color: var(--fg-faint); }
.lg-line.lv-info { color: var(--fg-dim); }
/* 本任务的 ID 与邮箱高亮：一屏日志里最需要一眼认出的就是它们。 */
.lg-hit { background: var(--signal-wash); color: var(--fg); border-radius: 2px; padding: 0 2px; }
/* 行尾的中文注解：说清这行是什么意思、下一步该做什么。压暗一档并另起一行，
   这样它读起来是「注解」而不是日志本身的一部分 —— 上游原话仍是那一行的主体。 */
.lg-note { display: block; color: var(--fg-faint); padding-left: 1.2em; }
.log-output, .log {
  flex: 1; margin: 0; padding: 12px 14px; background: var(--sunk);
  font-family: var(--font-mono); font-size: var(--t1); line-height: 1.7; color: var(--fg-dim);
  /* 按词换行，不从中间截断：word-break: break-word 会把
     willis_hunt@computer4u.com 拆到两行，日志里最需要一眼认出的就是这种标识符。
     只有单个词本身比整块还长时才允许断。 */
  white-space: pre-wrap; word-break: normal; overflow-wrap: break-word;
  /* ★ 撑满抽屉，别留一大片空白 ★ 这里原来是 max-height: 420px，于是日志被压在
     上面一小格里滚动，而抽屉下面几百像素全是空的 —— 一个跑 700 个账号的任务，
     能看见的只有七八行。flex:1 本来就会撑满，是那条死高把它顶住了。
     min-height: 0 是 flex 子项能真正滚动的前提，少了它内容会把抽屉撑破。 */
  flex: 1; min-height: 0; overflow-y: auto;
}
/* 抽屉里的两个页签：逐账号结果 / 日志。
   抽屉本来只有日志一屏，v11 把逐账号结果拆成真表之后它有了两屏 —— 而这两屏
   回答的是两个不同的问题（「哪些号没成」和「这一批当时在做什么」），并排塞进
   半屏里会让两边都读不完，所以是页签不是分栏。 */
.drawer-tabs { display: flex; gap: 2px; padding: 0 12px; border-bottom: 1px solid var(--line); flex: none; }
.drawer-tab {
  appearance: none; background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 7px 10px; font: inherit; font-size: var(--t1); color: var(--fg-dim); cursor: pointer;
}
.drawer-tab:hover:not(:disabled) { color: var(--fg); }
.drawer-tab.is-on { color: var(--fg); border-bottom-color: var(--signal); }
/* 禁用的页签不是「藏起来」：它说明这个任务没有逐账号结果，而那本身是要看到的
   事实。点不动，但看得见、有 title 说明为什么。 */
.drawer-tab:disabled { color: var(--fg-faint); cursor: default; }
.drawer-tab .tab-n { margin-left: 5px; font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.drawer-tab.is-on .tab-n { color: var(--fg-dim); }

/* 逐账号结果那一屏。min-height: 0 是 flex 子项能真正滚动的前提。 */
.job-items { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* ★ 这一行是必需的，不是重复 ★ 上面这条类选择器设了 display，它的优先级高过
   浏览器默认样式表里的 [hidden] { display: none }。少了它，切到「日志」页签时
   逐账号结果那一屏**照样显示**——两屏叠在一起，而 el.hidden 明明设成了 true。 */
.job-items[hidden] { display: none; }
.ji-head { flex: none; padding: 9px 12px; border-bottom: 1px solid var(--line); }
.ji-line { font-size: var(--t1); color: var(--fg-dim); }
/* 「未表态」不是失败。它用去饱和色，和「失败」的砖红分开 —— 把执行器没说过的
   话画成失败，是这一屏最不该犯的错。 */
.ji-unstated { margin-left: 8px; color: var(--fg-faint); }
.ji-empty { padding: 14px 12px; color: var(--fg-faint); font-size: var(--t1); text-align: center; }
.ji-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.ji-chip {
  appearance: none; background: var(--bg); border: 1px solid var(--line); border-radius: 3px;
  padding: 3px 8px; font: inherit; font-size: var(--t0); color: var(--fg-dim); cursor: pointer;
}
.ji-chip:hover { border-color: var(--line-strong); color: var(--fg); }
.ji-chip.is-on { border-color: var(--signal); color: var(--fg); background: var(--signal-wash); }
.ji-chip .n { margin-left: 6px; font-family: var(--font-mono); color: var(--fg-faint); }
/* 失败码按钮写的是原码，等宽体 —— 它是标识符，不是句子。 */
.ji-chip.k-bad { font-family: var(--font-mono); }
.ji-chip.k-bad .n { color: var(--brick); }
.ji-chip.k-ok .n { color: var(--jade); }
.ji-scroll { flex: 1; min-height: 0; overflow: auto; }
/* 列宽定死：五列由浏览器自由分配的话，上游原话那一列会把前四列挤成竖着的字。
   和任务表同一条规矩（.jobs-table）。 */
.items-table { table-layout: fixed; width: 100%; }
.items-table .c-seq { width: 52px; }
.items-table .c-mail { width: 34%; }
.items-table .c-verdict { width: 82px; }
.items-table .c-code { width: 24%; }
.items-table .c-msg { width: auto; }
.items-table .cell-mail strong { font-family: var(--font-mono); font-size: var(--t0); font-weight: 500; }
.items-table .ji-code { font-size: var(--t0); }
/* 上游原话可能很长，收成两行；全文进 title（和「结果」列同一条规矩）。 */
.items-table .ji-msg {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  color: var(--fg-faint); font-size: var(--t0);
}
#job-items-pagination { flex: none; border-top: 1px solid var(--line); }

/* .log 是没有使用者的遗留规则，保留原来的死高，免得将来有人用上时撑破容器。 */
.log { flex: none; max-height: 420px; }
.log .ln { display: flex; gap: 12px; }
.log .ts { color: var(--fg-faint); flex: none; }
.log .msg { color: var(--fg-dim); min-width: 0; word-break: break-word; }
.log .msg.err { color: var(--brick); }
.log .msg.ok { color: var(--jade); }

/* 代理出口顺序：三家同时在用，位次决定分流。所以这一栏是「第几位」，
   不是「选中哪个」—— 单选的形状会让人以为其余两家没在跑。 */
/* 五列：位次 · 名字 · 百分比 · 这一位意味着什么 · 拖拽把手。
   基础 .sw-row 是四列（那时第一格是单选的圆点）。 */
.sw-order .sw-row { grid-template-columns: 20px 1fr auto auto auto; cursor: grab; }
.sw-order .sw-row.dragging { opacity: .45; cursor: grabbing; }
.sw-order .ord {
  flex: none; width: 20px; text-align: center; font-family: var(--font-mono);
  font-size: var(--t2); color: var(--fg-dim);
}
.sw-order .sw-row:first-child .ord { color: var(--signal); }
/* 把手只是「可以拖」的提示，不是唯一的拖拽区域——整行都能拖。 */
.sw-order .grip { flex: none; color: var(--fg-faint); font-size: var(--t1); letter-spacing: -2px; padding-left: 4px; }
.sw-note { margin: 8px 12px 12px; font-size: var(--t1); color: var(--fg-faint); line-height: 1.6; }

/* 批量操作的流量预估。它回答的是「按下执行之前该不该犹豫」，所以贴着那个
   按钮，而不是藏在别处。余量不够时变砖红 —— 那是要立刻处理的事。 */
.action-cost { margin: 8px 0 0; font-size: var(--t1); color: var(--fg-faint); line-height: 1.7; }
.action-cost.tight { color: var(--signal-dim); }
.action-cost.short { color: var(--brick); }
.action-cost b { color: var(--fg-dim); font-weight: 600; }

/* ══════════════════════════════════════════════════════════════════════
   文件与产物
   ══════════════════════════════════════════════════════════════════════ */
.files-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.drop-zone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  border: 1px dashed var(--line-strong); background: var(--sunk); padding: 28px 20px; cursor: pointer;
  color: var(--fg-dim); text-align: center; transition: border-color .16s var(--ease), background .16s var(--ease); margin: 16px;
}
.drop-zone:hover, .drop-zone.dragging { border-color: var(--signal); background: var(--signal-wash); }
.drop-zone input[type="file"] { display: none; }
.drop-zone .upload-icon { font-size: var(--t6); color: var(--fg-faint); }
.drop-zone small { font-size: var(--t1); color: var(--fg-faint); }
.upload-list, .artifact-list, .upload-preview { padding: 12px 16px 16px; display: flex; flex-direction: column; gap: 8px; }
.upload-item, .artifact-item { display: flex; align-items: center; gap: 12px; padding: 9px 12px; border: 1px solid var(--line); background: var(--sunk); }
.upload-item > span, .artifact-item > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--t2); }
.upload-item small, .artifact-item small { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-faint); }
/* 待上传确认区：选中之后、发出去之前的一站。 */
.upload-staged { padding: 12px 16px; display: flex; flex-direction: column; gap: 10px; border-bottom: 1px solid var(--line); }
.staged-heading { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.staged-heading strong { font-size: var(--t2); }
.staged-heading small { font-size: var(--t1); color: var(--fg-faint); }
.staged-item { border: 1px solid var(--line); background: var(--sunk); }
.staged-item + .staged-item { margin-top: 8px; }
.staged-head { display: flex; align-items: center; gap: 12px; padding: 9px 12px; }
.staged-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--t2); }
.staged-head small { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-faint); white-space: nowrap; }
/* 预览按原文等宽显示并横向滚动：取码链接那类长行换行之后就看不出结构了。 */
.staged-preview { margin: 0; padding: 8px 12px 10px; border-top: 1px solid var(--line); font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-muted); overflow-x: auto; white-space: pre; }
.staged-preview-none { color: var(--fg-faint); font-style: italic; white-space: normal; }
.staged-actions { display: flex; align-items: center; gap: 12px; }
.staged-note { font-size: var(--t1); color: var(--amber); }
/* 重名：整张卡片描边变色，扫一眼就知道这一份要做决定。 */
.staged-item.staged-conflict { border-color: var(--amber); }
.staged-item.staged-resolved { border-color: var(--line); }
.staged-conflict-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 12px; border-top: 1px solid var(--line); font-size: var(--t1); color: var(--amber); }
.staged-conflict-bar > span { flex: 1 1 240px; min-width: 0; }
.staged-conflict-done { color: var(--fg-faint); }
.staged-overwrite { color: var(--amber); }

.upload-preview-title { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg); padding: 4px 0; }
.upload-preview-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.upload-preview-list li { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-dim); }

/* ══════════════════════════════════════════════════════════════════════
   计划表 / 代理商会话 / 设置
   ══════════════════════════════════════════════════════════════════════ */
.schedule-col-plan { width: 26%; } .schedule-col-status { width: 10%; } .schedule-col-rate { width: 14%; }
.schedule-col-pending { width: 12%; } .schedule-col-next { width: 18%; } .schedule-col-actions { width: 20%; }
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
.settings-panel { padding: 0; }
.settings-panel .form-grid, .settings-panel > .setting-field, .settings-panel .solver-keys { padding: 16px; }
.settings-panel.wide { grid-column: 1 / -1; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }
.setting-field { display: flex; flex-direction: column; gap: 6px; }
.solver-keys { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.proxy-auth-toolbar { display: flex; gap: 9px; flex-wrap: wrap; }
.proxy-auth-list { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
/* 「去哪儿复制哪个 Cookie」——「自动读取」那条路删掉之后，这一句就是操作的人
   唯一的指引，不能省。 */
/* 账号密码两格并排。能自动登录的家才有这一块——填一次，往后令牌过期后端自己续。 */
.proxy-login-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
/* 手工粘 token 是兜底：默认收起，登录不了才展开。摆在同一层会让人以为两条路
   都得走一遍。 */
.proxy-auth-fallback { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.proxy-auth-fallback > summary { cursor: pointer; font-size: var(--t1); color: var(--fg-faint); list-style: none; }
.proxy-auth-fallback > summary::-webkit-details-marker { display: none; }
.proxy-auth-fallback > summary::before { content: '▸ '; }
.proxy-auth-fallback[open] > summary::before { content: '▾ '; }
.proxy-auth-fallback > summary:hover { color: var(--fg); }

/* 凭据的实况：能用 / 不能用 / 不知道。不能用是要立刻处理的事，给它砖红；
   能用给正文色（不必抢眼）；不知道保持默认灰。 */
.proxy-auth-live.ok { color: var(--fg-dim); }
.proxy-auth-live.bad { color: var(--brick); font-weight: 500; }

.proxy-auth-how { margin: 2px 0 8px; font-size: var(--t1); color: var(--fg-dim); line-height: 1.6; }
.proxy-auth-how code { font-family: var(--font-mono); color: var(--fg); background: var(--sunk);
  padding: 1px 5px; border-radius: var(--r); }

.proxy-auth-item { border: 1px solid var(--line); background: var(--sunk); padding: 13px 15px; display: flex; flex-direction: column; gap: 8px; }
.proxy-auth-item strong { font-size: var(--t3); font-weight: 600; }
.proxy-auth-item small { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-faint); }
.proxy-auth-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ══════════════════════════════════════════════════════════════════════
   模态 / 账号详情
   ══════════════════════════════════════════════════════════════════════ */
/* 「按清单选」的粘贴框。等宽字体：清单里是 ID 和邮箱，等宽下一眼看得出有没有
   多一个空格或少一个字符。 */
#paste-select-input {
  width: 100%; min-height: 180px; resize: vertical;
  font-family: var(--font-mono); font-size: var(--t1); line-height: 1.6;
  background: var(--sunk); color: var(--fg); border: 1px solid var(--line-strong);
  border-radius: var(--r); padding: 8px 10px;
}
#paste-select-input:focus { outline: 2px solid var(--signal); outline-offset: -1px; }
.modal-sub { margin: 4px 0 0; font-size: var(--t1); color: var(--fg-faint); line-height: 1.5; }

/* 500px 太窄：资格探测那几块每行都有「勾选 + 说明 + 国家/币种」三段，500 下
   说明被挤成三四行，整个对话框变成一根细长条。720 让说明基本一行放得下，
   而 form-grid 的 auto-fit(190px) 也能真的排到三列。小屏仍由 100vw-32px 兜住。 */
.modal { border: 1px solid var(--line-strong); background: var(--panel); color: var(--fg); padding: 0; width: min(720px, calc(100vw - 32px)); max-height: calc(100vh - 64px); overflow-y: auto; border-radius: var(--r); }
.modal::backdrop { background: rgba(0, 0, 0, .6); }
.modal form { display: flex; flex-direction: column; gap: 14px; padding: 20px 22px 22px; }
.modal-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.modal-heading h2 { font-size: var(--t5); }
.modal-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 4px; }
/* 确认框比表单窄：它只有一段话和两个按钮，撑到 720px 会让那段话一行横跨整屏。 */
.modal-narrow { width: min(460px, calc(100vw - 32px)); }
/* 换行要保住：确认文案是分段写的（「会发生什么」/「不会发生什么」各一段），
   挤成一坨读不出层次。white-space: pre-line 让 
 照原样断行。 */
.confirm-body { margin: 0; font-size: var(--t2); line-height: 1.65; color: var(--fg); white-space: pre-line; }
/* 不可撤销的动作用危险色确认键 —— 和「移除」这种可恢复的区分开。 */
.modal-actions .danger-button { background: var(--brick); border-color: var(--brick); color: #fff; }
.modal-actions .danger-button:hover { filter: brightness(1.08); }
.account-detail-modal { width: min(560px, calc(100vw - 32px)); }
.detail-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); }
.detail-meta-item { background: var(--panel); padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
.detail-meta-item span { font-family: var(--font-mono); font-size: var(--t0); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-faint); }
.detail-meta-item b { font-size: var(--t3); font-weight: 600; }
.credential-reveal-panel { border: 1px solid var(--line); background: var(--sunk); padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.detail-section-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.detail-section-heading span { font-size: var(--t3); font-weight: 600; }
.detail-section-heading small { font-size: var(--t1); color: var(--fg-faint); }
.reveal-row { display: grid; grid-template-columns: 70px 1fr auto auto; gap: 10px; align-items: center; }
.reveal-row span { font-size: var(--t2); color: var(--fg-dim); }
.reveal-row code { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg); background: var(--ground); padding: 5px 9px; border: 1px solid var(--line); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reveal-field, .copy-revealed { padding: 4px 10px; height: 28px; font-size: var(--t2); }

/* ══════════════════════════════════════════════════════════════════════
   空状态 —— 说清原因和下一步
   ══════════════════════════════════════════════════════════════════════ */
.empty-state { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 24px 18px; color: var(--fg-dim); font-size: var(--t3); }
.empty { display: flex; flex-direction: column; align-items: flex-start; gap: 9px; padding: 26px 20px; }
.empty .glyph { font-family: var(--font-mono); font-size: var(--t6); color: var(--fg-faint); }
.empty h4 { font-size: var(--t4); }
.empty p { margin: 0; color: var(--fg-dim); font-size: var(--t3); max-width: 46ch; }

/* ══════════════════════════════════════════════════════════════════════
   Toast
   ══════════════════════════════════════════════════════════════════════ */
.toast-region { position: fixed; top: 16px; right: 16px; z-index: 100; display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
.toast {
  pointer-events: auto; min-width: 200px; max-width: 340px; padding: 10px 14px;
  border: 1px solid var(--line-strong); background: var(--raised); color: var(--fg);
  font-size: var(--t2); border-left: 2px solid var(--signal); box-shadow: 0 12px 30px rgba(0, 0, 0, .5);
  animation: toast-in .2s var(--ease);
}
.toast.success { border-left-color: var(--jade); }
.toast.error { border-left-color: var(--brick); }
.toast.info { border-left-color: var(--slate); }
@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ══════════════════════════════════════════════════════════════════════
   响应式
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 1080px) {
  .overview-grid, .overview-grid.lower, .overview-grid.activity,
  .files-grid, .settings-grid, .solver-keys { grid-template-columns: 1fr; }
  /* 窄屏下图表回到整行；.unit-grid 是定宽内容，横向滚动而不是压扁格子。 */
  .chart-panel .activity-chart { overflow-x: auto; }
  .split-workspace { grid-template-columns: 1fr; }
  .readouts, .metric-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 820px) {
  .app-shell { grid-template-columns: 1fr; }
  .sidebar {
    position: fixed; left: 0; top: 0; bottom: 0; width: 230px; transform: translateX(-100%);
    transition: transform .2s var(--ease); box-shadow: 0 0 40px rgba(0, 0, 0, .6);
  }
  .sidebar.open { transform: none; }
  .menu-toggle { display: inline-flex; }
  .cap { grid-template-columns: 1fr; }
  .cap-verdict { border-right: none; border-bottom: 1px solid var(--line); }
}
@media (max-width: 560px) {
  .readouts, .metric-grid { grid-template-columns: 1fr; }
  .content-scroll { padding: 16px 14px 32px; }
}

/* 尊重「减少动效」 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════
   主题切换（功能性 light 覆盖）
   dark 是设计本体；main.js 的切换按钮仍是可用控件，所以提供一套一致的
   浅色映射。语义（signal/jade/brick/slate）保持不变，只换地/面/字。
   ══════════════════════════════════════════════════════════════════════ */
/* 底纹在浅色下必须压下去：同样的不透明度在深底上是余光里的纹理，
   在浅底上就是铺满全屏的方格纸，抢过面板本身。 */
:root[data-theme="light"] .login-field,
body[data-theme="light"] .login-field { opacity: .16; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .login-field { opacity: .16; }
}

:root[data-theme="light"], body[data-theme="light"] {
  /* ── 工程纸 ────────────────────────────────────────────────────────
     底不是白，是一张暖灰的工程纸：白底把这套界面压成通用后台，暖灰让
     墨色和三态色都沉得住。深色那套（:root）一个值都没动 —— 它的对比度
     是算过的，而且 redesign-tokens 那条守卫把它逐值钉住了。

     ★ 每个承载文字的色都重算过对比度 ★ 括号里是它对 --panel #FBFAF6 的比值，
     阈值 AA 4.5:1。写在这里是因为换底色最容易悄悄破坏的就是这个。 */
  --ground:      #F2F0EA;
  --panel:       #FBFAF6;
  --raised:      #EAE7DD;
  --sunk:        #E4E0D6;

  /* 唯一交互强调色：深琥珀。比原来的 #B26B12 更沉，因此做面做字是同一个值，
     不必再拆出 signal-dim 那一档（5.66:1）。 */
  --signal:      #8A5A08;
  --signal-hi:   #6E4706;
  --signal-dim:  #8A5A08;
  --signal-wash: rgba(138, 90, 8, .10);

  /* 语义三态：绿 6.24:1 / 砖红 7.26:1 —— 都在纸底上沉得住，不刺眼 */
  --jade:        #146B52;
  --jade-wash:   rgba(20, 107, 82, .12);
  --brick:       #9E2A1E;
  --brick-wash:  rgba(158, 42, 30, .10);
  /* 「未知 / 未测」刻意去饱和。4.83:1 是对 --ground 算的（比对 panel 更严），
     因为最小的那批字（表头、eyebrow、分页）常直接落在 ground 上。 */
  --slate:       #656A71;
  --slate-wash:  rgba(101, 106, 113, .10);

  --fg:          #15171A;
  --fg-dim:      #4A5057;   /* 7.66:1 */
  --fg-faint:    #656A71;   /* 5.27:1 对 panel、4.83:1 对 ground */
  --on-signal:   #FBFAF6;   /* 深琥珀实心块上的字：纸色，不是纯白 */
  --on-brick:    #FBFAF6;

  /* 线要看得见 —— 这套设计靠细线分层不靠阴影，α 太低时面板边界会消失 */
  --line:        rgba(21, 23, 26, .13);
  --line-strong: rgba(21, 23, 26, .27);
}

/* ══ 异步容器预留终态高度 ══════════════════════════════════════════════
   这些容器在取数期间是空的（或只有一行「加载中」），填充之后才有真实高度。
   不预留的话每次刷新都会塌一次再弹回来，而概览的轮询在生产是 3 秒一次——
   页面每 3 秒抖一下。

   用 min-height 不用 height：厂商数量会变（生产 3 家代理、2 家打码），写死高度
   会在多一家时把内容裁掉。这些数字不是拍的，是各自终态的实际高度：
   产能面板四行 + 结论列、活动图 118px 见 .unit-grid、余量面板每家一行约 56px。 */
#capacity-body { min-height: 132px; }
.chart-panel .activity-chart { min-height: 150px; }
#quota-list, #solver-list { min-height: 116px; }

/* ══ [A1] 打码与流量、测活配置（设计稿第 08、09 节） ══════════════════════════════ */

.vic { width: 18px; height: 18px; flex: none; background-size: contain; background-repeat: no-repeat; background-position: center; image-rendering: -webkit-optimize-contrast; }
.vname { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.vname a { color: inherit; text-decoration: none; border-bottom: 1px dotted transparent; }
.sw-row:hover .vname a { border-bottom-color: var(--signal-dim); }
.vname .ext { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); opacity: 0; transition: opacity .13s var(--ease); white-space: nowrap; }
.sw-row:hover .vname .ext { opacity: 1; }
.vic-capsolver { background-image: url("./icons/capsolver.png"); }
.vic-capmonster { background-image: url("./icons/capmonster.png"); }
.vic-iproyal { background-image: url("./icons/iproyal.png"); }
.vic-ipipbright { background-image: url("./icons/ipipbright.png"); }
.vic-711proxy { background-image: url("./icons/711proxy.png"); }

/* 可点切换行：打码与代理同一套交互。单击 = 设为当前；右击 = 加速器菜单；双击无语义。 */
.sw-list { display: flex; flex-direction: column; }
.sw-row { display: grid; grid-template-columns: 12px 1fr auto auto; align-items: center; gap: 13px; padding: 12px 14px; border: 1px solid transparent; border-bottom: 1px solid var(--line); cursor: pointer; transition: background .13s var(--ease), border-color .13s var(--ease); position: relative; }
.sw-row:last-child { border-bottom: 1px solid var(--line); }
.sw-row:hover { background: var(--raised); border-color: var(--line-strong); }
.sw-row:hover .hint { opacity: 1; }
.sw-row .pip { width: 8px; height: 8px; border: 1px solid var(--line-strong); border-radius: 50%; align-self: start; margin-top: 3px; }
.sw-row.on { background: var(--signal-wash); border-color: var(--signal-dim); cursor: default; }
.sw-row.on .pip { background: var(--signal); border-color: var(--signal); box-shadow: 0 0 0 3px var(--signal-wash); }
/* 过期/缺密钥的行不是禁用：仍可点，点了走补密钥/补 token 再切，而不是静默失败。 */
.sw-row.needs-key:hover, .sw-row.expired:hover { background: var(--brick-wash); border-color: var(--brick); }
.sw-row .nm { display: flex; flex-direction: column; gap: 3px; min-width: 0; font-size: var(--t3); font-weight: 600; color: var(--fg); }
.sw-row .nm small { font-weight: 400; font-size: var(--t1); color: var(--fg-faint); font-family: var(--font-mono); white-space: normal; }
.sw-row .fig { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg-faint); text-align: right; white-space: nowrap; }
.sw-row .fig b { display: block; color: var(--fg); font-weight: 700; font-family: var(--font-num); font-size: var(--t5); line-height: 1.05; font-variant-numeric: tabular-nums; }
/* 未取到：不亮读数、不填 0。 */
.sw-row .fig.unknown b { color: var(--fg-faint); font-family: var(--font-mono); font-size: var(--t3); font-weight: 500; }
.sw-row .hint { opacity: 0; transition: opacity .13s var(--ease); font-family: var(--font-num); font-size: var(--t1); letter-spacing: .1em; text-transform: uppercase; color: var(--signal); white-space: nowrap; }
.sw-row.on .hint { opacity: 1; color: var(--signal-dim); }
.sw-row.needs-key .hint, .sw-row.expired .hint { color: var(--brick); opacity: 1; }

/* 代理行的余量条：已知按 pct 画，未知画斜纹空条。
   绝不 0% 实心、绝不满条——读不出流量的厂商画一根看似「余量 100%」的满条是本项目最贵的缺陷。 */
.sw-track { height: 3px; background: var(--sunk); margin-top: 2px; }
.sw-track > i { display: block; height: 3px; background: var(--jade); }
.sw-track > i.warn { background: var(--signal); }
.sw-track > i.low { background: var(--brick); }
.sw-track.unknown { background: repeating-linear-gradient(90deg, var(--line-strong) 0 3px, transparent 3px 6px); }

/* 当前厂商可用 N 次（按次计，读不出显示未取到、不显 0） */
.sw-total { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 12px 14px 6px; padding-top: 11px; border-top: 1px solid var(--line-strong); }
.sw-total .k { font-size: var(--t2); color: var(--fg-dim); }
.sw-total .v { font-family: var(--font-num); font-size: var(--t6); font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; color: var(--fg); }
.sw-total .v small { font-family: var(--font-ui); font-size: var(--t2); font-weight: 500; color: var(--fg-dim); }
.sw-total .v.unknown { color: var(--fg-faint); font-family: var(--font-mono); font-size: var(--t4); font-weight: 500; }

/* 当前厂商读不出/无密钥时的实况提示。只在能实证时出现，
   不臆造「设置≠生产」——前端拿不到生产 env 的真值，无法判定分歧。 */
.sw-warn { margin: 11px 14px; border: 1px solid var(--brick); background: var(--brick-wash); padding: 9px 11px; font-size: var(--t2); color: var(--fg-dim); line-height: 1.5; }
.sw-warn b { color: var(--brick); font-weight: 600; }
.sw-warn .lnk { color: var(--signal); cursor: pointer; white-space: nowrap; }
.sw-warn .lnk:hover { text-decoration: underline; }

/* 右击加速器菜单——每一项都是真动作（设为当前 / 重新查余额 / 打开站点 / 去设置填密钥） */
.sw-ctx { position: fixed; z-index: 60; min-width: 178px; background: var(--raised); border: 1px solid var(--line-strong); box-shadow: 0 14px 34px rgba(0,0,0,.55); padding: 4px; }
.sw-ctx .ci { padding: 7px 10px; font-size: var(--t2); color: var(--fg-dim); cursor: pointer; display: flex; justify-content: space-between; gap: 14px; align-items: center; }
.sw-ctx .ci:hover { background: var(--signal-wash); color: var(--fg); }
.sw-ctx .ci kbd { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.sw-ctx .ci.dis { opacity: .45; cursor: not-allowed; }
.sw-ctx .cd { height: 1px; background: var(--line); margin: 4px 0; }

/* 第 08 节：删掉假开关之后，测活配置去哪了——指路块（不是隐藏 scheduler） */
.liveness-guide { border: 1px solid var(--line); background: var(--panel); padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
.liveness-guide .lg-row { display: grid; grid-template-columns: 22px 1fr; gap: 12px; align-items: baseline; }
.liveness-guide .lg-row .idx { font-family: var(--font-num); font-size: var(--t3); font-weight: 700; color: var(--signal); }
.liveness-guide h4 { font-size: var(--t3); color: var(--fg); margin: 0 0 4px; }
.liveness-guide p { margin: 0; font-size: var(--t2); color: var(--fg-dim); line-height: 1.62; }
.liveness-guide .lg-actions { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 6px; }
.liveness-guide .lg-note { font-size: var(--t2); color: var(--fg-faint); line-height: 1.58; border-top: 1px solid var(--line); padding-top: 10px; }
.liveness-guide .lg-note b { color: var(--slate); font-weight: 600; }

/* ══ [A1] 区块结束 ══ */

/* ══ [A2] 资格探测勾选与任务表单 enum 渲染（设计稿第 10 节） ══════════════════════════════
   本区块只由 A2 修改。改既有规则要就地改（那属于谁的视图谁改），
   新增规则写在这里。 */

/* enum[] 支付方式勾选探测：在任务对话框里占满整行。原生 checkbox 视觉隐藏，
   用 .bx 方框呈现勾选态，勾选态由 :checked 兄弟选择器纯 CSS 驱动（不靠 JS 切类），
   所以默认勾选与首次渲染就正确。 */
.probe-field { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 12px; }
.probe-set { border: 1px solid var(--line); }
.probe-hd {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 9px 12px; background: var(--sunk); border-bottom: 1px solid var(--line);
  font-size: var(--t1); letter-spacing: .16em; text-transform: uppercase; color: var(--fg-faint);
}
.probe-hd em {
  font-style: normal; letter-spacing: 0; text-transform: none;
  font-size: var(--t1); color: var(--fg-dim);
}
.probe-i {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--line); cursor: pointer;
  position: relative; transition: background .12s var(--ease);
}
.probe-i:last-child { border-bottom: 0; }
.probe-i:hover { background: var(--raised); }
.probe-i.free { background: var(--jade-wash); }
.probe-check { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.probe-i .bx {
  width: 14px; height: 14px; flex: none; position: relative;
  border: 1px solid var(--line-strong); background: var(--sunk); border-radius: 2px;
}
.probe-check:checked ~ .bx { border-color: var(--signal); background: var(--signal); }
.probe-check:checked ~ .bx::after {
  content: "\2713"; position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center; font-size: var(--t0); font-weight: 700; color: var(--ground);
}
.probe-check:focus-visible ~ .bx { outline: 2px solid var(--signal); outline-offset: 2px; }
.probe-i .nm { font-size: var(--t3); font-weight: 500; color: var(--fg); }
.probe-check:checked ~ .nm { color: var(--signal); }
.probe-i .cc {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--t1);
  letter-spacing: .03em; color: var(--fg-dim);
}
.probe-i .why {
  flex-basis: 100%; padding-left: 24px; font-size: var(--t1); line-height: 1.5; color: var(--fg-faint);
}
.probe-i .why b { color: var(--jade); font-weight: 500; }
.probe-i .why i { font-style: normal; color: var(--slate); }
.probe-note {
  padding: 10px 12px; background: var(--sunk); border-top: 1px solid var(--line);
  font-size: var(--t1); line-height: 1.55; color: var(--fg-dim);
}
.probe-note b { color: var(--signal); font-weight: 500; }

/* 查支付方式那一格。

   ★ 这一段的全部目的是「不用滑」★ 上一版是 15 个国家码、一个两行、
   竖着排 —— 对话框里要滑好几屏。现在是自适应网格，一行三到四个，
   11 种方式总共三四行就完了。出处那句话改成 title + 选中后才展开：
   它在「要不要点」时有用，一直摊开就只是高度。

   品牌色只出现在 18px 的标记里，不溢到边框和底色上 —— 这一页只有一个
   交互色（信号琥珀），选中与否归它管；品牌色只负责「这是哪一种」。 */
.pm-field { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 12px; }
.pm-groups { border: 1px solid var(--line); }
.pm-grp-hd {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px; background: var(--sunk); border-bottom: 1px solid var(--line);
  font-size: var(--t1); letter-spacing: .16em; text-transform: uppercase; color: var(--fg-faint);
}
.pm-grp-hd em {
  font-style: normal; letter-spacing: 0; text-transform: none;
  font-size: var(--t1); color: var(--fg-dim);
}
.pm-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 6px; padding: 8px 10px;
}
.pm-pick {
  position: relative; display: flex; align-items: center; gap: 7px;
  padding: 6px 8px; border: 1px solid var(--line); cursor: pointer;
  transition: background .12s var(--ease), border-color .12s var(--ease);
}
.pm-pick:hover { background: var(--raised); }
.pm-pick.on { border-color: var(--signal); background: var(--signal-wash); }
.pm-pick-check { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.pm-pick-check:focus-visible ~ .pm-pick-ico { outline: 2px solid var(--signal); outline-offset: 2px; }
.pm-pick-ico { display: inline-flex; flex: none; color: var(--fg-faint); }
.pm-ico { display: block; }
.pm-pick-nm {
  flex: 1 1 auto; min-width: 0; font-size: var(--t2); font-weight: 500; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pm-pick.on .pm-pick-nm { color: var(--signal); }
.pm-pick-flag { flex: none; border-radius: 50%; }
.pm-pick-cc {
  flex: none; font-family: var(--font-mono); font-size: var(--t0);
  letter-spacing: .03em; color: var(--fg-dim);
}
/* 「+N」：这一种不止那一个国家。PayPal 是目前唯一一个。 */
.pm-pick-more {
  flex: none; padding: 0 3px; border: 1px solid var(--line-strong); border-radius: 2px;
  font-family: var(--font-mono); font-size: var(--t0); line-height: 13px; color: var(--fg-faint);
}
/* 「另问一个国家」那一排：没有方式名，格子可以更窄。 */
.pm-grid-cc { grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); }
.pm-pick.is-country .pm-pick-nm { font-family: var(--font-mono); font-size: var(--t1); }
/* 选中那几种的出处。它跟着勾选长出来，所以没选时一点高度不占。 */
.pm-picked {
  display: flex; flex-direction: column; gap: 6px;
  padding: 9px 12px; border: 1px solid var(--line); background: var(--sunk);
}
.pm-note-row { display: flex; align-items: flex-start; gap: 7px; font-size: var(--t1); line-height: 1.5; }
.pm-note-row .pm-ico { flex: none; margin-top: 1px; }
.pm-note-row b { flex: none; font-weight: 600; color: var(--fg); }
.pm-note-row span { color: var(--fg-faint); }

/* 预算条：勾选 → 去重国家数 → 建单次数。用 signal 底色，因为它承载的是本次操作
   会在 OpenAI 留多少记录这个代价判断，不是语义状态。 */
.budget {
  border: 1px solid var(--signal-dim); background: var(--signal-wash);
  padding: 13px 15px; display: flex; flex-direction: column; gap: 10px;
}
.budget .eq { font-family: var(--font-mono); font-size: var(--t3); line-height: 1.5; color: var(--fg); }
.budget .eq b { color: var(--signal); font-weight: 600; }
.budget .eq span { color: var(--fg-faint); }
.budget-caps { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: var(--t1); color: var(--fg-dim); }
.budget-caps b { color: var(--fg); font-weight: 600; }
.budget .warn {
  display: flex; gap: 8px; align-items: flex-start; font-size: var(--t1); line-height: 1.5;
  color: var(--fg-dim); border-top: 1px solid var(--signal-dim); padding-top: 10px;
}
.budget .warn > span { flex: 1; min-width: 0; }
.budget .warn b { color: var(--signal); font-weight: 500; }
.budget .warn::before {
  content: "!"; flex: none; width: 15px; height: 15px; border: 1px solid var(--signal);
  color: var(--signal); font-family: var(--font-mono); font-size: var(--t1); display: flex;
  align-items: center; justify-content: center; margin-top: 1px;
}
/* ══ [A2] 区块结束 ══ */

/* ══ [A3] 任务队列 / 邮箱池 / 手机号池 接组件层 ══════════════════════════════
   本区块只由 A3 修改。改既有规则要就地改（那属于谁的视图谁改），
   新增规则写在这里。 */

/* 任务队列：失败原因前置到行内（设计稿 Screen 03「失败原因前置」）。
   砖红、稍小，和「结果」列同宽度约束下自然折行；不是判死红块，是一行说明文字。 */
.fail-reason { color: var(--brick); font-size: var(--t2); line-height: 1.35; }
/* ══ [A3] 区块结束 ══ */

/* ══ [A4] 文件与产物 / 域名计划 接组件层 ══════════════════════════════
   本区块只由 A4 修改。改既有规则要就地改（那属于谁的视图谁改），
   新增规则写在这里。 */

/* 域名计划操作列：一组文字按钮，留出可点的间距，不挤成一坨。 */
/* <td> 不能设成 flex：那样它就不再是表格单元格，行内垂直对齐随之失效——
   实测这一格底边比同行其他格高 17–18px。横排交给内部的 span（F19）。 */
.schedule-actions .sa-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* 产物 / 上传行的元信息（类型 · 体积 · 时间）等宽去饱和，不被长文件名挤没。
   「未取到」是原样文本，绝不会被 CSS 补成一条看着像「满」的东西——.quota-fill
   那个不声明 width 就画满父容器的坑，这里靠文本而不是进度条从根上避开。 */
.artifact-item small, .upload-item small { flex: none; }

/* 空状态的下一步提示与主句同列堆叠（.empty-state 本就是 flex column）。 */
.empty-state > span { color: var(--fg-dim); }
/* ══ [A4] 区块结束 ══ */

/* ── F06/F07 页头 ─────────────────────────────────────────────────────
   一页一名：页头的 <h2> 等于侧栏文字，顶栏只留面包屑。副标改成由数据填的
   事实句——营销句对每天用它的两三个人没有信息量。 */
.page-fact { margin: 4px 0 0; font-family: var(--font-mono); font-size: var(--t2);
  color: var(--fg-dim); font-variant-numeric: tabular-nums; letter-spacing: .01em; }

/* ── F16 数字输入按 ch 定宽 ───────────────────────────────────────────
   「同时运行任务数」原来是 405px 的框装两位数。ch 是当前字体下 0 的宽度，
   用它定宽，框有多大就等于能填几位。 */
.num-2 { width: 7ch; } .num-3 { width: 8ch; } .num-4 { width: 9ch; }
.field-help { font-size: var(--t1); color: var(--fg-faint); min-height: 15px; }
.setting-field.readonly-note p { margin: 0; font-size: var(--t2); color: var(--fg-dim); line-height: 1.5; }

/* ── F15 「怎么做」两行。论证留在设计稿，界面只放动作。 ─────────────── */
.how-rows { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); }
.how-row { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto; gap: 14px;
  align-items: center; padding: 12px 14px; background: var(--panel); }
.how-row > b { font-size: var(--t3); font-weight: 600; }
.how-row > p { margin: 0; font-size: var(--t2); color: var(--fg-dim); }
.how-note { margin: 12px 0 0; font-size: var(--t1); color: var(--fg-faint); line-height: 1.6; }
.chip-unknown { border: 1px dashed var(--line-strong); border-radius: var(--r); padding: 2px 8px;
  color: var(--slate); font-variant-numeric: tabular-nums;
  background: repeating-linear-gradient(135deg, transparent 0 3px, var(--slate-wash) 3px 6px); }

@media (max-width: 860px) {
  .how-row { grid-template-columns: 44px minmax(0, 1fr); }
  .how-row > button { grid-column: 2; justify-self: start; }
  .pagination { flex-wrap: wrap; }
  .pg-range { margin-right: 0; flex: 1 0 100%; }
}

/* ── 任务「结果」列（F12 / F13）─────────────────────────────────────────
   失败给中文原因 + 下一步；成功给「20 个 · 成功 18」；在跑给进度；没有结果留空。
   未知 code 标「未翻译」并显示后端原文——静默吞掉没见过的 code，等于把
   「出了没人预料到的错」渲染成「没事」。 */
.job-outcome { font-size: var(--t2); line-height: 1.45; }
.job-outcome .reason { color: var(--brick); }
.job-outcome .next-step { color: var(--fg-faint); }
.job-outcome .tally { font-family: var(--font-mono); color: var(--fg-dim); font-variant-numeric: tabular-nums; }
.job-outcome .untranslated {
  font-family: var(--font-mono); font-size: var(--t0); color: var(--slate);
  border: 1px dashed var(--line-strong); border-radius: var(--r); padding: 0 4px; margin-left: 4px;
}
.data-table td.wrap { white-space: normal; }

/* ── 任务表的列宽纪律 ───────────────────────────────────────────────────
   抽屉一开，表格只剩半屏，六列由浏览器自由分配的结果是「结果」列被挤成
   三个字一行、一条原因折成八行。这正是本稿批评的「列宽没有纪律」——稿子
   自己实现的界面不能犯。前五列按内容定宽，剩下的全给「结果」。 */
.jobs-table { table-layout: fixed; }
/* 这张表最密，内边距收到 9px（稿的 M3 界面稿用的是 8px）。省下的 36px 全部
   进「结果」列——那一列装的是失败原因，是这张表上唯一需要读完整句子的地方。 */
.jobs-table td, .jobs-table th { padding-left: 9px; padding-right: 9px; }
.jobs-table .c-id { width: 104px; }
.jobs-table .c-type { width: 124px; }
.jobs-table .c-state { width: 98px; }
.jobs-table .c-start { width: 112px; }
.jobs-table .c-dur { width: 78px; }
.jobs-table td, .jobs-table th { overflow: hidden; text-overflow: ellipsis; }
.jobs-table td.wrap, .jobs-table td.job-outcome { white-space: normal; overflow: visible; }

/* 抽屉开着时，操作者正在读的是被选中那条的日志，别条的原因是次要信息：
   收成两行 + 省略号，全文进 title。不收的话它会把行撑到八行高，把整张表
   顶出视口——比看不全更糟。 */
.split-workspace.has-drawer .job-outcome .oc {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}

/* ══════════════════════════════════════════════════════════════════════
   账号表与详情抽屉（M4 · 拍板 D2）
   ══════════════════════════════════════════════════════════════════════ */

/* 列宽纪律（F11）。原来九列平分 2200px，一行内眼距 1000px+，而「套餐」「出口」
   两列在生产 25 行里全是空的却每行照画一个「—」。现在标识列按等宽字符数定宽，
   **只有邮箱列弹性**——它是这张表上唯一长度不可预知、也唯一必须看全的东西。 */
.accounts-table { table-layout: fixed; }
.accounts-table td, .accounts-table th { padding-left: 9px; padding-right: 9px; }
.accounts-table .c-stripe { width: 3px; }
.accounts-table .c-check { width: 38px; }
.accounts-table .c-type { width: 96px; }
.accounts-table .c-plan { width: 92px; }
.pay-method { display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--accent-weak, rgba(90,140,110,.14)); color: var(--fg); font-weight: 600; font-size: var(--t0); }
.accounts-table .c-elig { width: 122px; }
.accounts-table .c-cred { width: 84px; }
/* 发之前的出口国对账条。不匹配是砖色 —— 它不是提示，是代价警告：
   2026-09-11 实测这个形状的一批 25 个号全灭，25 条记录撤不回。 */
.match-bar {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  padding: 8px 10px; border: 1px solid var(--line); font-size: var(--t1); line-height: 1.6;
  color: var(--fg-dim);
}
.match-bar b { color: var(--fg); font-weight: 600; }
.match-bar em { font-style: normal; color: var(--fg-faint); }
.match-bar i { flex-basis: 100%; font-style: normal; color: var(--fg-faint); }
.match-bar.is-ok { border-color: var(--jade); background: var(--jade-wash); }
.match-bar.is-ok b { color: var(--jade); }
.match-bar.is-bad { border-color: var(--brick); background: var(--brick-wash); }
.match-bar.is-bad b { color: var(--brick); }
.match-bar.is-bad i { color: var(--fg-dim); }
.match-bar.is-unknown { border-color: var(--slate); background: var(--slate-wash); }
.match-cc {
  font-family: var(--font-mono); font-size: var(--t0); padding: 1px 5px;
  border: 1px solid var(--line-strong); color: var(--brick);
}
.match-cc.ok { color: var(--jade); border-color: var(--jade); }

/* 支付那一列：最多画三四个标记，窄一点就够。 */
.accounts-table .c-pay { width: 116px; }
.pay-cell { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.pay-mark { display: inline-flex; align-items: center; gap: 3px; }
.pay-mark .pm-ico { width: 14px; height: 14px; }
.pay-mark i {
  font-style: normal; font-family: var(--font-mono); font-size: var(--t0); color: var(--signal);
}
/* ★ 「没问成」和「只有通用两种」必须长得不一样 ★ 混为一谈会让人以为这个号
   真的什么都没有，而实际上代价已经付了（上游那条 checkout 记录撤不回）。 */
.pay-bad { font-size: var(--t1); color: var(--brick); }
.pay-plain { font-size: var(--t1); color: var(--fg-faint); }

/* 邮箱来源下拉：一个按钮 + 一扇带搜索的小面板。
   ★ 按钮上永远写着两件事 ★ 选的是哪一批、它还能出几个号 ——
   后者为 0 时变色，因为那意味着这一批发下去会当场被准入拒掉。 */
.batch-pick { position: relative; }
/* 邮箱来源的「号池 / 现买」切换：和下面那一格之间留一行的距离，别贴在一起。 */
#job-source-switch { margin-bottom: 8px; align-self: flex-start; }
.vendor-pick select { width: 100%; }
.batch-btn {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg-raise, transparent); font: inherit; text-align: left; cursor: pointer;
}
.batch-btn b { font-weight: 600; }
.batch-btn em, .batch-btn #job-batch-left { font-style: normal; font-size: var(--t0); color: var(--fg-faint); }
.batch-btn em.zero { color: var(--warn, #b3792b); }
.batch-menu {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 4px);
  padding: 8px; border: 1px solid var(--line); border-radius: 5px;
  background: var(--bg-raise, #fff); box-shadow: 0 6px 20px rgba(0,0,0,.12);
}
.batch-menu input[type=search] {
  width: 100%; margin-bottom: 6px; padding: 5px 8px;
  border: 1px solid var(--line); border-radius: 3px; font: inherit;
}
.batch-opts { max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.batch-opt {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 4px 8px;
  padding: 5px 8px; border: 0; border-radius: 3px; background: none;
  font: inherit; text-align: left; cursor: pointer;
}
.batch-opt:hover { background: var(--bg-hover, rgba(0,0,0,.04)); }
.batch-opt.on { background: color-mix(in srgb, var(--accent, #3b6ea5) 12%, transparent); }
.batch-opt .batch-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.batch-opt .batch-n { font-family: var(--font-mono); font-weight: 600; }
.batch-opt .batch-n.zero { color: var(--fg-faint); font-weight: 400; }
.batch-opt em { grid-column: 1 / -1; font-style: normal; font-size: var(--t0); color: var(--fg-faint); }
.batch-note { margin: 8px 0 0; font-size: var(--t0); color: var(--fg-faint); line-height: 1.5; }
.batch-empty { margin: 8px 4px; font-size: var(--t1); color: var(--fg-faint); }

/* 单据徽标：这条读数来自 0 元单还是全价单。
   ★ 两种必须一眼分得开，但不能抢方式图标的位置 ★ 方式（momo/gcash）是这一列
   存在的理由，单据是它的限定语 —— 所以用小字 + 淡边框，不用实心色块。
   全价单是「能出 momo 的那张」，给它一点暖色；0 元单给冷色。 */
.bill-tag {
  display: inline-flex; align-items: center; gap: 2px;
  margin-left: 6px; padding: 0 5px;
  border: 1px solid var(--line); border-radius: 3px;
  font-size: var(--t0); line-height: 16px; white-space: nowrap;
  color: var(--fg-faint);
}
.bill-tag b { font-weight: 600; font-family: var(--font-mono); }
.bill-tag.full { border-color: color-mix(in srgb, var(--warn, #b3792b) 45%, var(--line)); color: var(--warn, #8a5d1f); }
.bill-tag.zero { border-color: color-mix(in srgb, var(--accent, #3b6ea5) 40%, var(--line)); color: var(--accent, #3b6ea5); }
/* 抽屉里那一行：徽标跟在国家/币种后面，和方式 chips 之间留一口气。 */
.pm-row .bill-tag { margin-left: 8px; }
.accounts-table .c-alive { width: 120px; }
.accounts-table .c-reg { width: 104px; }
.accounts-table .cell-mail { overflow: hidden; }
.accounts-table .cell-mail strong,
.accounts-table .cell-mail small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.accounts-table tbody tr { cursor: pointer; }

/* 账号抽屉比任务页的日志抽屉窄：详情是窄列键值对，日志才需要宽度装 130 字符的
   行。稿的 M4 界面稿里这两栏也不是对半，是约 62/38。 */
#accounts-workspace.has-drawer { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }

.detail-drawer {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--sunk); border-left: 1px solid var(--line);
  animation: drawer-in .18s var(--ease);
}
.detail-drawer .drawer-body { flex: 1; overflow-y: auto; padding: 4px 0 8px; }
.dv-sec { padding: 12px 14px; border-bottom: 1px solid var(--line); }
.dv-sec:last-child { border-bottom: 0; }
.dv-sec > .eyebrow { display: block; margin-bottom: 8px; }
.dv-line { font-size: var(--t2); color: var(--fg); }
.dv-note { font-size: var(--t1); color: var(--fg-faint); margin-top: 6px; line-height: 1.5; }
.dv-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 3px 0; font-size: var(--t2); }
.dv-row > span { color: var(--fg-faint); flex: none; }
.dv-row > b { font-family: var(--font-mono); font-weight: 500; color: var(--fg); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 池路径这类长值截断就等于没显示（"batch_20260820/accounts/TamRossxyx" 一眼看
   不到尾巴，而尾巴恰恰是账号名）。给它一行 .wrap，换行而不是省略号。 */
.dv-row.wrap { align-items: flex-start; }
.dv-row.wrap > b { white-space: normal; overflow-wrap: anywhere; text-overflow: clip; text-align: right; }
.dv-code { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-dim); background: var(--sunk);
  padding: 1px 5px; border-radius: var(--r); overflow-wrap: anywhere; }

/* 六项资格逐项摊开。原来只有一句「已探测 · 无」，而后端给的是六个独立的三态
   判定——「哪几项测过、分别什么结果」收成一句就答不了（设计稿 §07：结论必须
   带上它的来源）。命中项带上活动 ID，那是真正能拿去用的东西。 */
/* 注册进度三步。到了 / 没到 / 不知道三态各有各的颜色——一个 failed 的号卡在
   「没到 OpenAI」和卡在「密码没设上」是两回事，要跑的补救任务也不一样。 */
.step-row { display: flex; flex-wrap: wrap; gap: 6px; }
.step { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px;
  border: 1px solid var(--line); border-radius: var(--r); font-size: var(--t1); color: var(--fg-dim); }
.step > i { font-style: normal; font-family: var(--font-mono); font-weight: 700; }
.step.ok { border-color: var(--line-strong); color: var(--fg); }
.step.ok > i { color: var(--jade); }
.step.bad { border-color: var(--brick); color: var(--fg); }
.step.bad > i { color: var(--brick); }
.step.unknown > i { color: var(--fg-faint); }

.elig-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px;
  background: var(--line); border: 1px solid var(--line); border-radius: var(--r); margin-top: 8px; }
.elig-row { display: flex; align-items: baseline; gap: 6px; padding: 5px 8px; background: var(--panel);
  font-size: var(--t1); min-width: 0; }
.er-name { font-family: var(--font-mono); color: var(--fg-dim); flex: none; }
.er-v { font-weight: 600; flex: none; }
.er-v.ok { color: var(--jade); }
.er-v.unknown { color: var(--fg-faint); }
/* 「未测」和「无」必须分得开：一个是没查过，一个是查过没有。 */
.er-v.idle { color: var(--fg-faint); opacity: .6; }
.er-note { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── 证据阶梯 ───────────────────────────────────────────────────────────
   「还活着吗」不是一个布尔，是一叠强度不同的证据。三行按级别从弱到强，
   每行说清：结论是什么、凭哪一级证据、什么时候测的。 */
.ladder { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); }
.lad-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px;
  align-items: center; padding: 7px 10px; background: var(--panel); font-size: var(--t1); }
.lad-mark { display: inline-flex; min-width: 0; }
.lad-name { font-family: var(--font-mono); color: var(--fg-dim); }
.lad-when { font-family: var(--font-mono); color: var(--fg-faint); font-variant-numeric: tabular-nums; }
/* 「未做」= 这一级能做但没跑。 */
.lad-row.is-idle .lad-name, .lad-row.is-idle .lad-when { color: var(--fg-faint); }
/* 「未接入」= 后端压根没有这一级的字段。和「未做」是两个结论，形态必须分开，
   否则读的人会等一个永远不会来的结果。 */
.lad-row.is-unavailable { background: repeating-linear-gradient(135deg,
  var(--panel) 0 4px, var(--sunk) 4px 8px); }
.lad-row.is-unavailable .lad-name, .lad-row.is-unavailable .lad-when {
  color: var(--slate); font-style: normal; }

/* ── 凭证点 ─────────────────────────────────────────────────────────────
   三态：有 = 实心，没有 = 空心描边，读不到 = 斜纹。 */
.cred-line { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: var(--t2); }
.dv-country { font-family: var(--font-mono); color: var(--fg); }
.cred-dot { display: inline-flex; align-items: center; gap: 5px; color: var(--fg-dim); font-size: var(--t1); }
.cred-dot i { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.cred-dot.ok i { background: var(--jade); }
.cred-dot.bad i { border: 1px solid var(--brick); background: transparent; }
.cred-dot.unknown i { border: 1px dashed var(--slate); background: transparent; }
.cred-dot.unknown { color: var(--slate); }

.detail-drawer .reveal-row { display: grid; grid-template-columns: 68px minmax(0, 1fr) auto auto;
  gap: 8px; align-items: center; padding: 4px 0; font-size: var(--t1); }
.detail-drawer .reveal-row > span { color: var(--fg-faint); }
.detail-drawer .reveal-row code { font-family: var(--font-mono); font-size: var(--t1);
  color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.drawer-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; border-top: 1px solid var(--line); background: var(--panel); }
.drawer-actions .danger { margin-left: auto; }

@media (max-width: 1100px) {
  #accounts-workspace.has-drawer { grid-template-columns: minmax(0, 1fr); }
}

/* 未选中时的本页分布。条形只表示占比，数字才是结论——所以数字用等宽对齐，
   条形不带刻度也不标百分比（那会让人以为它是个可比的度量）。 */
.detail-drawer.is-summary .drawer-heading { border-bottom: 1px solid var(--line); }
.sum-row { display: grid; grid-template-columns: 104px minmax(0, 1fr) 34px; gap: 10px;
  align-items: center; padding: 4px 0; font-size: var(--t1); color: var(--fg-dim); }
.sum-bar { height: 4px; background: var(--raised); border-radius: 2px; overflow: hidden; }
.sum-bar > i { display: block; height: 100%; width: 0; background: var(--slate); }
.sum-row > b { font-family: var(--font-num); font-size: var(--t3); font-weight: 600;
  color: var(--fg); text-align: right; font-variant-numeric: tabular-nums; }

/* 「最近结果」（M6）。三列：类型 / 结果 / 多久前。在跑那条置顶并带脉冲——
   脉冲是真状态（只有 running 才有），不是装饰。 */
.recent-list { display: flex; flex-direction: column; }
.recent-row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) auto;
  gap: 12px; align-items: center; width: 100%;
  padding: 9px 18px; background: transparent; border: 0; border-top: 1px solid var(--line);
  text-align: left; cursor: pointer; font-size: var(--t2); color: var(--fg-dim);
}
.recent-row:first-child { border-top: 0; }
.recent-row:hover { background: var(--raised); }
.rc-type { display: inline-flex; align-items: center; gap: 7px; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-out { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-ago { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-faint);
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.rc-type .pulse { width: 6px; height: 6px; border-radius: 50%; background: var(--signal);
  flex: none; animation: rc-pulse 1.6s var(--ease) infinite; }
@keyframes rc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .rc-type .pulse { animation: none; } }

/* 出口轮换：注册流实际走哪家代理出口。它和上面每一家的「余量/会话」是两个
   问题——余量说的是「还剩多少」，这里说的是「现在轮到谁」。界面上原来只答了
   前一个，用户的原话是「代理界面没有显示当前选择 这个还能不选择？」。 */
.exit-rotation { border: 1px solid var(--line); background: var(--sunk); padding: 12px 15px; display: flex; flex-direction: column; gap: 9px; margin-bottom: 12px; }
.exit-rotation-head { display: flex; align-items: baseline; gap: 10px; }
.exit-rotation-head strong { font-size: var(--t2); font-weight: 600; }
.exit-rotation-list { display: flex; flex-direction: column; gap: 6px; }
.exit-row { display: flex; align-items: center; gap: 10px; font-family: var(--font-mono); font-size: var(--t1); }
.exit-row .exit-name { min-width: 104px; font-weight: 600; }
.exit-row .exit-note { color: var(--fg-faint); flex: 1; }
.exit-row.in-rotation .exit-name { color: var(--fg); }
.exit-row.out .exit-name { color: var(--fg-faint); }
.exit-row.cooling .exit-note { color: var(--brick); }
.exit-toggle { font-family: inherit; font-size: var(--t1); padding: 2px 9px; border: 1px solid var(--line); background: transparent; color: var(--fg-dim); cursor: pointer; }
.exit-toggle:hover:not(:disabled) { border-color: var(--fg-dim); color: var(--fg); }
.exit-toggle:disabled { opacity: .45; cursor: not-allowed; }

/* 导出格式选择：贴着按钮，不另起一行 —— 它是那个按钮的参数，不是独立的设置。 */
.inline-select { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t1); }
.inline-select select { font-family: inherit; font-size: var(--t1); padding: 3px 6px; border: 1px solid var(--line); background: var(--sunk); color: var(--fg); }

/* 任务对话框里那句「暂不可用：X（原因）」。它解释的是选项为什么被禁用 ——
   「不能跑」是症状，「哪个端点没配」才是能据以行动的事实。 */
.form-hint { font-size: var(--t1); line-height: 1.5; margin: 2px 0 0; }

/* 含密钥的产物：一眼看得出来。一份 secrets 导出就是整批号的钥匙，
   它在列表里原来和一份普通清单长得一模一样。 */
.artifact-item.artifact-secret { border-left: 3px solid var(--danger, #d64545); padding-left: 9px; }
.artifact-item .secret-flag {
  display: inline-block; margin-right: 6px; padding: 0 6px;
  border-radius: 4px; font-size: var(--t0); line-height: 18px;
  background: color-mix(in srgb, var(--danger, #d64545) 14%, transparent);
  color: var(--danger, #d64545);
}

/* ── 换绑工作台 ──────────────────────────────────────────────────────────
   两栏配对 + 配对表 + 处置。类名一律 rb- 前缀：这一页的结构和别的页不一样
   （别处是一张表，这里是两栏对着挑），复用 list-view 那套类会让两边的样式
   互相牵制 —— 改这一页的行高把账号页的表也带歪，是这个仓库前端最常见的返工。 */
.rb-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; }
@media (max-width: 900px) { .rb-pair { grid-template-columns: 1fr; } }

.rb-box {
  border: 1px solid var(--line); background: var(--panel);
  display: flex; flex-direction: column; min-width: 0;
}
.rb-box + .rb-box, .rb-eq { margin-bottom: 14px; }
.rb-box-h {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--line); background: var(--raised);
}
.rb-box-h h3 { margin: 0; font-size: var(--t3); font-weight: 600; color: var(--fg); }
.rb-n { font-family: var(--font-mono); font-size: var(--t1); color: var(--fg-faint); }

/* 列表本身滚动，页面不跟着长 —— 两栏一高一矮时配对表会被推出视野。 */
.rb-list { max-height: 420px; overflow-y: auto; }

.rb-row {
  display: grid; grid-template-columns: 18px 1fr; gap: 2px 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--line); cursor: pointer;
}
.rb-row:hover { background: var(--sunk); }
.rb-row[data-ready="0"] { opacity: .55; cursor: not-allowed; }
.rb-row[data-ready="0"]:hover { background: none; }
.rb-row[data-on] { background: var(--sunk); }
.rb-box-tick {
  grid-row: 1 / span 2; align-self: center;
  width: 15px; height: 15px; border: 1px solid var(--line-strong); background: var(--panel);
  display: flex; align-items: center; justify-content: center;
}
.rb-row[data-on] .rb-box-tick { background: var(--signal); border-color: var(--signal); }
.rb-seq {
  font-family: var(--font-mono); font-size: var(--t0); font-weight: 600;
  color: var(--on-signal); line-height: 1;
}
.rb-seq-flat { color: var(--fg-faint); }
.rb-addr {
  font-family: var(--font-mono); font-size: var(--t2); color: var(--fg);
  overflow-wrap: anywhere; min-width: 0;
}
.rb-meta, .rb-why { grid-column: 2; font-size: var(--t1); }
.rb-meta { color: var(--fg-faint); }
/* 不可选的理由用砖红：它不是装饰，是这一行**为什么点不动**的唯一答案。 */
.rb-why { color: var(--brick); }
.rb-empty { margin: 0; padding: 18px 12px; font-size: var(--t2); color: var(--fg-faint); }

.rb-eq-body { max-height: 260px; overflow-y: auto; }
.rb-pair-row {
  display: grid; grid-template-columns: 18px minmax(0, 1fr) auto minmax(0, 1fr) auto;
  gap: 8px; align-items: baseline;
  padding: 8px 12px; border-bottom: 1px solid var(--line);
}
.rb-pair-row[data-unpaired] { color: var(--brick); }
.rb-arrow { font-size: var(--t1); color: var(--fg-faint); }

.rb-after { display: flex; flex-direction: column; gap: 8px; padding: 12px; }
.rb-opt { display: flex; align-items: flex-start; gap: 8px; font-size: var(--t2); color: var(--fg); }
.rb-tip {
  margin: 0; padding: 10px 12px; border-top: 1px solid var(--line);
  font-size: var(--t1); color: var(--fg-dim);
}
.rb-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t2); color: var(--fg-dim); }

/* ── 换绑标识与绑定沿革 ──────────────────────────────────────────────────
   徽标只占一枚小方块：账号列表那一行已经很挤，而「换过绑」是一个**存在性**
   事实（换没换过），不是一个要读的数值。完整的链在抽屉里。 */
.rebound-badge {
  display: inline-block; margin-left: 6px; padding: 0 5px;
  border: 1px solid var(--signal); color: var(--signal);
  font-family: var(--font-mono); font-size: var(--t0); line-height: 15px;
  vertical-align: middle; white-space: nowrap;
}

.lineage { display: flex; flex-direction: column; }
.lin-step { display: grid; grid-template-columns: 14px 1fr; gap: 8px; padding: 5px 0; }
/* 竖线连起相邻两环 —— 沿革是一条链，不是几条并列的记录。
   最后一环不画线：线画到底会让人以为下面还有一环没显示出来。 */
.lin-step:not(:last-child) .lin-dot::after {
  content: ""; position: absolute; left: 50%; top: 9px; bottom: -14px;
  width: 1px; background: var(--line-strong); transform: translateX(-50%);
}
.lin-dot {
  position: relative; width: 7px; height: 7px; margin: 4px auto 0;
  border-radius: 50%; background: var(--fg-faint);
}
.lin-dot-hollow { background: none; border: 1px solid var(--line-strong); }
.lin-dot-now { background: var(--signal); }
.lin-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lin-body b { font-size: var(--t2); color: var(--fg); overflow-wrap: anywhere; }
.lin-body small { font-size: var(--t1); color: var(--fg-faint); }
/* 去向用砖红：「已进回收站」不是装饰，它回答的是「这个地址还能不能用」。 */
.lin-fate { color: var(--brick); }
.lin-fate.now { color: var(--jade); }
.lin-why { color: var(--fg-dim) !important; }

/* ── 换绑工作台：计数、原因分布、行上的凭据条 ────────────────────────── */
/* 「未取到」不是 0：读不到就得看得出来，不能悄悄退回一个看起来正常的数。 */
.rb-unknown { color: var(--brick); }

.rb-why-bar {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  padding: 7px 12px; border-bottom: 1px solid var(--line); background: var(--sunk);
  font-size: var(--t1); color: var(--fg-faint);
}
.rb-why-bar:empty { display: none; }
.rb-why-lead { color: var(--fg-dim); }
.rb-why-chip { white-space: nowrap; }
.rb-why-chip b { font-family: var(--font-mono); color: var(--fg-dim); font-weight: 600; }

/* 凭据三格贴在地址后面：换绑要走一次完整重认证，缺一样都发不出去 ——
   这是这一行上最能决定「能不能点」的事实，不该埋进那串灰字里。 */
.rb-creds { display: inline-flex; gap: 3px; margin-left: 7px; vertical-align: middle; }
.rb-cred {
  padding: 0 4px; border: 1px solid var(--line-strong);
  font-size: var(--t0); line-height: 14px; color: var(--fg-faint);
}
.rb-cred.on { border-color: var(--jade); color: var(--jade); }
.rb-cred.off { border-color: var(--brick); color: var(--brick); text-decoration: line-through; }

.rb-hist {
  display: inline-block; margin-left: 6px; padding: 0 4px;
  border: 1px solid var(--signal); color: var(--signal);
  font-family: var(--font-mono); font-size: var(--t0); line-height: 14px; vertical-align: middle;
}

/* ── 抽屉里的支付方式 ────────────────────────────────────────────────── */
.pm-body { display: flex; flex-direction: column; gap: 6px; }
.pm-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  padding: 5px 0; border-bottom: 1px solid var(--line);
}
.pm-row:last-child { border-bottom: none; }
.pm-country { font-family: var(--font-mono); font-size: var(--t2); color: var(--fg); }
.pm-ccy { font-family: var(--font-mono); font-size: var(--t0); color: var(--fg-faint); }
.pm-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.pm-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 6px 2px 4px; border: 1px solid var(--line-strong);
  font-family: var(--font-mono); font-size: var(--t0); line-height: 16px; color: var(--fg-faint);
}
/* 结果行里的标记比参数框里小一号：那里是要点的按钮，这里只是读。 */
.pm-chip .pm-ico { width: 14px; height: 14px; }
/* 本地方式用信号色：card/link 到处都有，它们才是这一行的信息。 */
.pm-chip.local { border-color: var(--signal); color: var(--signal); font-weight: 600; }
.pm-plain { font-size: var(--t1); color: var(--fg-faint); }
.pm-why { font-size: var(--t1); color: var(--brick); }
.pm-empty { margin: 0; font-size: var(--t1); color: var(--fg-faint); }

/* ── 邮箱类型（设置页）────────────────────────────────────────────────
 *
 * 骨架：三步说明 → 你建的类型（一类一卡）→ 系统自带（只读卡）。
 * 卡片宁大勿密：这一栏一次只看几个类型，密排省下的那点高度换不来可读性。
 */
.mailbox-steps {
  list-style: none; margin: 0 0 18px; padding: 0;
  display: flex; flex-wrap: wrap; gap: 10px;
}
.mailbox-steps li {
  display: flex; align-items: baseline; gap: 8px;
  padding: 10px 14px; border-radius: 8px;
  background: var(--surface-2, rgba(255,255,255,.03));
  border: 1px solid var(--line, rgba(255,255,255,.08));
  flex: 1 1 210px;
}
.mailbox-steps li > span {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--t2); font-weight: 600;
  background: var(--accent-soft, rgba(255,255,255,.10));
}
.mailbox-steps li > small { display: block; opacity: .62; margin-left: 4px; }

.mailbox-cards { display: flex; flex-direction: column; gap: 12px; }
.mailbox-card {
  border: 1px solid var(--line, rgba(255,255,255,.08));
  border-radius: 10px; padding: 14px 16px;
  background: var(--surface-2, rgba(255,255,255,.02));
  display: flex; flex-direction: column; gap: 12px;
}
.mailbox-card.readonly { opacity: .82; }
.mailbox-card-head { display: flex; align-items: center; gap: 12px; }
.mailbox-card-name { flex: 1 1 auto; font-size: var(--t4); font-weight: 600; }
.mailbox-card-field { display: flex; align-items: flex-start; gap: 12px; }
.mailbox-field-label {
  flex: none; width: 64px; padding-top: 6px;
  font-size: var(--t2); opacity: .62; letter-spacing: .04em;
}
.mailbox-chips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; }
.mailbox-chip {
  border: 1px solid var(--line, rgba(255,255,255,.12));
  background: var(--surface-3, rgba(255,255,255,.05));
  border-radius: 999px; padding: 4px 10px; font-size: var(--t2); cursor: pointer;
}
.mailbox-chip:hover { border-color: var(--danger, #d66); }
.mailbox-domain-input { flex: 1 1 220px; min-width: 200px; }
.mailbox-way { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; }
.mailbox-way-note { opacity: .62; line-height: 1.5; }
.mailbox-way-missing { color: var(--warning, #d9a441); line-height: 1.5; }
.mailbox-builtin-head {
  display: flex; align-items: baseline; gap: 10px;
  margin: 22px 0 10px; flex-wrap: wrap;
}
