/* ==========================================================================
   Task Console —— iOS / iPadOS 观感的运维控制台
   --------------------------------------------------------------------------
   取向：iPadOS 的「侧栏 + 内容区」。毛玻璃顶栏与侧栏、大圆角分组卡片、
   克制的发丝分隔线、SF 字阶、iOS 语义色（蓝=主操作 绿=成功 红=危险
   橙=警告 灰=次要）。深色为主，浅色跟随系统。

   刻意避开的东西（这个项目明确不要）：渐变文字标题、均匀铺满的营销卡片网格、
   大而无当的留白。指标区做成**一整块分组读数板**而不是 8 张独立卡片，
   表格是主角，留白只用来分层不用来撑场面。

   字体全部走系统栈，图标全部内联 SVG / Unicode —— 不引任何外部资源。
   ========================================================================== */

:root {
  /* 深色是主态：这台控制台常年开着，多数时间在夜里看。
     浅色由 @media (prefers-color-scheme: light) 整段覆盖。 */
  color-scheme: dark light;

  /* ── iOS 语义色（Dark）──────────────────────────────────────────────────
     ★ 每个值后面的比值是实测的最差情况 ★ 由 tests/tools/test_contrast_meets_aa.py 解析
     这份文件算出来的，覆盖每种色在 surface / bg / surface-2 / 表头 / 日志底 /
     浅填充胶囊 / 自家淡底上的组合，取最小值。改颜色请重跑那个脚本，别凭感觉。
     口径：文字（含次要、说明、占位符）≥ 4.5:1（WCAG AA）；
           控件边界与有意义的图形 ≥ 3:1（WCAG 1.4.11）。

     ★ 为什么区分「文字色」和「实心色」★ 同一个蓝，够亮才能当深色底上的文字，
     够暗才能让白字压在上面 —— 一个值不可能两头都过。所以拆成
     --tint（文字/图标/边框）和 --tint-solid（实心按钮底，配白字）。 */
  --tint: #94c8ff;                          /* 实测 7.67:1（12px 表格文字要 AAA） */
  --tint-solid: #0a70e0;                    /* 实心按钮底，实测白字 4.77:1 */
  /* hover 只能在「白字仍达标」的范围内提亮 —— 再亮一档白字就掉到 4.0 了 */
  --tint-solid-hover: #0a73e5;              /* 实测白字 4.57:1 */
  --tint-fill: rgba(10, 132, 255, .18);
  --tint-fill-hover: rgba(10, 132, 255, .28);
  --tint-ring: rgba(61, 155, 255, .32);
  --green: #5cdb7c;                         /* 实测 7.61:1 */
  --green-fill: rgba(48, 209, 88, .16);
  /* ★ 深色失效红刻意停在 5.5:1，不跟其它色一起提到 7:1 —— 这不是漏改 ★
     提到 7:1 要把它冲淡成 #ffada8，一屏 21 行扫过去读作**粉色**，不是警报。
     失效红的价值在于「这一行需要你立刻处理」，只有它和存活绿是那一屏里唯二的
     动作信号；淡化它等于把颜色预算花错地方。而且用户报的问题现场是**浅色白底**，
     深色不是 —— 浅色那边照旧 7:1（#9e000e）。
     5.5:1 仍高于 WCAG AA 的 4.5，且 .form-error（14px）按 14~15px 档正好要 5.5。 */
  --red: #ff8078;                           /* 实测 5.52:1 */
  --red-solid: #e11b0f;                     /* 危险实心按钮，实测白字 4.81:1 */
  --red-fill: rgba(255, 69, 58, .16);
  --orange: #ffb33d;                        /* 实测 7.54:1 */
  --orange-fill: rgba(255, 159, 10, .16);
  --primary: var(--tint-solid);
  --success: var(--green);
  --warning: var(--orange);
  --danger: var(--red);
  --line: var(--separator);

  /* ── 背景层级 ──────────────────────────────────────────────────────────
     iOS 深色分组表：纯黑打底，卡片抬到 #1C1C1E，卡内控件再抬一层。 */
  --bg: #000000;
  --surface: #1c1c1e;
  --surface-2: #2c2c2e;
  --surface-3: #3a3a3c;
  --surface-head: #242426;   /* ★ sticky 表头必须不透明，否则滚动时透出行 */
  --code-bg: #0d0d0f;
  --glass: rgba(24, 24, 26, .72);
  --glass-solid: #18181a;    /* 不支持 backdrop-filter 时的兜底 */

  /* ── 填充与分隔（iOS systemFill / separator）─────────────────────────── */
  --fill: rgba(120, 120, 128, .20);
  --fill-hover: rgba(120, 120, 128, .32);
  --separator: rgba(84, 84, 88, .48);
  --hairline: rgba(84, 84, 88, .30);
  /* 复选框/开关这类**控件边界**：不是文字，但必须能看见边在哪（1.4.11 要求 3:1）。
     以前直接借用 --text-3，那条只有 1.3~2.8:1，等于一个没有边的框。 */
  --control-border: rgba(235, 235, 245, .45);
  /* 趋势图「未完成」那一段：它有图例、是一条真的数据序列，不是装饰，
     所以也要 ≥3:1（原来借用 --fill-hover，只有 1.3~1.5:1，整段等于看不见）。 */
  --chart-idle: rgba(235, 235, 245, .39);

  /* ── 文字（iOS label 三级）──────────────────────────────────────────────
     ★ 不照抄 iOS 的 .6/.3 ★ Apple 的 secondaryLabel/tertiaryLabel 在白底上只有
     3.6:1 和 1.9:1，本来就没打算过 WCAG AA。用户实测反馈「白色加灰色字体很容易
     看不清楚」，所以三级全部按 ≥4.5:1 重定，层级改由字号和字重承担。 */
  --text: #ffffff;                          /* 正文 —— 最差 13.46:1 */
  --text-2: rgba(235, 235, 245, .78);       /* 次要 —— 实测 7.57:1（AAA） */
  /* ★ --text-3 只许用在 ≥14px 或非文字上 ★ 见浅色那边的长注释。 */
  --text-3: rgba(235, 235, 245, .66);       /* 三级 —— 实测 5.89:1 */

  --shadow-1: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-2: 0 16px 48px rgba(0, 0, 0, .58), 0 2px 8px rgba(0, 0, 0, .4);
  --shadow-pop: 0 2px 6px rgba(0, 0, 0, .5);

  /* 下拉箭头（内联 data URI，不外链）。浅色主题整条替换。 */
  /* 箭头/放大镜是「这是个下拉」「这是个搜索框」的唯一提示，属于有意义的图形，
     不透明度按 ≥3:1 给（原来 .62/.55 太淡，几乎看不见）。 */
  --chevron: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.7 6 7.7l3-3' fill='none' stroke='%23ebebf5' stroke-opacity='.82' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --magnifier: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%23ebebf5' stroke-opacity='.78' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='6.1' cy='6.1' r='4.1'/%3E%3Cpath d='M9.2 9.2 12.4 12.4'/%3E%3C/g%3E%3C/svg%3E");

  /* ── 圆角 ──────────────────────────────────────────────────────────────
     ★ 比 iOS 收一档 ★ 这是桌面端的数据密集工具，不是手机设置页。大圆角+厚留白
     会把每一行都撑成一张卡片，一屏只剩十几行 —— 而这张表天天要扫的是几十上百行。
     参照的是 Linear / Height 那种克制感：小圆角、发丝线、信息优先。 */
  --r-xs: 5px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 10px;
  --r-xl: 16px;
  --r-pill: 980px;

  /* ── 尺寸 ── */
  --sidebar: 228px;
  --topbar-h: 52px;
  --shell-inset: 84px;       /* 顶栏 52 + 主区上下留白 32 —— 撑满高度靠它 */
  --row-h: 32px;             /* 桌面端密集表格：一屏能多出一倍的行 */

  /* ── 动效 ── iOS 的减速曲线，150~250ms，不做花哨动画 ── */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --dur: .22s;
  --dur-fast: .15s;

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
          "Segoe UI Variable Text", "Segoe UI", "PingFang SC", "Hiragino Sans GB",
          "Microsoft YaHei", "Noto Sans SC", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
          "Liberation Mono", monospace;

  font-family: var(--font);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ── 浅色 ────────────────────────────────────────────────────────────────
   iOS systemGroupedBackground：底 #F2F2F7、卡片纯白。文字色改用带透明度的
   label 色，叠在任何底色上都自然。绿/红换成对白底可读的深色版本。 */
@media (prefers-color-scheme: light) {
  :root {
    /* 浅色下每个颜色都要能压在白卡片、#F2F2F7 页底、以及浅填充胶囊上还看得清 ——
       最难的其实是「浅填充胶囊」（#EFEFF0），所有比值都按它算。 */
    --tint: #004ba3;                        /* 实测 7.20:1 */
    --tint-solid: #0a6fe0;                  /* 实心按钮底，实测白字 4.81:1 */
    --tint-solid-hover: #0059c4;
    --tint-fill: rgba(0, 122, 255, .10);
    --tint-fill-hover: rgba(0, 122, 255, .16);
    --tint-ring: rgba(0, 98, 214, .28);
    --green: #175928;                       /* 实测 7.31:1 */
    --green-fill: rgba(52, 199, 89, .16);
    --red: #9e000e;                         /* 实测 7.41:1 */
    --red-solid: #cf0012;                   /* 危险实心按钮，实测白字 5.73:1 */
    --red-fill: rgba(255, 59, 48, .12);
    --orange: #803900;                      /* 实测 7.29:1 */
    --orange-fill: rgba(255, 149, 0, .16);

    --bg: #f2f2f7;
    --surface: #ffffff;
    --surface-2: #f2f2f7;
    --surface-3: #ffffff;
    --surface-head: #f7f7fa;
    --code-bg: #f7f7fa;
    --glass: rgba(249, 249, 251, .78);
    --glass-solid: #f9f9fb;

    --fill: rgba(120, 120, 128, .12);
    --fill-hover: rgba(120, 120, 128, .20);
    --separator: rgba(60, 60, 67, .18);
    --hairline: rgba(60, 60, 67, .12);
    --control-border: rgba(60, 60, 67, .62);
    --chart-idle: rgba(60, 60, 67, .58);

    /* ★ 4.5:1 只是「≈16px 正文」的下限，不是小字的下限 ★
       上一版全部卡在 4.5 及格线，同时把大量次级灰压到 11~12px —— 颜色比和字号是
       **相乘**的，所以「41 组全达标」和用户说的「灰字白底看不清」能同时成立。
       中文更吃亏：CJK 笔画密度高，Windows 上 --font 落到 Microsoft YaHei，
       11px 下笔画直接糊在一起。现在次级色按 AAA（≥7:1）定，字号下限提到 12px。 */
    --text: #1c1c1e;                        /* 正文 —— 最差 14.78:1 */
    --text-2: rgba(60, 60, 67, .92);        /* 次要 —— 实测 7.60:1（AAA） */
    /* ★ --text-3 只许用在 ≥14px 或非文字（占位符/失效态）上 ★
       它达不到 7:1，所以不能承载小字。≤13px 的地方一律用 --text-2；
       真要降噪就用号池那个「精简列」开关把列收起来，别靠淡化。 */
    --text-3: rgba(60, 60, 67, .82);        /* 三级 —— 实测 5.75:1 */

    --shadow-1: 0 1px 2px rgba(0, 0, 0, .06);
    --shadow-2: 0 20px 60px rgba(0, 0, 0, .16), 0 2px 8px rgba(0, 0, 0, .06);
    --shadow-pop: 0 1px 3px rgba(0, 0, 0, .14);

    --chevron: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.7 6 7.7l3-3' fill='none' stroke='%233c3c43' stroke-opacity='.85' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --magnifier: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%233c3c43' stroke-opacity='.8' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='6.1' cy='6.1' r='4.1'/%3E%3Cpath d='M9.2 9.2 12.4 12.4'/%3E%3C/g%3E%3C/svg%3E");
  }
}

/* ---------- 基础 ---------- */

* { box-sizing: border-box; }

body {
  margin: 0;
  min-width: 320px;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.45;
  letter-spacing: -.005em;
}

button, input, select, textarea { font: inherit; color: inherit; }
button { border: 0; background: none; }
button, a, .segmented-item, .switch { -webkit-tap-highlight-color: transparent; }
a { color: inherit; text-decoration: none; }
[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--tint);
  outline-offset: 2px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 13px; letter-spacing: 0; }
.muted { color: var(--text-2); }

/* 内联图标：跟着文字颜色走，不引任何图标库 */
.ic {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ic-solid { fill: currentColor; stroke: none; }

/* 空的 eyebrow 不占位（只保留 id 挂点） */
.eyebrow {
  margin: 0 0 4px;
  color: var(--text-2);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.eyebrow:empty { display: none; }

.skip-link {
  position: fixed;
  z-index: 100;
  top: 10px;
  left: 10px;
  padding: 10px 16px;
  border-radius: var(--r-md);
  background: var(--tint-solid);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  box-shadow: var(--shadow-2);
  transform: translateY(-180%);
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus { transform: translateY(0); }

/* 滚动条：iOS 那种细的浮层条，不占版面 */
* { scrollbar-width: thin; scrollbar-color: var(--fill-hover) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: var(--r-pill);
  background: var(--fill-hover);
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--text-3); background-clip: content-box; }

/* ---------- 按钮 ----------
   四种：主（实心蓝）/ 次（浅填充）/ 危险（红）/ 文字（无底）。
   按下统一 scale(.96) —— iOS 的触感反馈来自缩放而不是变色。 */

.button {
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 12px;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text);
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              opacity var(--dur-fast) var(--ease);
}
.button:active:not(:disabled) { transform: scale(.96); }
.button:disabled { opacity: .38; cursor: not-allowed; }

/* ★ 实心按钮用 --tint-solid 不是 --tint ★ --tint 是给深色底上的文字用的、偏亮，
   白字压上去只有 2.6:1。实心底必须够暗，白字才读得出来。 */
.button-primary { background: var(--tint-solid); color: #fff; font-weight: 600; }
.button-primary:hover:not(:disabled) { background: var(--tint-solid-hover); }

.button-secondary { background: var(--fill); color: var(--text); }
.button-secondary:hover:not(:disabled) { background: var(--fill-hover); }

.button-ghost { color: var(--tint); }
.button-ghost:hover:not(:disabled) { background: var(--tint-fill); }

/* 次要动作的默认形态：无边框文字按钮。批量条里一排 8 个填充按钮就是一堵墙，
   找一个要从头扫到尾；做成文字按钮之后，实心的那个（主操作）自然跳出来。 */
.button-quiet { color: var(--text-2); }
.button-quiet:hover:not(:disabled) { background: var(--fill); color: var(--text); }

/* 破坏性操作平时低调（灰字），指上去才变红 —— 既不吓人也不会误点得毫无察觉 */
.button-danger { color: var(--text-2); }
.button-danger:hover:not(:disabled) { background: var(--red-fill); color: var(--red); }
.button-danger-solid { background: var(--red-solid); color: #fff; font-weight: 600; }
.button-danger-solid:hover:not(:disabled) { background: var(--red-solid); filter: brightness(1.12); }

.button-mini { height: 26px; padding: 0 10px; font-size: 13px; border-radius: var(--r-sm); }
.button-wide { width: 100%; height: 46px; font-size: 16px; border-radius: var(--r-md); }

/* 加载态：真的转起来，而不是只把文字换掉 */
.button.is-loading::before {
  content: '';
  width: 13px;
  height: 13px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  opacity: .75;
  animation: spin .7s linear infinite;
}
.button-spinner {
  display: none;
  width: 15px;
  height: 15px;
  border: 2px solid rgba(255, 255, 255, .4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
.button:disabled .button-spinner { display: block; }
@keyframes spin { to { transform: rotate(360deg); } }

.icon-button {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.icon-button:hover { background: var(--fill); color: var(--text); }
.icon-button:active { transform: scale(.9); }
.icon-button .ic { width: 18px; height: 18px; }

.link-action { color: var(--tint); font-size: 14px; font-weight: 500; }
.link-action:hover { text-decoration: underline; }

/* ---------- 分段控件 ----------
   iOS 的 segmented control：整体一块浅填充，选中项抬成一张白/深卡。
   用来替代那些只有 3~6 个短选项的下拉（任务状态、产物排序），
   点一下就切换，不用先展开再选。原生 <select> 仍在 DOM 里驱动逻辑。 */

.segmented {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  max-width: 100%;
  padding: 2px;
  border-radius: var(--r-md);
  background: var(--fill);
  overflow-x: auto;
  scrollbar-width: none;
}
.segmented::-webkit-scrollbar { display: none; }
.segmented-item {
  height: 28px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-2);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.segmented-item:hover { color: var(--text); }
.segmented-item[aria-pressed="true"] {
  background: var(--surface-3);
  color: var(--text);
  font-weight: 600;
  box-shadow: var(--shadow-pop);
}
.segmented-item:active { transform: scale(.94); }
/* 面板标题栏里的迷你版：和 .button-mini 同高，不把标题行撑起来 */
.segmented-mini { padding: 1px; border-radius: var(--r-sm); }
.segmented-mini .segmented-item { height: 24px; padding: 0 9px; font-size: 12px; }
/* 被分段控件/开关接管的原生下拉：留在 DOM 里供 JS 读写，只是不显示 */
.is-mounted { display: none !important; }

/* ---------- 开关 ---------- */

.switch {
  position: relative;
  width: 51px;
  height: 31px;
  flex: 0 0 auto;
  padding: 0;
  border-radius: var(--r-pill);
  /* 关闭态靠一圈描边划出边界：单纯一块浅灰只有 1.3:1，看不出这里有个控件 */
  background: var(--fill-hover);
  box-shadow: inset 0 0 0 1.5px var(--control-border);
  cursor: pointer;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .3);
  transition: transform var(--dur) var(--ease);
}
.switch[aria-checked="true"] { background: var(--green); box-shadow: none; }
.switch[aria-checked="true"]::after { transform: translateX(20px); }

/* ---------- 表单 ---------- */

.field { display: grid; gap: 7px; margin-bottom: 16px; }
.field > span:first-child {
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.01em;
}
.field input,
.field select {
  width: 100%;
  height: 40px;
  padding: 0 13px;
  border: 0;
  border-radius: var(--r-md);
  /* ★ 必须是 background-color，不能用 background 简写 ★ 简写会把 background-image
     一并重置成 none —— 而下面 select 的下拉箭头正是靠 background-image 画的，
     且 `.field select` 比 `select` 特异性高，写简写等于把所有表单里的箭头全抹掉
     （表现：下拉框看起来和普通输入框一模一样，没人知道那是能展开的）。 */
  background-color: var(--fill);
  color: var(--text);
  font-size: 15px;
  outline: none;
  transition: background-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.field input::placeholder { color: var(--text-3); }
.field input:hover, .field select:hover { background-color: var(--fill-hover); }
.field input:focus, .field select:focus { box-shadow: 0 0 0 3.5px var(--tint-ring); }
.field input:disabled, .field select:disabled { opacity: .45; cursor: not-allowed; }

select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 32px;
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right 11px center;
  cursor: pointer;
}

/* 一行说明：字段名讲「是什么」，hint 讲「填什么、怎么填」 */
.field-hint {
  margin-top: 1px;
  color: var(--text-2);
  font-size: 13px;
  line-height: 1.5;
  font-weight: 400;
}
.field-hint b { color: var(--text); font-weight: 600; }

/* 标签在左、开关在右 —— iOS 设置里那种一行 */
.field-inline {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px 14px;
}
.field-inline > span:first-child { grid-column: 1; grid-row: 1; }
.field-inline .switch { grid-column: 2; grid-row: 1; }
.field-inline .field-hint { grid-column: 1 / -1; }

.password-field { position: relative; display: block; }
.password-field input { padding-right: 44px; }
.password-field .icon-button { position: absolute; top: 4px; right: 4px; }
/* 明文/密文两个眼睛靠 input 的 type 切换，不需要 JS 改图标 */
.password-field input[type="password"] ~ .icon-button .ic-eye-off,
.password-field input[type="text"] ~ .icon-button .ic-eye { display: none; }

.form-error {
  min-height: 18px;
  margin: 10px 0 0;
  color: var(--red);
  font-size: 14px;
  line-height: 1.45;
}

.inline-note {
  margin: 0 0 14px;
  padding: 11px 13px;
  border-radius: var(--r-md);
  background: var(--tint-fill);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
}
.inline-note-warn { background: var(--orange-fill); }
.inline-note-error, .inline-note.is-error { background: var(--red-fill); }
.inline-note .field-hint { display: block; margin-top: 5px; }

/* 筛选条上的小控件：iOS 的 pull-down button —— 浅填充胶囊，不描边 */
.mini-select, .mini-input {
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--r-sm);
  background-color: var(--fill);        /* 同上：简写会抹掉箭头/放大镜 */
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: background-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.mini-select { padding-right: 30px; background-position: right 9px center; }
.mini-input { min-width: 210px; }
/* 搜索框左边一个放大镜（内联 data URI），和 iOS 搜索栏一致 */
.mini-input[type="search"] {
  padding-left: 32px;
  background-image: var(--magnifier);
  background-repeat: no-repeat;
  background-position: left 10px center;
}
.mini-input::placeholder { color: var(--text-3); }
.mini-select:hover, .mini-input:hover { background-color: var(--fill-hover); }
.mini-select:focus, .mini-input:focus { box-shadow: 0 0 0 3.5px var(--tint-ring); }

/* 复选框：iOS 表格编辑态那种圆角方框，勾是画出来的（不引图标） */
input[type="checkbox"] {
  position: relative;
  width: 20px;
  height: 20px;
  margin: 0;
  flex: 0 0 auto;
  appearance: none;
  -webkit-appearance: none;
  border: 1.5px solid var(--control-border);
  border-radius: var(--r-xs);
  background: transparent;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
input[type="checkbox"]:hover { border-color: var(--tint); }
input[type="checkbox"]:active { transform: scale(.88); }
input[type="checkbox"]:checked,
input[type="checkbox"]:indeterminate { border-color: var(--tint-solid); background: var(--tint-solid); }
input[type="checkbox"]:checked::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 6px;
  width: 4px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
input[type="checkbox"]:indeterminate::after {
  content: '';
  position: absolute;
  top: 7.5px;
  left: 3.5px;
  width: 10px;
  height: 2px;
  border-radius: 1px;
  background: #fff;
}
input[type="checkbox"]:disabled { opacity: .38; cursor: not-allowed; }

/* ---------- 登录 ----------
   ★ 登录页不跟随主界面的字号 ★ 它是一屏一卡的排版，字一大就笨重失衡；
   主界面是长时间盯着操作的地方，字大才看得清。两边各按各的密度走。 */

.auth-view {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(1100px 620px at 50% -10%, var(--tint-fill), transparent 65%),
    var(--bg);
  isolation: isolate;
  overflow: hidden;
}
.auth-view::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .22;
  background-image:
    linear-gradient(var(--hairline) 1px, transparent 1px),
    linear-gradient(90deg, var(--hairline) 1px, transparent 1px);
  background-size: 48px 48px;
  mask-image: radial-gradient(circle at center, #000, transparent 72%);
}
.auth-card {
  width: min(100%, 420px);
  padding: 32px 30px 26px;
  border: 1px solid var(--separator);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  animation: auth-card-in 180ms var(--ease) both;
}
@keyframes auth-card-in { from { opacity: 0; transform: translateY(8px) scale(.985); } }
.auth-head { display: flex; align-items: center; gap: 13px; margin-bottom: 26px; }
.auth-head h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
.auth-sub { margin: 3px 0 0; color: var(--text-2); font-size: 13px; }
.auth-view .field { margin-bottom: 15px; }
.auth-view .field > span:first-child { font-size: 13px; font-weight: 600; color: var(--text-2); }
.auth-view .field input { height: 42px; font-size: 15px; }
.auth-view .button-wide { margin-top: 4px; }
.auth-meta {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 22px 0 0;
  padding: 0;
}
.auth-meta > div {
  padding: 10px 11px;
  border-radius: var(--r-md);
  background: var(--fill);
}
.auth-meta dt { color: var(--text-2); font-size: 12px; font-weight: 600; letter-spacing: .04em; }
.auth-meta dd { margin: 3px 0 0; color: var(--text-2); font-size: 12px; }

.brand-mark {
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: var(--r-md);
  background: var(--tint-solid);
  color: #fff;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -.02em;
}
.brand-mark-small { width: 30px; height: 30px; border-radius: var(--r-sm); font-size: 12px; }

/* ---------- 外壳 ---------- */

.app-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: var(--sidebar) minmax(0, 1fr);
}

/* iPadOS 侧栏：毛玻璃、无描边，只靠一条发丝线和内容区分开 */
.sidebar {
  position: sticky;
  z-index: 20;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  padding: 14px 12px 12px;
  border-right: 1px solid var(--hairline);
  background: var(--glass-solid);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .sidebar {
    background: var(--glass);
    -webkit-backdrop-filter: saturate(180%) blur(22px);
    backdrop-filter: saturate(180%) blur(22px);
  }
}
.sidebar-brand { height: 44px; display: flex; align-items: center; gap: 11px; padding: 0 6px; }
.sidebar-brand div:last-child { display: grid; gap: 0; min-width: 0; }
.sidebar-brand strong { font-size: 16px; font-weight: 700; letter-spacing: -.02em; }
.sidebar-brand span { color: var(--text-2); font-size: 12px; letter-spacing: .05em; text-transform: uppercase; }

.sidebar-nav { flex: 1; margin-top: 22px; }
.nav-label {
  margin: 0 10px 6px;
  color: var(--text-2);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
}
.nav-label-spaced { margin-top: 22px; }
.nav-item {
  height: 38px;
  display: flex;
  align-items: center;
  gap: 11px;
  margin: 2px 0;
  padding: 0 10px;
  border-radius: var(--r-md);
  color: var(--text-2);
  font-size: 15px;
  font-weight: 500;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.nav-item:hover { background: var(--fill); color: var(--text); }
.nav-item:active { transform: scale(.98); }
.nav-item.active { background: var(--tint-fill); color: var(--tint); font-weight: 600; }
.nav-item.active .nav-icon { color: var(--tint); }
.nav-icon {
  width: 22px;
  display: inline-grid;
  place-items: center;
  color: var(--text-2);
  font-size: 16px;
}
.nav-item:hover .nav-icon { color: var(--text); }
.nav-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-badge {
  min-width: 20px;
  height: 20px;
  margin-left: auto;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  background: var(--tint-solid);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.sidebar-footer { display: grid; gap: 6px; }
.side-service {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 8px;
  color: var(--text-2);
  font-size: 13px;
}
.side-service .muted { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--text-2); }
.status-dot {
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--text-3);
}
.status-dot-success { background: var(--green) !important; }
.status-dot-danger { background: var(--red) !important; }

.side-build {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 8px;
  color: var(--text-2);
  font-size: 12px;
}
.side-build .mono { font-family: var(--mono); font-size: 12px; }

.side-user {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
  padding: 10px 8px 2px;
  border-top: 1px solid var(--hairline);
}
.avatar {
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--fill-hover);
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
}
.side-user > div { min-width: 0; display: grid; }
.side-user strong {
  max-width: 108px;
  overflow: hidden;
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.side-user span { color: var(--text-2); font-size: 12px; }
.side-logout { margin-left: auto; }

.app-main { min-width: 0; }

/* 毛玻璃顶栏 */
.topbar {
  position: sticky;
  z-index: 15;
  top: 0;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 16px;
  border-bottom: 1px solid var(--hairline);
  background: var(--glass-solid);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .topbar {
    background: var(--glass);
    -webkit-backdrop-filter: saturate(180%) blur(22px);
    backdrop-filter: saturate(180%) blur(22px);
  }
}
.topbar-title { min-width: 0; display: flex; align-items: center; gap: 10px; }
.breadcrumb { margin: 0; color: var(--text-2); font-size: 12px; font-weight: 500; letter-spacing: .01em; }
.breadcrumb span { color: var(--text-2); }
.topbar h1 {
  margin: 0;
  overflow: hidden;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.02em;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-actions { display: flex; align-items: center; gap: 10px; }
.topbar-status {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 28px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--text-2);
  font-size: 13px;
  white-space: nowrap;
}
.topbar-status .muted { color: var(--text-2); font-family: var(--mono); font-size: 12px; }
.mobile-brand { display: none; }
.back-button { flex: 0 0 auto; margin-right: 4px; white-space: nowrap; }
.crumb-link {
  padding: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.crumb-link:hover { color: var(--tint); }

/* ★ 不限宽 ★ 这是运维面板不是阅读页面，表格列多，1920/2560 屏上宽度全都用得上。 */
.main-content {
  width: 100%;
  padding: 16px;
}
.view { display: none; }
.view.is-active { display: block; animation: view-in .2s var(--ease) both; }
@keyframes view-in { from { opacity: 0; transform: translateY(4px); } }

/* ---------- 指标读数板 ----------
   不是 8 张营销卡片，而是**一整块分组读数板**：一个圆角容器，内部用发丝线分格。
   ★ 列数写死会出事 ★ 写死 6 列时加到第 7 张就单独换行、右边空一大条。
   auto-fit 让格子按可用宽度自己排，以后再加也不用回来改列数。 */
.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
/* auto-fit 是兜底（以后加卡不用回来改列数），但它在某些宽度上会把最后一张卡
   单独甩到第二行、右边空一大条 —— 用户为这个骂过一次。所以在常见断点上各给一个
   **能整除 8 张卡**的列数，保证每一行都填满。 */
@media (min-width: 1440px) { .metric-grid { grid-template-columns: repeat(9, minmax(0, 1fr)); } }
@media (min-width: 768px) and (max-width: 1439px) { .metric-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.metric {
  display: grid;
  align-content: start;
  gap: 2px;
  padding: 10px 13px 11px;
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.metric-caption { color: var(--text-2); font-size: 12px; font-weight: 500; }
.metric strong {
  font-size: 23px;
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.metric-detail { color: var(--text-2); font-size: 12px; }
.metric-strong strong { color: var(--green); }
.metric-danger strong { color: var(--red); }

.gap-top { margin-top: 12px; }
.grid-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
.split { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(340px, .8fr); gap: 12px; }
.split-even { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }

/* ---------- 卡片 ---------- */

.panel {
  min-width: 0;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.panel-heading {
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 14px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--hairline);
}
.panel-heading h2, .panel-heading h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -.01em;
}
.panel-subtitle { margin: 2px 0 0; color: var(--text-2); font-size: 12px; line-height: 1.45; }
.heading-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }

.mail-capacity-cell { min-width: 190px; }
.mail-capacity {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 12px;
  white-space: nowrap;
}
.mail-capacity.is-open {
  border-color: color-mix(in srgb, var(--tint) 28%, var(--line));
  background: color-mix(in srgb, var(--tint) 8%, var(--surface));
  color: var(--tint);
}
.mail-capacity-quota { margin-left: 8px; font-size: 12px; white-space: nowrap; }
.row-action-note {
  display: inline-flex;
  align-items: center;
  min-height: 26px;
  padding: 0 7px;
  color: var(--text-2);
  font-size: 12px;
  white-space: nowrap;
}
.count-badge {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.panel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 14px;
  border-top: 1px solid var(--hairline);
  color: var(--text-2);
  font-size: 13px;
}
.panel-body { padding: 14px 16px 2px; }

.filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--hairline);
}

/* 批量条：常显，没选中就整体压暗 —— 藏起来的话用户勾选前不知道能干什么，
   而且它一出现整张表就往下跳一截。 */
.bulk-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 14px;
  border-bottom: 1px solid var(--hairline);
  background: var(--tint-fill);
  color: var(--text);
  font-size: 13px;
}
.bulk-bar strong { font-weight: 700; font-variant-numeric: tabular-nums; }
.bulk-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
.bulk-bar.is-idle { background: transparent; color: var(--text-2); }
.bulk-bar.is-idle strong { color: var(--text-2); }
.bulk-bar button:disabled { opacity: .38; cursor: not-allowed; }
.pool-import-card .field { margin-bottom: 14px; }
.pool-import-textarea {
  width: 100%;
  min-height: 190px;
  resize: vertical;
  padding: 10px 12px;
  border: 1px solid var(--separator);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font: 13px/1.55 var(--mono);
}
.pool-import-textarea:focus { border-color: var(--tint); box-shadow: 0 0 0 3px var(--tint-fill); }
.check-line { display: flex; align-items: center; gap: 9px; margin: 2px 0 14px; color: var(--text); font-size: 14px; }
.eligibility-dialog { width: min(520px, calc(100vw - 32px)); }
.eligibility-mode-field { padding: 10px; }
.eligibility-mode-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.eligibility-mode-card {
  min-width: 0;
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  padding: 11px 12px;
  border: 1px solid var(--separator);
  border-radius: var(--r-sm);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.eligibility-mode-card:hover { background: var(--fill); }
.eligibility-mode-card:has(input:checked) { border-color: var(--tint); background: var(--tint-fill); box-shadow: 0 0 0 1px var(--tint); }
.eligibility-mode-card input[type="radio"] { width: 18px; height: 18px; margin: 0; accent-color: var(--tint); }
.eligibility-mode-copy { min-width: 0; display: grid; gap: 2px; }
.eligibility-mode-copy strong { font-size: 13px; line-height: 1.25; }
.eligibility-mode-copy small { color: var(--text-2); font-size: 11px; line-height: 1.35; white-space: nowrap; }
.eligibility-probe-line { align-items: flex-start; margin-bottom: 8px; }
.eligibility-probe-line span { display: grid; gap: 2px; }
.eligibility-probe-line small { color: var(--text-2); font-size: 12px; line-height: 1.4; }
.eligibility-card .field > select:disabled { opacity: .58; cursor: not-allowed; }
.eligibility-probes {
  margin: 0 0 14px;
  padding: 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
}
.eligibility-probes legend { padding: 0 4px; color: var(--text-2); font-size: 13px; }

#pool-bulk {
  min-height: 44px;
  gap: 12px;
  border-block: 1px solid var(--line);
  background: color-mix(in srgb, var(--surface) 92%, var(--primary) 8%);
  transition: background-color 160ms ease, border-color 160ms ease;
}
.app-shell:not([hidden]) { animation: shell-in 140ms var(--ease) both; }
@keyframes shell-in { from { opacity: 0; } }
#pool-bulk[data-selection-state="zero"] {
  background: var(--surface);
}
.pool-context-actions {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}
.pool-context-hint { color: var(--text-2); font-size: 14px; }
.pool-action-group { white-space: nowrap; }
.pool-context-clear { margin-left: 4px; }
.pool-legacy-actions[hidden] { display: none !important; }

.operation-panel {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 90;
  width: min(420px, calc(100vw - 32px));
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  box-shadow: 0 18px 50px rgb(15 23 42 / 18%);
  backdrop-filter: blur(14px);
  animation: operation-in 180ms var(--ease);
}
.operation-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.operation-panel-head > div { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.operation-status { color: var(--text-2); font-size: 12px; }
.operation-progress { height: 4px; margin-top: 10px; overflow: hidden; border-radius: 999px; background: var(--fill); }
.operation-progress span { display: block; width: 0; height: 100%; border-radius: inherit; background: var(--primary); transition: width 180ms ease; }
.operation-summary { margin: 9px 0 0; color: var(--text-2); font-size: 12px; }
.operation-failures { max-height: 140px; margin: 8px 0 0; padding-left: 18px; overflow: auto; color: var(--danger); font-size: 14px; }
.operation-result { margin-top: 10px; }
.operation-panel[data-status="success"], .operation-panel[data-status="partial"] { bottom: 16px; }
.operation-panel[data-status="success"] .operation-progress span { background: var(--success); }
.operation-panel[data-status="partial"] .operation-progress span { background: var(--warning); }
.operation-panel[data-status="failed"] .operation-progress span { background: var(--danger); }
@keyframes operation-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }

@media (prefers-reduced-motion: reduce) {
  .operation-panel { animation: none; }
  .operation-progress span { transition: none; }
}

@media (max-width: 736px) {
  #pool-bulk { align-items: stretch; }
  .pool-context-actions { overflow-x: auto; justify-content: flex-start; padding-bottom: 2px; }
  .pool-context-actions .button { flex: 0 0 auto; }
}

/* ---------- 表格 ----------
   iPadOS 列表观感：行高舒适、发丝分隔线、hover 浅填充、选中蓝色淡染。
   ★ 表格自己滚 ★ 没有 max-height 时 50 行会把整页撑到 3000px+，
   任何滚动都变成大跳；sticky 表头保证滚动时列名还在。 */

.table-wrap { width: 100%; overflow: auto; max-height: min(66vh, 660px); }
.table-wrap-short { min-height: 176px; max-height: 268px; }
.artifact-panel .table-wrap { min-height: 300px; }
.table-wrap thead th { position: sticky; top: 0; z-index: 2; background: var(--surface-head); }

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
  height: 30px;
  padding: 0 10px;
  border-bottom: 1px solid var(--separator);
  color: var(--text-2);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -.005em;
  text-align: left;
  white-space: nowrap;
}
td {
  height: var(--row-h);
  padding: 0 10px;
  border-bottom: 1px solid var(--hairline);
  color: var(--text);
  white-space: nowrap;
}
/* 元信息列（注册时间/耗时/批次/已导出）压低一档：15 列平铺时得让身份和状态先被看见 */
/* ★ 只压普通文本，别压状态标记 ★ 写成 `.col-meta .flag` 会以 (0,2,0) 盖掉
   .flag-on/.flag-ok/.flag-seen 这些 (0,1,0) 的状态色 —— 「已导出/看过/未导出」
   三态会被压成同一个灰，等于三态白做。只有「什么都没发生」那档跟着列一起压暗。 */
.col-meta { color: var(--text-2); }
#pool-table [data-pool-column] {
  width: var(--pool-column-width);
  min-width: var(--pool-column-width);
}
#pool-table [data-pool-column="actions"] { width: 148px; min-width: 148px; }
#pool-table .pool-column-hidden { display: none; }
:root[data-pool-meta="off"] #pool-table .pool-duration-col { color: transparent; }
:root[data-pool-meta="off"] #pool-table .pool-duration-col.flag-warn-text { color: var(--warning); }

@media (max-width: 520px) {
  .eligibility-mode-grid { grid-template-columns: 1fr; }
  .eligibility-mode-copy small { white-space: normal; }
}

/* ── 号池：密度 / 列显隐 ──────────────────────────────────────────────────
   两个开关都写在 <html> 的 data 属性上（JS 控制），整页一次生效，
   不用给每张表各加一套类。默认紧凑 —— 这张表的日常动作是「扫」。 */
:root[data-density="comfortable"] { --row-h: 42px; }
:root[data-density="comfortable"] table { font-size: 14px; }
:root[data-density="comfortable"] th { height: 36px; padding: 0 14px; }
:root[data-density="comfortable"] td { padding: 0 14px; }
:root[data-density="comfortable"] #reg-rows tr,
:root[data-density="comfortable"] #pool-rows tr { contain-intrinsic-size: auto 42px; }

/* 「精简列」：把四个元信息列整列收掉。压字色只能让它们退后、退不掉宽度 ——
   15 列里省掉 4 列，身份/状态/资格才真挤得进一屏。
   ★ data 属性大小写 ★ HTML 会把 dataset.poolMeta 落成 data-pool-meta，
   选择器必须写连字符形式，写成 data-poolMeta 匹配不上。 */
:root[data-pool-meta="off"] #pool-table .col-meta:not(.pool-duration-col) { display: none; }

tbody tr { transition: background var(--dur-fast) var(--ease); }
tbody tr:hover { background: var(--fill); }
/* 选中态必须一眼看得出：透明度太低时叠在卡片底色上只差几个 RGB 级。 */
tbody tr.is-selected { background: var(--tint-fill-hover); box-shadow: inset 3px 0 0 var(--tint); }
tbody tr.is-checked { background: var(--tint-fill); }
tbody tr:last-child td { border-bottom: 0; }

.table-state td { height: 168px; text-align: center; vertical-align: middle; }
.empty-state { color: var(--text-2); font-size: 14px; line-height: 1.6; }

/* 复选框那一列要好点：整格都是热区，拖动多选也靠它 */
.col-check { width: 46px; padding-right: 0; }
th.col-check, td.col-check { padding-left: 16px; }
.col-check + th, .col-check + td { padding-left: 10px; }
td.col-check { cursor: pointer; }

.row-click { cursor: pointer; }
/* ★ 任务 id 不用蓝色 ★ 12px 的蓝字要到 7:1 就得暗成一团，而且任务表每行都有一个，
   又是一片色。整行本来就能点（row.onclick → selectJob），操作列还有「日志」按钮，
   所以这里用正文色 + hover 才变蓝，可点性不丢，颜色留给真正的状态。 */
.task-id {
  max-width: 140px;
  padding: 0;
  overflow: hidden;
  color: var(--text);
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.task-id:hover { color: var(--tint); text-decoration: underline; }

.table-actions-col { text-align: right; }
.table-actions { text-align: right; }
.table-actions-inner { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
/* ★ 行内按钮默认无边框、低对比 ★ 每行都亮着一排填充按钮，几十行叠起来就是一堵墙，
   眼睛没有落点。表格的主角是数据 —— 按钮平时退到背景里，指到这一行才显形。 */
.row-action {
  min-height: 22px;
  display: inline-flex;
  align-items: center;
  padding: 0 7px;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--text-2);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
tbody tr:hover .row-action { color: var(--text-2); }
.row-action:hover, .row-action:focus-visible { background: var(--fill); color: var(--text); }
.row-action-fix { color: var(--tint); }
.row-action-fix:hover { background: var(--tint-fill); color: var(--tint); }
.row-action-danger:hover { background: var(--red-fill); color: var(--red); }
.row-more { padding: 0 6px; font-size: 14px; line-height: 1; letter-spacing: .06em; }

/* ★ 操作列钉在右边 ★ 号池 15 列必然放不下一屏，而「详情 / ⋯」是这一行唯一的入口 ——
   不钉住的话得先横向滚到最右才够得着。背景必须不透明，否则底下的行会透出来；
   hover/选中态用 background-image 叠一层，这样 background-color 还能保持不透明。 */
#pool-table th.table-actions-col,
#pool-table td.table-actions,
#reg-table th.table-actions-col,
#reg-table td.table-actions {
  position: sticky;
  right: 0;
  z-index: 1;
  background-color: var(--surface);
  box-shadow: -10px 0 10px -10px rgba(0, 0, 0, .3);
}
#pool-table thead th.table-actions-col,
#reg-table thead th.table-actions-col { z-index: 3; background-color: var(--surface-head); }
tbody tr:hover td.table-actions { background-image: linear-gradient(var(--fill), var(--fill)); }
tbody tr.is-checked td.table-actions { background-image: linear-gradient(var(--tint-fill), var(--tint-fill)); }
tbody tr.is-selected td.table-actions { background-image: linear-gradient(var(--tint-fill-hover), var(--tint-fill-hover)); }

/* 「更多操作」菜单。挂在 body 上（fixed）—— 挂在单元格里会被表格的 overflow 裁掉。 */
.row-menu {
  position: fixed;
  z-index: 80;
  min-width: 148px;
  padding: 4px;
  border-radius: var(--r-md);
  background: var(--surface-2);
  box-shadow: var(--shadow-2);
  animation: menu-in .12s var(--ease) both;
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } }
.row-menu-item {
  width: 100%;
  height: 28px;
  display: flex;
  align-items: center;
  padding: 0 10px;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
.row-menu-item:hover, .row-menu-item:focus-visible { background: var(--tint-fill); color: var(--tint); }

/* 邮箱和姓名各占一列。
   ★ 别把姓名挂在邮箱后面 ★ 原来姓名是 inline span + margin-left，起点跟着邮箱
   长度浮动，一屏看下去参差不齐，根本没法竖着扫。核对身份要的就是「扫一列」，
   所以给它独立单元格；行高仍是一行，不回到当初那种上下两行的排版。 */
.cell-identity { white-space: nowrap; }
.identity-main { display: inline-flex; align-items: center; gap: 6px; vertical-align: middle; }
.identity-email { font-weight: 600; color: var(--text); }
/* 手机号池页要用的两块。★ 用了新 class 就得配样式 ★ 只写 HTML 不写 CSS，
   这两块会以完全没有样式的裸文本出现 —— 看起来就像页面坏了。 */
.callout {
  margin: 10px 0;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  font-size: 12px;
  line-height: 1.55;
  border: 1px solid var(--line);
  background: var(--fill);
  color: var(--text-2);
}
.callout-warn {
  /* ★ width/style 显式写出来 ★ 只写 border-color 的话，实测浏览器算出来是
     `0px none <颜色>` —— 别处有规则把 border-width/style 重置了，单给颜色
     等于没有边框。三个属性一起写死，不依赖继承来的那份。 */
  border-width: 1px;
  border-style: solid;
  border-color: color-mix(in srgb, var(--orange) 35%, var(--line));
  background: color-mix(in srgb, var(--orange) 8%, transparent);
  color: var(--text);
}
.callout strong { color: var(--text); }

/* 状态小结条：几个「数字 + 标签」的小格子，比整块指标板轻 */
.metric-strip { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; }
.metric-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--fill);
}
.metric-chip-value { font-weight: 600; font-size: 13px; }
.metric-chip-label { color: var(--text-2); font-size: 12px; }

/* 详情抽屉里取出来的明文凭据。每项一行，点右边「复制」拿单项，
   顶上「全部复制 / 复制三段式」拿整套 —— 挨个点五次纯属折磨。 */
.cred-box {
  margin: 12px 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--fill);
}
.cred-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.cred-head-title { font-weight: 600; font-size: 13px; margin-right: auto; }
.cred-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 0;
  border-top: 1px solid var(--line-soft, var(--line));
}
.cred-row:first-of-type { border-top: 0; }
.cred-label { flex: 0 0 96px; color: var(--text-2); font-size: 12px; }
.cred-value {
  flex: 1 1 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  word-break: break-all;          /* 令牌很长，必须能换行，不然把抽屉撑破 */
  user-select: all;               /* 双击就能整段选中 */
}
.cred-value-long { max-height: 88px; overflow: auto; }
/* 这里的复制按钮要一直可见 —— 表格里那种「悬停才显形」在抽屉里找不着 */
.cred-copy { opacity: 1; flex: 0 0 auto; }

.cell-person {
  white-space: nowrap;
  color: var(--text-2);
  font-size: 12px;
  /* 名字长短不一，给个上限省得把后面的列挤走；超出用省略号 */
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 复制按钮平时压低存在感，指到行上才显形 —— 每行都亮着会很吵 */
.row-copy {
  padding: 2px 8px;
  border-radius: var(--r-xs);
  opacity: 0;
  background: var(--fill);
  color: var(--text-2);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
tbody tr:hover .row-copy, .row-copy:focus-visible { opacity: 1; }
.row-copy:hover { background: var(--tint-fill); color: var(--tint); }

/* 单元格里的次要说明（例如「存活」后面那个检测时间）—— 同样排在同一行内 */
.cell-sub {
  margin-left: 6px;
  color: var(--text-2);
  font-family: var(--mono);
  font-size: 12px;
}
/* 产物名下面那行是整句说明，而 td 是 nowrap —— 不限宽会把整张表撑宽，
   「操作」列（下载按钮）被挤出可视区，非得横向滚动才点得到。 */
#artifact-list .cell-sub {
  max-width: 42ch;
  font-family: var(--font);
  white-space: normal;
  line-height: 1.45;
}

/* ---------- 状态徽章 / 标记 ---------- */

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
.status-running { background: var(--tint-fill); color: var(--tint); }
.status-running .status-dot { background: var(--tint); animation: pulse 1.6s var(--ease) infinite; }
.status-queued, .status-stopping { background: var(--orange-fill); color: var(--orange); }
.status-queued .status-dot, .status-stopping .status-dot { background: var(--orange); }
.status-succeeded, .status-ok { background: var(--green-fill); color: var(--green); }
.status-succeeded .status-dot, .status-ok .status-dot { background: var(--green); }
.status-failed, .status-cancelled { background: var(--red-fill); color: var(--red); }
.status-failed .status-dot, .status-cancelled .status-dot { background: var(--red); }
.status-partial { background: var(--orange-fill); color: var(--orange); }
.status-partial .status-dot { background: var(--orange); }
@keyframes pulse { 50% { opacity: .3; } }

/* 存在性标记刻意低对比：一屏里几乎每行都「已设置/已绑定」，全用强色等于全都不显眼。
   真正需要跳出来的是缺失和失效。 */
.flag { display: inline-block; min-width: 14px; font-size: 13px; color: var(--text-2); }
/* 「符合预期」的状态（已注册/已设置/已绑定/令牌在/已导出）—— 中性，不抢眼。
   ★ 别再给它上绿色 ★ 一屏几十行全绿等于没有重点，真正的信号（失效红、存活绿）
   反而被淹掉。颜色是稀缺资源，只留给需要人做反应的那几格。 */
.flag-ok { color: var(--text-2); }
/* 「看过」= 半交付：比「未导出」重、比「已导出」轻，三档一眼分得开。
   ★ 不能只靠文案区分 ★ 用户是扫这一列找「哪些还没交付」，颜色一样就得逐字读。 */
.flag-seen { color: var(--text-2); }
/* ★ 存活三态要一眼分得开 ★ 绿=活、红=死、灰斜体=还没测过（或测不出来）。
   「未检测」绝不能画成红色 —— 那会让人把还没测过的好号直接扔掉。 */
.flag-on { color: var(--green); font-weight: 600; }
.flag-off { color: var(--text-2); }
.flag-dead { color: var(--red); font-weight: 600; }
.flag-unknown { color: var(--text-2); font-style: italic; }
/* 「可重试」——白跑一趟、邮箱还干净，和「失败」（号已被占）必须一眼分得开 */
.flag-warn {
  display: inline-block;
  padding: 1px 9px;
  border-radius: var(--r-pill);
  background: var(--orange-fill);
  color: var(--orange);
  font-weight: 600;
}
/* 「可找回」——号在 OpenAI 那边已经建好，只差把令牌换回来，不是废号 */
.flag-info {
  display: inline-block;
  padding: 1px 9px;
  border-radius: var(--r-pill);
  background: var(--tint-fill);
  color: var(--tint);
  font-weight: 600;
}
/* 耗时偏长的号标出来 —— 通常是那个邮箱池收码慢 */
.flag-warn-text { color: var(--orange); }

/* 套餐 + 优惠资格合成一格：一眼看出「这号能不能白嫖 Plus」 */
/* 套餐就是一个词（free/plus），不值得占一个胶囊 —— 每行一个灰药丸只是噪声 */
.plan-badge {
  display: inline-block;
  color: var(--text-2);
  font-size: 12px;
  white-space: nowrap;
}
.plan-badge-good { color: var(--green); font-weight: 650; }
.plan-badge-plain { color: var(--text-2); }
.plan-badge-unknown { color: var(--text-2); }

/* ── 资格标签：Plus / Pro / GCash ──
   三态必须一眼可分：有=绿实心、无=灰淡、未检测=虚线框。
   ★ 未检测不能画成「无」★ 那会让人把没探过的好号当废号扔掉。 */
/* ★ 和套餐徽章排在同一行 ★ 换行排会把号池每一行撑到三行高，一屏只剩七八个号。
   td 本来就是 nowrap，这里跟着不换行即可。 */
.qual-row { display: inline-flex; flex-wrap: nowrap; gap: 5px; margin-left: 7px; vertical-align: middle; }
/* ★ 三态靠「字色 / 淡灰 / 虚线框」区分，不靠三种填充色 ★
   26 行 × 3 个资格 = 78 个标签，全做成实心药丸时整屏就剩这些色块了。
   有资格才是要看见的那一个：绿字加粗；无资格淡灰；未检测虚线框（绝不能长得像「无」）。 */
.qual-tag {
  padding: 0 5px;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  font-size: 12px;
  line-height: 1.7;
  white-space: nowrap;
}
.qual-yes { color: var(--green); font-weight: 650; }
.qual-no { color: var(--text-2); }
.qual-unknown { border: 1px dashed var(--control-border); color: var(--text-2); }

/* 邮箱池顶部的指标条：比总览的读数板更紧凑，一行放得下 6 个 */
.metric-strip {
  display: flex;
  flex-wrap: wrap;
  margin: 0 0 14px;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.mini-metric {
  flex: 1 1 132px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 11px 14px 12px;
  border-right: 1px solid var(--hairline);
}
.mini-metric:last-child { border-right: 0; }
/* JS 渲染的是 .mini-metric-caption + <strong>，两套命名都留着以防调用方变化 */
.mini-metric-caption, .mini-metric-label { color: var(--text-2); font-size: 12px; }
.mini-metric strong, .mini-metric-value {
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.mini-metric-good strong, .mini-metric-success .mini-metric-value { color: var(--green); }
.mini-metric-warn strong { color: var(--orange); }
.mini-metric-bad strong, .mini-metric-danger .mini-metric-value { color: var(--red); }

/* ---------- 图表 / 分布 / 活动 ---------- */

.chart { padding: 12px 14px; }
.chart svg { display: block; width: 100%; height: 196px; }
/* 没有任何任务时不画空坐标系 —— 空图配一个「峰值 1」读着像图挂了 */
.chart-empty { display: grid; place-items: center; min-height: 196px; margin: 0; }
.legend { display: flex; gap: 12px; color: var(--text-2); font-size: 12px; }
.legend i { display: inline-block; width: 8px; height: 8px; margin-right: 5px; border-radius: 2px; }

.distribution { display: grid; align-content: start; gap: 9px; min-height: 200px; padding: 12px 14px; }
.dist-row {
  display: grid;
  grid-template-columns: 94px minmax(0, 1fr) 46px;
  align-items: center;
  gap: 10px;
  font-size: 13px;
}
.dist-label { overflow: hidden; color: var(--text-2); text-overflow: ellipsis; white-space: nowrap; }
.dist-track { height: 8px; overflow: hidden; border-radius: var(--r-pill); background: var(--fill); }
.dist-fill { height: 100%; border-radius: var(--r-pill); background: var(--tint); transition: width var(--dur) var(--ease); }
.dist-fill-neutral { background: var(--text-3); }
.dist-fill-danger { background: var(--red); }
.dist-fill-info { background: var(--tint); }
.dist-value { color: var(--text); font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
/* 三态里的「未检测」：比数字更淡且斜体，一眼能和真实的 0 区分开 */
.dist-value-unknown { color: var(--text-2); font-weight: 400; font-style: italic; }

.activity-list { min-height: 236px; max-height: 292px; margin: 0; padding: 0; overflow: auto; list-style: none; }
.activity-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 12px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--hairline);
  font-size: 14px;
}
.activity-item:last-child { border-bottom: 0; }
.activity-main { min-width: 0; overflow: hidden; color: var(--text); text-overflow: ellipsis; white-space: nowrap; }
.activity-meta { color: var(--text-2); font-family: var(--mono); font-size: 12px; }
.activity-outcome { color: var(--text-2); }
.outcome-failed, .outcome-rate_limited, .outcome-denied { color: var(--red); }

/* ---------- 日志 ---------- */

.log-panel-card { display: flex; min-height: 420px; flex-direction: column; }
.log-heading { align-items: flex-start; }
.live-label {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 26px;
  padding: 0 11px;
  border-radius: var(--r-pill);
  background: var(--fill);
  color: var(--text-2);
  font-size: 12px;
  white-space: nowrap;
}
.log-toolbar {
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--hairline);
}
.log-offset { margin-right: auto; color: var(--text-2); font-family: var(--mono); font-size: 12px; }
.toolbar-control {
  height: 26px;
  padding: 0 10px;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.toolbar-control:hover { background: var(--fill); color: var(--text); }
.toolbar-control:active { transform: scale(.94); }
.toolbar-control.is-active { background: var(--tint-fill); color: var(--tint); }

#log-panel {
  flex: 1;
  min-height: 320px;
  max-height: 560px;
  margin: 0;
  padding: 14px 16px;
  overflow: auto;
  background: var(--code-bg);
  color: var(--text-2);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
}
.log-placeholder { color: var(--text-2); }

/* ---------- 文件 ---------- */

.upload-panel form { padding: 14px 18px; }
/* ★ 保持 56px，别再放大 ★ 上传是一次性入口，下面那张文件列表才是天天看的东西 ——
   它占多高，列表就少几行。所以做成一条横排：图标 + 一行字，不要竖着堆三层。 */
.file-dropzone {
  min-height: 56px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 4px 10px;
  padding: 8px 14px;
  /* 虚线框是「这里可以放文件」的唯一提示，按控件边界给 3:1，不用极淡的分隔线色 */
  border: 1.5px dashed var(--control-border);
  border-radius: var(--r-lg);
  color: var(--text-2);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.file-dropzone:hover, .file-dropzone.is-dragging, .file-dropzone:focus-within {
  border-color: var(--tint);
  background: var(--tint-fill);
}
.file-dropzone strong { color: var(--text); font-size: 14px; font-weight: 600; }
.file-dropzone > span:last-of-type { color: var(--text-2); font-size: 12px; }
.upload-icon .ic { width: 15px; height: 15px; }
.upload-icon {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--tint-fill);
  color: var(--tint);
}
.file-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.upload-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; }
.upload-actions .muted {
  max-width: 60%;
  overflow: hidden;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- 弹层 ----------
   iOS 的 sheet：大圆角、厚阴影、背景压暗并轻微模糊。 */

.modal {
  width: min(460px, calc(100vw - 32px));
  padding: 0;
  border: 0;
  border-radius: var(--r-xl);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-2);
}
.modal::backdrop {
  background: rgba(0, 0, 0, .5);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.modal[open] { animation: sheet-in var(--dur) var(--ease) both; }
@keyframes sheet-in { from { opacity: 0; transform: scale(.96); } }
.modal-card { padding: 22px 24px 20px; }
.modal-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 18px;
}
.modal-heading h2 { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -.02em; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
.confirm-text { margin: 0 0 8px; color: var(--text); font-size: 16px; line-height: 1.5; }

/* ★ 宽度要设在 <dialog> 上，不是 .modal-card 上 ★
   .modal 本身是 min(460px, …)，只把内部卡片撑宽只会让内容溢出容器 ——
   表现就是弹窗里出现横向滚动条、左边标签被裁掉（实测「并发数」只剩「数」）。 */
#register-dialog { width: min(760px, calc(100vw - 32px)); }
#confirm-dialog { width: min(440px, calc(100vw - 32px)); }
#confirm-dialog .field-hint { margin-bottom: 4px; }

/* 发起注册弹窗：每种收码方式一组，各填各的参数 */
.register-group {
  margin-bottom: 14px;
  padding: 14px 16px 4px;
  border-radius: var(--r-lg);
  background: var(--surface-2);
}
.register-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.register-group-head strong { font-size: 15px; font-weight: 700; }
.register-group-head .muted { font-size: 13px; }
.register-group-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0 14px; }
.register-group .field { margin-bottom: 12px; }
.register-group input:disabled, .register-group select:disabled { opacity: .45; cursor: not-allowed; }

/* 右侧抽屉：iPad 的侧边 sheet */
.drawer {
  position: fixed;
  z-index: 60;
  inset: 0;
  display: flex;
  justify-content: flex-end;
  background: rgba(0, 0, 0, 0);
  visibility: hidden;
  transition: background var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.drawer.is-open { background: rgba(0, 0, 0, .5); visibility: visible; transition-delay: 0s; }
.drawer-card {
  width: min(420px, 100vw);
  height: 100%;
  padding: 22px 24px;
  overflow: auto;
  background: var(--surface);
  box-shadow: var(--shadow-2);
  transform: translateX(100%);
  transition: transform var(--dur) var(--ease);
}
.drawer.is-open .drawer-card { transform: translateX(0); }
.drawer-note {
  margin-top: 8px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--fill);
  color: var(--text-2);
  font-size: 13px;
  line-height: 1.6;
}

/* 详情列表：iOS 分组表那种「左标签右值」 */
.detail-list {
  margin: 0 0 16px;
  padding: 0;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--surface-2);
}
.cred-row-copy { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
.cred-row-copy:hover { background: var(--surface-2); }
.cred-row-copy:focus-visible {
  outline: 2px solid var(--tint);
  outline-offset: 1px;
}
.detail-row {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr);
  border-bottom: 1px solid var(--hairline);
}
.detail-row:last-child { border-bottom: 0; }
.detail-list dt {
  padding: 9px 12px;
  color: var(--text-2);
  font-size: 13px;
}
.detail-list dd {
  position: relative;
  margin: 0;
  padding: 9px 12px;
  overflow: hidden;
  color: var(--text);
  font-size: 14px;
  text-overflow: ellipsis;
}
.detail-copy-row { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
.detail-copy-row:hover { background: var(--fill); }
.detail-copy-row:focus-visible {
  outline: 2px solid var(--tint);
  outline-offset: -2px;
}
.detail-copy-row dd { padding-right: 52px; }
.detail-copy-row dd::after {
  content: '复制';
  position: absolute;
  right: 12px;
  top: 50%;
  color: var(--tint);
  font-size: 12px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(-50%);
  transition: opacity var(--dur-fast) var(--ease);
}
.detail-copy-row:hover dd::after,
.detail-copy-row:focus-visible dd::after { opacity: 1; }

/* Toast：右下角浮层，左侧一道语义色 */
.toast-region { position: fixed; z-index: 100; right: 18px; bottom: 18px; display: grid; gap: 8px; }
.toast {
  position: relative;
  min-width: 250px;
  max-width: 360px;
  padding: 13px 16px 13px 20px;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface-2);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  box-shadow: var(--shadow-2);
  animation: toast-in var(--dur) var(--ease) both;
}
.toast::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  background: var(--text-3);
}
.toast-success::before { background: var(--green); }
.toast-error::before { background: var(--red); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }

/* ---------- 设置页 ---------- */

.settings-grid { display: grid; gap: 12px; }
/* ★ 同一行里的输入框必须对齐 ★ .field 是 grid，默认 align-content 会**拉伸内部行** ——
   同一行里某个字段被两行说明撑高时，它自己的标签和输入框之间也被拉开，控件就掉下去了。
   align-content: start 让内部行按内容排；再给标签统一行高，标签长短不一也不会错位。 */
.settings-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0 18px;
  align-items: start;
}
.settings-row .field { align-content: start; margin-bottom: 18px; }
.settings-row .field > span:first-child { display: block; min-height: 22px; line-height: 22px; }
/* 说明文字占位高度统一，避免有说明/没说明的两列底部参差 */
.settings-row .field-hint { display: block; margin-top: 4px; min-height: 18px; }
.settings-sub { margin: 8px 0 10px; color: var(--text-2); font-size: 14px; font-weight: 600; }
.settings-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 18px;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
}
.settings-actions .muted { font-size: 13px; }

/* ── 撑满可视高度 ──────────────────────────────────────────────────────────
   面板不吃满视口时，下面会空出一大片背景（用户原话「这一块空缺是干嘛的」）。
   号池/邮箱池就是「一张大表撑满一页」；任务页和文件页的分栏同理，
   每一栏各自撑满、栏内表格/日志吃掉剩余高度，底部那行「显示 N/N 条」仍固定可见。 */
#pool .panel,
#register .panel,
#phone .panel {
  display: flex;
  flex-direction: column;
  /* ★ 用 max-height，不是 height ★ 写死 height 会让面板永远占满一屏：
     筛出 6 行时表格区照样撑到底，行下面留一大片空白，页脚被顶到屏幕最下边
     （用户原话「列表下面又留出一片空白干嘛」）。
     改成 max-height 之后：行多 → 撑到上限、表格内部滚动，页脚仍贴在视口底；
     行少 → 面板跟着内容收缩，页脚紧跟在最后一行下面，不留空。 */
  height: calc(100vh - var(--shell-inset));
}
#pool .table-wrap,
#register .table-wrap,
#phone .table-wrap {
  /* ★ 不要 min-height ★ 那 220px 就是「6 行数据下面一大片空白」的直接来源。
     内容少就让它自然收缩；空列表有 .empty-state 那一行占位，不会塌成一条缝。 */
  min-height: 0;
  max-height: none;
  flex: 0 1 auto;          /* 只在超出时压缩，不主动抢占剩余空间 */
}
/* 头部/筛选/批量条/页脚不参与压缩，否则表格一长它们就被挤扁 */
#pool .panel-heading, #pool .filter-bar, #pool .bulk-bar, #pool .panel-foot,
#register .panel-heading, #register .filter-bar, #register .bulk-bar,
#register .metric-strip, #register .panel-foot,
#phone .panel-heading, #phone .filter-bar, #phone .bulk-bar,
#phone .metric-strip, #phone .panel-foot { flex: 0 0 auto; }

#tasks > .split,
#files > .split {
  height: calc(100vh - var(--shell-inset));
  align-items: stretch;
}
#tasks > .split > .panel,
#tasks > .split > * > .panel,
#files > .split > .panel,
#files > .split > * > .panel {
  display: flex;
  min-height: 0;           /* 不加这条，flex 子项会被内容撑破而不是内部滚动 */
  max-height: 100%;
  flex-direction: column;
}
#tasks .panel-heading, #tasks .filter-bar, #tasks .log-toolbar,
#files .panel-heading, #files .bulk-bar, #files .upload-panel form { flex: 0 0 auto; }
#tasks .table-wrap,
#files .table-wrap { min-height: 120px; max-height: none; flex: 1 1 auto; }
#tasks #log-panel { max-height: none; flex: 1 1 auto; }

/* ★ 大表格的重排成本 ★ 几百行 × 十几列时，改窗口大小会让浏览器把整张表重新测量
   一遍，实测能把整机卡住。两个措施：
     1) 渲染上限（在 JS 里，默认 200 行 + 「加载更多」）—— 治本
     2) content-visibility: auto —— 让浏览器跳过视口外行的布局与绘制
   contain-intrinsic-size 给一个行高估计值，避免滚动条长度反复跳动。 */
#reg-rows tr,
#pool-rows tr {
  content-visibility: auto;
  contain-intrinsic-size: auto 32px;
}

/* ---------- 响应式 ---------- */

@media (max-width: 1279px) {
  .grid-2, .split, .split-even { grid-template-columns: 1fr; }
  .log-panel-card { min-height: 380px; }
}

@media (max-width: 1023px) {
  :root { --sidebar: 68px; }
  .sidebar { padding-inline: 10px; }
  .sidebar-brand { justify-content: center; padding: 0; }
  /* ★ .side-user > .nav-text 要单独写 ★ 媒体查询不加特异性：上面的
     `.side-user > div`（0,1,1）比 `.nav-text`（0,1,0）高，只写 .nav-text 压不住它 ——
     侧栏收成 68px 之后用户名还硬挤在那儿，横着糊出去一截。 */
  .sidebar-brand > div:last-child, .nav-label, .nav-text, .side-user > .nav-text,
  .side-service .muted, .side-build, .side-logout { display: none; }
  .nav-item { justify-content: center; padding: 0; }
  .nav-badge { position: absolute; margin: -18px 0 0 22px; }
  .side-service, .side-user { justify-content: center; padding-inline: 0; }
  /* 窄屏分栏会堆叠，这时再锁高度就会把内容压扁 —— 交还给自然高度 */
  #tasks > .split, #files > .split { height: auto; }
  #tasks > .split > .panel, #files > .split > .panel { max-height: none; }
  #tasks .table-wrap, #files .table-wrap { max-height: min(60vh, 560px); }
}

@media (max-width: 900px) {
  :root { --shell-inset: 76px; }
}

@media (max-width: 767px) {
  :root { --sidebar: 0px; }
  .app-shell { display: block; padding-bottom: 64px; }
  /* 侧栏变成 iOS 底部标签栏 */
  .sidebar {
    position: fixed;
    z-index: 40;
    inset: auto 0 0;
    width: 100%;
    height: 62px;
    display: block;
    padding: 6px 4px;
    overflow: hidden;
    border: 0;
    border-top: 1px solid var(--hairline);
  }
  .sidebar-brand, .sidebar-footer { display: none; }
  /* ★ 六个入口就要六列 ★ 写 5 列时「设置」会被挤到第二行、掉到屏幕外 ——
     手机上根本点不到设置页（这个坑是老版本带过来的）。 */
  .sidebar-nav { height: 100%; display: grid; grid-template-columns: repeat(6, 1fr); margin: 0; }
  .nav-item { height: 100%; flex-direction: column; justify-content: center; gap: 2px; margin: 0; padding: 0 2px; font-size: 12px; }
  .nav-item .nav-text { display: block; overflow: hidden; max-width: 100%; font-size: 12px; text-overflow: ellipsis; }
  .nav-item .ic { width: 22px; height: 22px; }
  .nav-item.active { background: transparent; }
  .nav-badge { margin: -34px 0 0 20px; }
  .topbar { height: 52px; padding: 0 14px; }
  .mobile-brand {
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border-radius: var(--r-sm);
    background: var(--tint);
    color: #fff;
    font-size: 12px;
    font-weight: 800;
  }
  .topbar-status, .breadcrumb { display: none; }
  .topbar h1 { font-size: 17px; }
  .topbar-actions { gap: 6px; }
  .main-content { padding: 14px 12px 28px; }
  .metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .metric strong { font-size: 24px; }
  .table-wrap { max-width: calc(100vw - 24px); }
  #task-table { min-width: 720px; }
  #pool-table { min-width: 1040px; }
  #reg-table { min-width: 860px; }
  #artifact-table { min-width: 560px; }
  .filter-bar .mini-input { min-width: 0; flex: 1; }
  .bulk-bar { flex-wrap: wrap; }
  .drawer-card { width: 100vw; padding: 18px; }
  .toast-region { right: 12px; bottom: 76px; left: 12px; }
  .toast { min-width: 0; max-width: none; }
  .auth-card { padding: 26px 22px 22px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* 任务进度带：目标 / 成功 / 失败，跑的过程中就能看到，不用等结束 */
.log-progress {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border, rgba(0,0,0,.08));
}
.progress-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--surface-2, rgba(120,120,128,.12));
  font-size: 12px;
}
.progress-chip-label { color: var(--text-tertiary, var(--faint)); }
.progress-chip strong { font-size: 13px; font-variant-numeric: tabular-nums; }
.progress-chip-ok strong { color: var(--ok-fg, #22c55e); }
.progress-chip-bad strong { color: var(--danger, #ef4444); }

/* 总览卡片：说明行长短不一会把数字挤得高低不齐 —— 给它一个固定的两行高度 */
.metric-detail {
  display: block;
  min-height: 2.4em;
  line-height: 1.2;
}

/* ══════════════════════════════════════════════════════════════════════════
   总览页重构（2026-08-14）
   ★ 为什么改 ★ 原来 9 个等权重的 metric 平铺一排，每个只有"标签+数字+一行说明"，
   数字全是同字号同颜色 —— 扫一眼分不出哪个重要，而且下半屏大片留白。
   用户原话："总揽的数据参数展示很低级简单"。
   改法参考监控面板的通行做法（Grafana stat panel / Datadog / Vercel Analytics）：
     · 分主次两档，主指标给大号等宽数字 + 占比条，次指标压缩成一行
     · 数字一律 tabular-nums —— 刷新时位数变化不会让整行跳动，这是"仪表感"的来源
     · 状态用色条表达比例，别再靠并列数字让人自己算
   ★ 不做的 ★ 渐变文字、彩色卡片堆砌、装饰性动效 —— 那是 SaaS 落地页的语言，
   放在常年开着的运维面板上只会干扰读数。
   颜色一律用既有 token（那套值每个都有实测对比度，见 :root 注释），不新增色值。
   ══════════════════════════════════════════════════════════════════════════ */

/* 数字统一等宽：位数变化不跳动 */
.stat-value,
.stat-sub-value,
.cap-value,
.cap-pct {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ── 主指标（3 个）───────────────────────────────────────────────────── */
.stat-primary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1px;                      /* 1px 间隙 + 容器底色 = 发丝分隔，不用画边框 */
  background: var(--separator);
  border: 1px solid var(--separator);
  border-radius: var(--r-lg, 12px);
  overflow: hidden;
}
.stat-primary {
  display: grid;
  gap: 10px;
  padding: 16px 18px 14px;
  background: var(--surface);
}
.stat-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.stat-label {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .02em;
  color: var(--text-2);
}
.stat-delta {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
}
.stat-delta.is-up { color: var(--green); }
.stat-delta.is-down { color: var(--red); }
.stat-value {
  font-size: 30px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -.02em;
  color: var(--text);
}
.stat-value.is-muted { color: var(--text-3); }
.stat-unit {
  margin-left: 4px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-3);
}

/* 占比条：把"3963/4273"这种关系画出来，别让人自己算 */
.stat-bar {
  display: flex;
  height: 4px;
  overflow: hidden;
  border-radius: var(--r-pill);
  background: var(--fill);
}
.stat-bar-seg { height: 100%; transition: width var(--dur, .2s) var(--ease, ease); }
.seg-ok { background: var(--green); }
.seg-warn { background: var(--orange); }
.seg-bad { background: var(--red); }
.seg-idle { background: var(--chart-idle); }
.seg-tint { background: var(--tint); }

.stat-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  font-size: 11px;
  color: var(--text-3);
}
.stat-foot b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

/* ── 次指标（一行压缩）──────────────────────────────────────────────── */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin-top: 1px;
  background: var(--separator);
  border: 1px solid var(--separator);
  border-radius: var(--r-lg, 12px);
  overflow: hidden;
}
.stat-sub {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  background: var(--surface);
  font-size: 12px;
}
.stat-sub-label { color: var(--text-2); }
.stat-sub-value {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.stat-sub-value.is-muted { color: var(--text-3); font-weight: 500; }

/* ── 代理流量容量条 ─────────────────────────────────────────────────── */
.cap-list { display: grid; gap: 14px; padding: 14px 16px; }
.cap-row { display: grid; gap: 6px; }
.cap-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  font-size: 12px;
}
.cap-name { font-weight: 500; color: var(--text); }
.cap-value { color: var(--text-2); font-size: 11px; }
.cap-pct { font-size: 12px; font-weight: 600; color: var(--text-2); }
.cap-track {
  position: relative;
  height: 6px;
  overflow: hidden;
  border-radius: var(--r-pill);
  background: var(--fill);
}
.cap-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--green);
  transition: width var(--dur, .2s) var(--ease, ease);
}
/* 阈值配色：40% 转黄、20% 转红。用 token 不新增色值 */
.cap-fill.is-warn { background: var(--orange); }
.cap-fill.is-low { background: var(--red); }
.cap-pct.is-warn { color: var(--orange); }
.cap-pct.is-low { color: var(--red); }
/* 查不到余量的家：显示"—"和原因，不画成 0（画 0 会被读成"用完了"）*/
.cap-row.is-unknown .cap-track { opacity: .35; }
.cap-note { font-size: 11px; color: var(--text-3); }

@media (prefers-reduced-motion: reduce) {
  .stat-bar-seg,
  .cap-fill { transition: none; }
}
