/* ─────────────────────────────────────────────────────────────────────────
   设计原则（整份样式都从这四条推出来）：
     1. 一行一个手机号，右边只有一个该点的按钮；其它动作在 ⋯ 里。
     2. 整页只有四种颜色：黑（正文）、灰（次要）、一个蓝（唯一的主按钮 + 进行中）、
        一个红（坏了 / 删除）。绿和黄不再出现在列表里——「太多太花」就是从它们来的。
     3. 字号只有 4 档，粗体只给一行里最该被扫到的那个词。
     4. 低频的东西（定时、代理、接码、名单维护）不占主页面，收进右侧抽屉。
   ───────────────────────────────────────────────────────────────────────── */
:root {
  color-scheme: light;
  --ink: #16191d;
  --muted: #667079;
  --faint: #9aa3ab;
  --line: #e5e8ec;
  --line-strong: #cbd1d8;
  --surface: #ffffff;
  --surface-subtle: #f4f6f8;
  /* 隔行底色。比 --surface-subtle 再深一点：表头用的就是 subtle，
     隔行如果和表头同色，第一行看起来像第二条表头。 */
  --row-alt: #eff2f6;
  --canvas: #f7f8fa;
  --dark: #16191d;

  /* 唯一的品牌色：主按钮、选中态、进行中 */
  --accent: #2563eb;
  --accent-strong: #1d4fd7;
  --accent-soft: #eaf0fe;

  --ok: #15803d;
  --warning: #b45309;
  --warning-soft: #fdf1e2;
  /* 和服务器断开时开着的窗口里那一行（connection.js 断开时换成带几点断的，见 body.is-stale .modal-card::before） */
  --stale-text: "页面连不上程序了，画面停住了，正在自动重连（只是页面断了的话，程序照常在抢）";
  /* 红和蓝都比原来浅一档。上限卡在白底 4.5:1（小字的可读下限）：
     --danger 5.04、--live 4.55，再浅就看不清了。
     --accent 不动：它是主按钮的**底色**，白字压在上面，浅了白字就糊。 */
  --danger: #c0453f;
  --danger-soft: #fdecec;
  --live: #3f72d9;

  --t-meta: 12px;
  --t-body: 13px;
  --t-item: 14px;
  --t-title: 16px;
  --t-hero: 20px;

  --radius: 8px;
  --radius-lg: 12px;
  /* ── 表格列宽 ──────────────────────────────────────────────────────
     表头和每一行共用这一套数字，所以不可能对不齐。左半边（手机号那几列）
     宽度跟着窗口 clamp 缩放（不做断点跳变，否则某个宽度上会突然折行），
     右半边的「上次结果」吃掉剩余宽度——窗口越宽它看得越全。
     下限是量出来的：780px 下全部取下限，一整行 629px，容器有 702px。 */
  --c-dot: 10px;
  --c-phone: clamp(112px, 12vw, 178px);
  --c-state: clamp(70px, 5.2vw, 74px);   /* 放得下「官网限流中」「登录已过期」 */
  --c-life: clamp(74px, 5.4vw, 82px);    /* 放得下最长的「1 小时 18 分」，实测 70px */
  --c-date: 46px;   /* 固定：「09–24」加粗就要 42px，给 basis 留余量，否则它会把后面的列顶偏 */
  --c-times: clamp(84px, 7.2vw, 104px);
  --c-pact: clamp(76px, 6.1vw, 88px);   /* 手机号自己的操作：登录 / 绑定本人 / 建任务 */
  --c-act: clamp(72px, 6.7vw, 96px);    /* 任务的操作：开始 / 停止 */
  --c-gap: clamp(6px, 0.7vw, 10px);
  /* 左半边总宽 = 点 + 号 + 状态 + 剩余 + 号的操作 + 四个间隙。
     漏算任何一列，那一列就会被挤到第二行去——「重新登录」当初就是这么折下去的。 */
  --head-w: calc(var(--c-dot) + var(--c-phone) + var(--c-state) + var(--c-life) + var(--c-pact) + 4 * var(--c-gap));
  --shadow: 0 10px 30px rgba(16, 24, 40, .12);
  font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Inter, system-ui, sans-serif;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-size: var(--t-body);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

/* 设置抽屉打开时锁住页面滚动，焦点也只在抽屉内循环。 */
body.settings-open { overflow: hidden; }
body.modal-open { overflow: hidden; }
[hidden] { display: none !important; }
img[src^="/icons/"] { width: 16px; height: 16px; flex: 0 0 auto; }

h1, h2, h3, p { margin: 0; }
button { font: inherit; color: inherit; cursor: pointer; }
button:disabled { cursor: not-allowed; }

/* ── 控件 ──────────────────────────────────────────────────────────────── */
input, select, textarea {
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body);
  min-width: 0;
}
textarea { height: auto; padding: 8px 10px; line-height: 1.5; resize: vertical; width: 100%; }
input[type="checkbox"] { width: 15px; height: 15px; margin: 0; padding: 0; accent-color: var(--accent); }
input[type="range"] { border: 0; padding: 0; background: transparent; }
input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
textarea[aria-invalid="true"] { border-color: var(--danger); }
input:disabled, select:disabled { color: var(--faint); background: var(--surface-subtle); }
/* 只读 ≠ 禁用：内容仍然要看得清（平台分配的手机号就是这种），但一眼看出不能改 */
input:read-only:not([type="range"]) { background: var(--surface-subtle); cursor: default; }
.search { width: 100%; }
.grow { flex: 1 1 200px; }
.spacer { flex: 1 1 auto; }

.button {
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--t-body);
  font-weight: 500;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: background .12s, border-color .12s, color .12s;
}
.button:hover:not(:disabled) { border-color: var(--faint); background: var(--surface-subtle); }
.button:disabled { color: var(--faint); border-color: var(--line); background: var(--surface-subtle); }
/* 10-09 凌晨（复查 B-L11）：现在不让点的按钮（点了说为什么）看着是灰的 */
.button.is-blocked { color: var(--faint); border-color: var(--line); background: var(--surface-subtle); }
.button.primary { border-color: var(--accent); background: var(--accent); color: #fff; }
.button.primary:hover:not(:disabled) { border-color: var(--accent-strong); background: var(--accent-strong); }
.button.primary:disabled { border-color: var(--line); background: var(--line); color: var(--faint); }
.button.primary img { filter: invert(1); }
.button.danger { color: var(--danger); }
.button.danger:hover:not(:disabled) { border-color: var(--danger); background: var(--danger-soft); }
.button.compact { height: 28px; padding: 0 11px; font-size: var(--t-meta); }
/* 无边框文字按钮：任务行的「开始 / 停止」。悬停才现出边框，平时只是一个词。 */
.button.ghost { border-color: transparent; background: transparent; color: var(--muted); font-weight: 500; }
.button.ghost:hover:not(:disabled) { border-color: var(--line-strong); background: var(--surface); color: var(--ink); }
.button.ghost.danger { color: var(--danger); }
.button.ghost.danger:hover:not(:disabled) { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.button.ghost:disabled { background: transparent; border-color: transparent; color: var(--faint); }
.button.ghost.compact { height: 22px; padding: 0 6px; }
.button.wide { width: 100%; }
.button.attention { border-color: var(--warning); color: var(--warning); }
.button img { width: 14px; height: 14px; }

.icon-button {
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
  display: inline-grid;
  place-items: center;
  list-style: none;
}
.icon-button:hover:not(:disabled) { background: var(--surface-subtle); border-color: var(--line); color: var(--ink); }
a.icon-button { text-decoration: none; }
.icon-button:disabled { opacity: .4; }
.icon-button::-webkit-details-marker { display: none; }
.icon-button.danger:hover:not(:disabled) { color: var(--danger); background: var(--danger-soft); }

.link-action { border: 0; padding: 0 2px; background: transparent; color: var(--muted); font-size: var(--t-meta); }
.link-action:hover:not(:disabled) { color: var(--ink); text-decoration: underline; }

.quiet-danger { border: 0; padding: 6px 8px; background: transparent; color: var(--faint); font-size: var(--t-meta); border-radius: var(--radius); }
.quiet-danger:hover:not(:disabled) { color: var(--danger); background: var(--danger-soft); }
/* 没东西可删时整个藏起来；但**正在删**的时候必须看得见——
   setBusy 做的就是 disabled + .is-busy，而 .is-busy 那个转圈是伪元素，
   visibility:hidden 会把它一起隐掉。实测点「删除全部手机号」按钮当场消失，
   上百个号要跑十几秒，人只看到一片空白，以为点坏了。 */
.quiet-danger:disabled:not(.is-busy) { visibility: hidden; }

.muted { color: var(--muted); font-size: var(--t-body); }
/* setBusy 加的：请求在飞的时候给个动静，否则慢接口（取号能到十几秒）看着像卡死 */
.is-busy { position: relative; color: transparent !important; }
.is-busy::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border: 2px solid currentcolor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--muted);
  animation: spin .7s linear infinite;
}
.button.primary.is-busy::after { color: #fff; }
@keyframes spin { to { transform: rotate(360deg); } }
/* 校验没过的输入框。少了这条，「身份证号不对」只有一行小字，框本身毫无反应 */
.invalid { border-color: var(--danger); background: var(--danger-soft); }
/* 和本地服务断了：整页压暗，避免把停住不动的旧数据当成实时的 */
body.is-stale .list, body.is-stale .grab-status { opacity: .55; }
/* 10-03（复查）：开着的窗口（取号、分配……）里也说一声：底下那条告警被窗口的遮罩挡着，窗口里「正在取号 5/24」看着还像在动
   （几点断的由 connection.js 写进 --stale-text） */
body.is-stale .modal:not([hidden]) .modal-card::before {
  content: var(--stale-text);
  display: block; margin: 0 0 12px; padding: 7px 10px; border-radius: 8px;
  background: var(--warning-soft); color: var(--warning); font-size: var(--t-meta); line-height: 1.5;
}
.hint, .pick-hint { margin: 0; color: var(--muted); font-size: var(--t-meta); line-height: 1.5; }
.form-error { color: var(--danger); font-size: var(--t-body); }
/* 10-10：页面自己在重连（人不用做什么）：橙色 */
.form-error.is-calm { color: var(--warning); }

/* ── 登录页 ────────────────────────────────────────────────────────────── */
.auth-gate { min-height: 100vh; padding: 20px; display: grid; place-items: center; }
.auth-card {
  width: min(340px, 100%);
  padding: 28px 24px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  display: grid;
  gap: 8px;
}
.auth-icon { width: 22px; height: 22px; }
.auth-card h1 { margin: 4px 0 12px; font-size: var(--t-title); font-weight: 600; }
.auth-card label { color: var(--muted); font-size: var(--t-meta); }
.auth-card .button { margin-top: 8px; }

/* ── 页面骨架 ──────────────────────────────────────────────────────────── */
/* 2016 = 两栏刚好排得下（轨道 998×2 + 间距 18 + 卡片边框 2）。
   这个硬顶和轨道下限是一对：曾经它是 1900，而那时两栏要 2380，
   于是分栏那套代码（视觉行配色、中间竖线）永远跑不到，是死的。
   改一个必须改另一个，测试盯着这条等式。 */
.app { width: min(2016px, 100% - 32px); margin: 0 auto; padding: 4px 0 44px; }
/* 只排得下一栏的时候，整页收窄到 1180px。
   一个任务横跨 1400px 的屏幕，等于把「上次结果」那一列拉成一大片空白——难看也难扫。
   1180px 是一栏读着舒服的宽度：「上次结果」能拿到 370px，最长那句正好一行装下。
   收的是 .app 不是列表卡片：顶栏、列表、页脚是同一根柱子，只收中间那一段会错开。
   2048px 起栅格自己分成左右两栏、中间空 18px，那时候不能再收——收了就又变回一栏。
   所以这里的 2047 必须正好是分栏断点减一，中间不许留缝（测试盯着）。 */
@media (max-width: 2047px) {
  .app { width: min(1180px, 100% - 32px); }
}
/* 曾经在这儿加过「号少于 4 个就不分栏」。去掉了：两个号并排是明确想要的效果，
   而「一个号别摊成整屏宽」由上面那条收窄规则管得住——auto-fill 排出两条轨道时，
   唯一那个号只占左边一条（998px），右边空着，本来就不会被拉满。 */

/* 吸顶：一百个号有四屏高，滚下去之后「添加手机号 / 设置 / 搜索」全跟着没了，
   想加个号得先滚回最上面。 */
.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: nowrap;
  margin-bottom: 8px;
  padding: 6px 0;
  background: var(--canvas);
}
/* 最左边的 logo：比按钮略高一点，不撑高顶栏。原图是正方形，缩到 36px 字看不清，所以裁掉上下的花纹、只留字和建筑（public/logo.png 是 3 倍图） */
.topbar-logo { flex: 0 0 auto; display: block; width: 64px; height: 36px; border-radius: 8px; }
/* 统计：两行叠着，跟在导出后面。横着排四段要 190px，会把右边的按钮挤下去。 */
.brand-summary {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  color: var(--faint);
  font-size: 11px;
  line-height: 1.35;
}
.brand-summary > span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* 第二行是空的（没有在抢、也没有待处理）时别留一道空隙 */
.brand-summary > span:empty { display: none; }
/* 「表格被改过了」那句提示。压成灰字、不给底色：它是「顺带一提」，
   不是要你立刻放下手里的事——原来那个蓝底胶囊比旁边的主按钮还抢眼。 */
.docs-stale {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  color: var(--faint);
  font-size: 11px;
  line-height: 1.35;
}
.docs-stale > span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

.topbar-actions { margin-left: auto; flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
/* 没抢到过票就整组不显示，连带那条分隔线 */
/* 左边「看结果」和右边「做事」之间的分界 */
.topbar-sep { flex: 0 0 auto; width: 1px; height: 18px; background: var(--line-strong); }

/* ── 底部状态条 ────────────────────────────────────────────────────────
   固定在屏幕最下面的一行小字，像编辑器的状态栏。
   左边是告警、右边是定时/倒计时。两者都没有时整条消失，一像素不占。
   固定而不是跟着文档流走：号一多列表就滚出屏幕，摆在文档末尾等于看不见。 */
.statusbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 24px;
  padding: 2px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--t-meta);
  line-height: 1.4;
}
/* 两条都藏起来时整条不显示 */
.statusbar:not(:has(> *:not([hidden]))) { display: none; }

.alert { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; }
/* 只有图标是红的。整条染红在屏幕底边会变成一条刺眼的横杠，而这些多半是
   「程序自己会恢复」的通知；真正紧急的那一行在列表里已经是红的了。
   用 mask 而不是 <img>：img 没法改颜色，而深色模式下全局那条 invert 会把它翻成青色。 */
.alert-icon {
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  background: var(--danger);
  -webkit-mask: url("/icons/triangle-alert.svg") center / contain no-repeat;
  mask: url("/icons/triangle-alert.svg") center / contain no-repeat;
}
/* 10-09 凌晨复查（A8）：程序自己在处理的（在自动重登、页面在自动重连、只说几点到期）三角用橙色；要人处理的才红 */
.alert.is-calm .alert-icon { background: var(--warning); }
.alert > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
/* 摊开时要能多行显示：就绪检查可能一次报四五条，挤成一行加省略号等于没报。
   状态栏本来是一行高，这里让它跟着长，最多占三分之一屏。 */
.alert > span.alert-expanded { white-space: pre-line; overflow: auto; text-overflow: clip;
  max-height: 33vh; line-height: 1.5; padding: 4px 0; }
.alert:has(> span.alert-expanded) { align-items: flex-start; }
.alert > .link-action { flex: 0 0 auto; color: var(--live); text-decoration: underline; }
.alert > .link-action:hover { color: var(--accent-strong); }

.grab-status { margin-left: auto; flex: 0 0 auto; display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.grab-status img { width: 13px; height: 13px; opacity: .65; }
.grab-status .task-countdown { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
/* 10-09 凌晨复查（A29）：快到点了人什么都不用做，不用红色，加粗、用强调色 */
.grab-status .task-countdown.soon { color: var(--accent-strong); font-weight: 700; }
.grab-status .link-action { color: var(--live); text-decoration: underline; }
/* ── 手机号列表 ────────────────────────────────────────────────────────── */
/* 这里**不能**用 overflow:hidden 来切圆角：⋯ 菜单是绝对定位的，会连它一起裁掉。
   行本身没有背景，需要自己切角的只有底部那条灰色页脚，单独给它圆角就行。 */
.list { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
/* 列表和表头共用同一套轨道，所以两者的分栏数永远一致。
   998px 是一栏的下限，正好让 2048 的屏排得下两栏（998×2 + 18 = 2014）。
   这个数不再按「最长那句上次结果不许换行」倒推——那样要 1180px，
   两栏得 2412px 的屏才排得出来，等于并排永远见不着。
   改成：栏窄了就让「上次结果」折到第二行（见 .task-status），一个字都不丢，
   只有那几行会高一点。并排和把话说全，这样两头都保住。
     <2048px 一栏（收到 1180px，那时一行装得下）· 2048px 起两栏。 */
/* minmax 的下限要套一层 min(…, 100%)：裸写 minmax(998px, 1fr) 时，容器比 998 窄
   轨道也不肯缩，整页被撑出横向滚动条（当时下限还是 1180，实测 1024 和 900 的窗口
   都会把页面撑到 1197px）。套上之后窄容器里轨道跟着缩到 100%，
   宽容器里才用 998 这个下限去决定排几栏。 */
.rows, .rows-head { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(998px, 100%), 1fr)); column-gap: 18px; }
.rows { align-items: start; }

.rows-head {
  position: sticky;
  top: 44px;
  z-index: 5;
  /* 三份表头里只有排得下的那几份留在第一行，其余折到第二行——
     固定高度 + overflow:hidden 把它们裁掉。栏数由 CSS 决定，JS 不用知道。 */
  height: 24px;
  grid-auto-rows: 24px;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
  background: var(--surface-subtle);
  color: var(--faint);
  font-size: 11px;
  white-space: nowrap;
}
.head-set {
  display: flex;
  align-items: center;
  gap: var(--c-gap);
  height: 24px;
  padding: 0 12px;
}
.head-set > .c-dot { flex: 0 0 var(--c-dot); }
.head-set > .c-phone { flex: 0 0 var(--c-phone); }
.head-set > .c-state { flex: 0 0 var(--c-state); }
.head-set > .c-life { flex: 0 0 var(--c-life); }
.head-set > .c-pact { flex: 0 0 var(--c-pact); }
.head-set > .c-date { flex: 0 0 var(--c-date); }
.head-set > .c-times { flex: 0 0 var(--c-times); }
.head-set > .c-last { flex: 1 1 0; min-width: 0; }
.head-set > .c-act { flex: 0 0 calc(var(--c-act) + 30px); }
/* 同一网络故障可能同时影响多个手机号。把共同原因集中说一次，
   每行只保留短状态，避免用户在多行里反复读同一段供应商报错。 */
.account-issue-summary {
  display: grid;
  gap: 4px;
  min-height: 30px;
  padding: 5px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: var(--t-meta);
  line-height: 1.45;
}
.account-issue-group { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.account-issue-group details { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.account-issue-group summary { cursor: pointer; }
.account-issue-group p { margin-top: 4px; white-space: pre-line; color: var(--muted); }
.account-issue-group .link-action { flex-shrink: 0; color: var(--accent-strong); }
/* 列表空了就别留一条没有内容的表头 */
.list:has(.empty-state) .rows-head { display: none; }
.list:has(.empty-state) .account-issue-summary { display: none; }
.list-foot { display: flex; justify-content: flex-end; padding: 2px 8px; border-top: 1px solid var(--line); background: var(--surface-subtle); border-radius: 0 0 calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px); }
/* 同理：正在删的时候页脚不能塌掉，否则按钮连同它站的那一行一起消失 */
.list-foot:has(> button:disabled:not(.is-busy)) { display: none; }
.empty-state { padding: 48px 20px; text-align: center; color: var(--muted); }
.empty-state p { font-size: var(--t-item); font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.empty-state small { font-size: var(--t-body); }

/* 号在左、任务在右，同一行。曾经是「号一行、任务另起一行」，一个号 65px；
   十个号看完要滚一屏半。现在单任务的号只占 30px。
   登录表单 / 建任务表单打开时才占满整行（见下面 :has 那两条）。 */
.phone-row { position: relative; display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0 var(--c-gap); padding: 2px 12px; }
.phone-row { border-top: 1px solid var(--line); }
/* 一眼看出「这两行是两个号」。
   原来两个号之间只有一条 #e5e8ec 的发丝线、上下各 2px 内边距，在白卡片上几乎看不见；
   而一个号名下可能有三四行任务，中间还夹着橙色提示——从哪儿到哪儿算同一个号，
   全靠自己数。隔行浅底是密集表格里最省地方的分组办法：一个像素的高度都不多占
   （用户要的是「能多紧就多紧」），而且号占几行都成立。
   两栏时按「视觉行」配色，让并排的左右两个同色，否则会排成棋盘格。 */
.rows > .phone-row:nth-child(even) { background: var(--row-alt); }
/* 排成两栏时换一套（断点和上面「收窄」那条是同一个数，中间不留缝）：
   1) 按「视觉行」配色，并排的左右两个同色，否则排成棋盘格；
   2) 中间加一道竖线：隔行浅底只切上下，切不开左右，18px 的槽在白底上读不出来，
      左右两个号看着像同一行里的十四个格子。线画在右栏左沿，表头要同样一条。 */
@media (min-width: 2048px) {
  .rows > .phone-row:nth-child(even) { background: transparent; }
  .rows > .phone-row:nth-child(4n+3),
  .rows > .phone-row:nth-child(4n+4) { background: var(--row-alt); }
  .rows > .phone-row:nth-child(even),
  .rows-head > .head-set:nth-child(even) {
    border-left: 1px solid var(--line-strong);
    padding-left: 18px;
  }
}
.rows { margin-top: -1px; }
/* 11-07 多选删除：见 public/modules/account-pick.js */
.pick-bar { position: sticky; top: 0; z-index: 3; display: flex; justify-content: flex-end; align-items: center; min-height: 26px; padding: 2px 8px; background: var(--surface-subtle); border-bottom: 1px solid var(--line); }
.pick-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; }
.pick-count { margin-right: auto; color: var(--ink); font-size: var(--t-meta); font-variant-numeric: tabular-nums; }
.pick-delete:not(:disabled) { border-color: var(--danger); color: var(--danger); }
.phone-pick { flex: 0 0 auto; width: 14px; height: 14px; margin: 0 -2px; accent-color: var(--danger); }
.rows > .phone-row.picked { background: var(--danger-soft); }
.phone-head { flex: 0 0 var(--head-w); display: flex; align-items: center; gap: var(--c-gap); flex-wrap: wrap; min-height: 26px; }

.phone-identity {
  flex: 0 0 calc(var(--c-dot) + var(--c-phone) + var(--c-gap));
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--c-gap);
  overflow: hidden;
  white-space: nowrap;
}
.account-dot { width: var(--c-dot); height: var(--c-dot); flex: 0 0 auto; border-radius: 50%; background: var(--faint); }
.account-dot.on { background: var(--ok); }
.account-dot.bad { background: var(--danger); }
.account-dot.busy { background: var(--live); box-shadow: 0 0 0 0 var(--live); animation: pulse 1.6s ease-out infinite; }
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(37, 99, 235, .45); } 100% { box-shadow: 0 0 0 7px rgba(37, 99, 235, 0); } }

/* 它现在是个 <span>，不再是可点的按钮，不需要那套按钮重置 */
.phone-name { flex: 0 0 auto; color: var(--ink); font-size: var(--t-item); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: left; }
.phone-identity .lead { color: var(--ink); }

.phone-brief { flex: 0 0 calc(var(--c-state) + var(--c-life) + var(--c-gap)); min-width: 0; display: flex; align-items: center; gap: var(--c-gap); overflow: hidden; white-space: nowrap; color: var(--muted); font-size: var(--t-body); }
.phone-state { flex: 0 0 var(--c-state); }
.phone-life { flex: 0 0 var(--c-life); }
.phone-brief > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.phone-state { font-weight: 500; }
.phone-state.danger { color: var(--danger); font-weight: 600; }
.phone-state.warning { color: var(--ink); }
/* 批量流程正在跑这个号：和任务的「监控中」同一个蓝，一眼看出是活的 */
.phone-state.live { color: var(--live); font-weight: 500; }
.phone-life { font-variant-numeric: tabular-nums; }
.phone-life.soon { color: var(--ink); font-weight: 600; }
.phone-life.critical { color: var(--danger); font-weight: 600; }
.phone-life.expired { color: var(--danger); font-weight: 600; }

/* 账号卡片的第二行：把接入出口和账号/额度校验放在手机号下面，
   不占一整列，也不把「状态」那一格塞成一串省略号。 */
.phone-meta {
  flex: 0 0 100%;
  order: 3;
  min-width: 0;
  margin: -1px 0 1px;
  padding-left: calc(var(--c-dot) + var(--c-gap));
  color: var(--faint);
  font-size: 10px;
  line-height: 1.35;
  white-space: normal;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.phone-task-summary { color: var(--muted); font-weight: 500; }
.phone-task-summary.empty { color: var(--warning); }

/* 只在出问题的手机号下留一小行说明。长原因自然折行，触屏也能直接读全。 */
.phone-note {
  flex: 0 0 100%;
  order: 4;
  min-width: 0;
  max-width: 100%;
  margin: -1px 0 1px;
  padding: 0 0 2px calc(var(--c-dot) + var(--c-gap));
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.phone-actions { margin-left: auto; flex: 0 0 var(--c-pact); display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
/* 行上的按钮和行同高，别把这一行撑高 */
/* 一列七八个「重新登录」摞起来是一堵墙。描边和字都压淡一档——
   要用的时候看得见，不用的时候退到背景里；鼠标移上去再恢复成正常对比。 */
.phone-actions > .button.compact { height: 24px; padding: 0 9px; border-color: var(--line); color: var(--muted); }
.phone-actions > .button.compact:hover:not(:disabled) { border-color: var(--line-strong); color: var(--ink); }

/* ⋯ 菜单：内容在打开时才生成（见 app.js fillPhoneMenu） */
.menu { position: relative; }
.phone-row > .phone-head .phone-more { position: absolute; right: 6px; top: 2px; }
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu[open] > summary { background: var(--surface-subtle); border-color: var(--line); color: var(--ink); }
.menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 15;
  min-width: 220px;
  max-height: min(60vh, 420px);
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: var(--shadow);
}
/* 下方空间不够时朝上弹（由 JS 量完加这个类），否则要滚半页才看得见最后一项 */
.menu-panel.up { top: auto; bottom: calc(100% + 4px); }
.menu-item { display: block; width: 100%; padding: 8px 10px; border: 0; border-radius: 6px; background: transparent; color: var(--ink); font-size: var(--t-body); text-align: left; white-space: nowrap; }
.menu-item:hover:not(:disabled) { background: var(--surface-subtle); }
.menu-item.danger { color: var(--danger); }
.menu-item.danger:hover:not(:disabled) { background: var(--danger-soft); }
.menu-item:disabled { color: var(--faint); }
.menu-sep { height: 1px; margin: 4px 2px; background: var(--line); }
.menu-field { display: grid; gap: 4px; padding: 6px 10px 8px; }
.menu-field > label { color: var(--muted); font-size: var(--t-meta); }
.menu-field > select, .menu-field > input[type="date"] { height: 30px; width: 100%; }
.menu-field.menu-check { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.menu-field.menu-check > input { margin: 0; }

/* 任务行：缩进到「状态」那一列，右边的按钮和手机号行对齐（留出 ⋯ 的宽度） */
.phone-tasks { flex: 1 1 0; min-width: 0; margin: 0; }
/* 只剩「改时间」这一个还是行内编辑器了：登录/绑本人搬进了 #setup-panel，
   建任务搬进了 #task-panel，`.sheet` 和 `.phone-editor` 都不会再出现在行里。 */
.phone-tasks:has(.task-editor) { flex-basis: 100%; }
/* 改时间一展开就占满整行宽度，挤在半栏里那几个字段会叠成一团 */
.rows > .phone-row:has(.task-editor) { grid-column: 1 / -1; }
.task-card { padding: 0; }
.task-card + .task-card { border-top: 1px dashed var(--line); }
/* 任务一行放完：日期 · 人 · 状态 · 按钮。人名和状态各自用省略号让位，按钮永远在这一行。
   窄屏才换行（见底下的媒体查询）。原来状态单独占一条副行，八个号就多出八行。 */
.task-head { display: flex; align-items: center; gap: var(--c-gap); flex-wrap: nowrap; min-height: 26px; }
.task-when { flex: 0 0 calc(var(--c-date) + var(--c-times) + var(--c-gap)); display: flex; align-items: baseline; gap: var(--c-gap); white-space: nowrap; }
.task-when strong { flex: 0 0 var(--c-date); min-width: 0; font-size: var(--t-item); font-weight: 600; font-variant-numeric: tabular-nums; }
.task-times { flex: 0 0 var(--c-times); min-width: 0; overflow: hidden; color: var(--muted); font-size: var(--t-body); font-variant-numeric: tabular-nums; text-overflow: ellipsis; }
.task-who { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font-size: var(--t-body); }
/* 挤不下就折行，不截断。这一列是任务唯一要看的东西，截掉只能靠鼠标悬停找回——
   「上次结果展示的不全」说的就是它。两栏时每栏只给它 176px，
   「上次需要处理 · 12:04 · 订单任务已提交，但没等到官网回执」要 322px，
   硬排一行必然被砍；折成两行则一个字不少，代价只是那一行高 17px。
   （试过「并排时把订单号省掉」：最长的两句「…但没等到官网回执」和
   「定时开抢启动失败（已重试 3 次）」压根没有订单号，省了也还是放不下。） */
.task-status { flex: 1 1 0; min-width: 3em; color: var(--muted); font-size: var(--t-meta); }
/* 任务状态一律灰字。正在跑由手机号圆点（蓝、在闪）说，成功不用绿字庆祝，只有真出错才红。 */
.task-status.running, .task-status.opening, .task-status.submitting, .task-status.captcha { color: var(--live); }
.task-status.success { color: var(--ink); }
.task-status.error { color: var(--danger); font-weight: 600; }
/* 10-08 晚：到点没准备好、后台正在补（程序在处理，不标红） */
.task-status.catchup { color: var(--warning); }
.task-actions { margin-left: auto; flex: 0 0 calc(var(--c-act) + 30px); display: flex; align-items: center; justify-content: flex-end; gap: 2px; padding-right: 30px; }
/* 这是一条**备注**不是警报：整块橙底橙字时，它比同一行的正文还抢眼，
   两行摞在一起整个列表就花了。改成左边一条竖线 + 常规字色——看得见，但不叫。 */
.task-uncertain-note { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; margin: 2px 0 3px; padding: 2px 0 2px 8px; border-left: 2px solid var(--warning); color: var(--muted); font-size: var(--t-meta); line-height: 1.4; }
.task-uncertain-note > span { min-width: 0; }
.task-uncertain-note > .link-action { flex: 0 0 auto; color: var(--muted); text-decoration: underline; }
.orphan-tasks:empty { display: none; }
.orphan-tasks { margin-top: 12px; padding: 4px 16px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }

/* ── 编辑器（弹窗里的登录 / 绑本人，行内的改时间）──────────────────── */
.phone-editor, .task-editor {
  margin: 4px 0 3px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--surface-subtle);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px 12px;
}
.task-editor { display: grid; gap: 12px; }
.inline-field { flex: 1 1 150px; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.inline-field > label { color: var(--muted); font-size: var(--t-meta); }
.inline-field.identity-pick { flex-basis: 100%; }
.phone-editor > .button { flex: 0 0 auto; }
.phone-hint { flex-basis: 100%; margin: 0; font-size: var(--t-meta); }
/* 10-05：没话说的时候不占地方（登录窗口没发码时不再写「用你自己的手机号收验证码」） */
.phone-hint:empty { display: none; }
.task-editor-field { display: grid; gap: 6px; }
.task-editor-field > span { color: var(--muted); font-size: var(--t-meta); font-weight: 600; }
.task-editor-field > select { width: min(260px, 100%); }
.task-editor-actions { display: flex; gap: 8px; }

/* ── 任务表单（在 #task-panel 弹窗里）─────────────────────────────────
   这几个 class 全项目只出现在这张表单里。以前是「一套基础值 + 一套
   #task-panel 前缀的覆盖」，基础那套一条都用不上，其中 .sheet-actions 的
   flex-end 还和现在「一左一右」的布局正好相反——靠 id 选择器压着才没出事。
   合成一套，不留第二种说法。 */
.sheet-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }
.field { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; min-width: 0; }
.field:first-child { margin-top: 6px; }
.field > label, .field-head { color: var(--muted); font-size: var(--t-meta); font-weight: 600; }
.field-head { display: flex; align-items: baseline; gap: 8px; }
.field-head small { font-weight: 400; color: var(--faint); }
.field-inline { display: flex; align-items: center; gap: 6px; }
.field-inline > select { flex: 1 1 auto; }
.sheet-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 12px; }
.sheet-actions > .button { flex: 0 0 auto; white-space: nowrap; }

/* 时段和同行人：同一种交互，同一套状态语言
   未选＝白底浅边　悬停＝边框加深　选中＝品牌色描边 + 浅底 */
.time-options, .pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
/* 高度由下面 `#pick-panel .pick-grid` 那条统一给（id 选择器权重更高，
   这里再写一个 240px 只会是个永远不生效的第二套说法）。 */
.pick-grid.scroll-cap { padding-right: 2px; }
.time-option { display: flex; align-items: center; gap: 8px; min-height: 36px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); cursor: pointer; font-size: var(--t-body); }
.time-option:hover { border-color: var(--line-strong); }
.time-option > span { font-variant-numeric: tabular-nums; }
.time-option > small { margin-left: auto; color: var(--faint); font-size: 11px; white-space: nowrap; }
.time-option > small.open { color: var(--muted); }
.time-option.unlisted { color: var(--faint); border-style: dashed; }
.time-option:has(input:checked), .time-option.on { border-color: var(--accent); background: var(--accent-soft); color: var(--ink); }
.time-option.unlisted:has(input:checked), .time-option.unlisted.on { border-color: var(--accent); border-style: solid; background: var(--accent-soft); color: var(--ink); }

.pick-option { display: flex; align-items: center; gap: 8px; min-height: 36px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); cursor: pointer; font-size: var(--t-body); min-width: 0; }
.pick-option:hover { border-color: var(--line-strong); }
.pick-option > span { font-weight: 500; white-space: nowrap; }
.pick-option > small { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--faint); font-size: 11px; font-variant-numeric: tabular-nums; }
.pick-option > em { flex: 0 0 auto; font-style: normal; color: var(--warning); font-size: 11px; }
.pick-option > em.blocked { color: var(--danger); }
.pick-option:has(input:checked), .pick-option.on { border-color: var(--accent); background: var(--accent-soft); }
.pick-option.disabled { opacity: .55; cursor: not-allowed; }
.pick-grid .group-heading { grid-column: 1 / -1; }

.group-heading { display: flex; align-items: baseline; gap: 8px; margin: 0; color: var(--muted); font-size: var(--t-meta); font-weight: 600; }
.group-heading small { font-weight: 400; color: var(--faint); }

.import-form { display: grid; gap: 8px; padding: 12px; border-radius: 10px; background: var(--surface-subtle); }
.import-head { display: flex; align-items: center; justify-content: space-between; color: var(--muted); font-size: var(--t-meta); font-weight: 600; }
.import-preview:empty { display: none; }
.import-preview p { margin: 0; font-size: var(--t-meta); }
.import-ok { color: var(--ok); }
.import-skip { color: var(--muted); }
.import-bad { color: var(--danger); }
.import-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px; }
/* 说明必须独占一行；清空、只导入、建任务三个动作各占一列，
   不依赖弹窗宽度，也不会在手机宽度下把文字挤成一字一行或把按钮裁掉。 */
.import-actions .hint { grid-column: 1 / -1; min-width: 0; margin: 0; line-height: 1.45; overflow-wrap: anywhere; }
.import-actions > .button { min-width: 0; white-space: nowrap; }
.import-actions > .button.primary { grid-column: 3; width: 100%; margin-left: 0; }
.import-actions > label { color: var(--muted); font-size: var(--t-meta); }
.import-actions > select { height: 30px; min-width: 0; }

/* ── 设置抽屉 ──────────────────────────────────────────────────────────── */
.drawer > summary { cursor: pointer; }
.drawer[open]::before { content: ""; position: fixed; inset: 0; z-index: 30; background: rgba(16, 24, 40, .35); }
.drawer-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 31;
  width: min(480px, 100%);
  overflow-y: auto;
  padding: 0 22px 32px;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.drawer-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; padding: 16px 0 12px; background: var(--surface); }
.drawer-head h2 { font-size: var(--t-title); font-weight: 600; }
.settings-section { padding: 16px 0; border-top: 1px solid var(--line); display: grid; gap: 10px; }
.settings-section:first-of-type { border-top: 0; }
.settings-section-muted { padding-top: 12px; padding-bottom: 12px; }
.settings-section-muted .settings-heading h3 { color: var(--muted); font-size: var(--t-body); font-weight: 500; }
.settings-section-muted .settings-heading p,
.settings-section-muted .settings-note { color: var(--faint); }
.settings-section-muted .settings-status { padding: 0; background: transparent; color: var(--faint); }
.settings-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.settings-heading h3 { font-size: var(--t-item); font-weight: 600; }
.settings-heading p { margin-top: 2px; color: var(--muted); font-size: var(--t-meta); }
.settings-status { flex: 0 0 auto; padding: 2px 8px; border-radius: 999px; background: var(--surface-subtle); color: var(--muted); font-size: 11px; white-space: nowrap; }
/* 不常用的设置统一收进一个「其它设置」（09-26 用户定：只有接码、代理、打码常开），里面不再分层折叠 */
.settings-more { border-top: 1px solid var(--line); }
.settings-more-head { display: flex; align-items: center; gap: 10px; padding: 14px 0; cursor: pointer; list-style: none; font-size: var(--t-item); font-weight: 600; }
.settings-more-head::-webkit-details-marker { display: none; }
.settings-more-summary { margin-left: auto; color: var(--muted); font-size: var(--t-meta); font-weight: 400; }
.settings-more-head::after { content: ""; flex: 0 0 auto; width: 7px; height: 7px; border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted); transform: rotate(-45deg); transition: transform .12s; }
.settings-more[open] > .settings-more-head::after { transform: rotate(45deg); }
.settings-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.settings-row > select { min-width: 110px; }
.settings-note { margin: 0; color: var(--muted); font-size: var(--t-meta); line-height: 1.5; }
/* 10-02「查有没有票」：查出有票、有人没问到、没查完时那一行用提醒色 */
.settings-note[data-tone="warn"] { color: var(--warning); }
.settings-subhead { display: flex; align-items: baseline; gap: 8px; font-size: var(--t-meta); }
.settings-subhead small { color: var(--faint); }
.check { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-body); cursor: pointer; }
.unit-field { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-body); }
.unit-field > input { width: 72px; }
.unit-field > small { color: var(--muted); }
.sub-details > summary { display: flex; align-items: baseline; gap: 8px; cursor: pointer; font-size: var(--t-body); font-weight: 500; list-style: none; }
.sub-details > summary::-webkit-details-marker { display: none; }
.sub-details > summary::before { content: ""; width: 6px; height: 6px; margin-right: 2px; border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted); transform: rotate(-45deg); transition: transform .12s; align-self: center; }
.sub-details[open] > summary::before { transform: rotate(45deg); }
.sub-details > summary small { color: var(--muted); font-weight: 400; }
.sub-details > *:not(summary) { margin-top: 10px; }
.provider-code-list { display: grid; gap: 6px; }
.provider-code-item { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--radius); font-size: var(--t-body); }
.provider-code-item > span { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.provider-code-item small { color: var(--faint); font-size: 11px; }
.provider-code-item em { color: var(--muted); font-style: normal; font-size: 11px; }
.provider-code-empty { margin: 0; }
.people-list { display: grid; }
/* 同上：高度由 `.people-card .people-list` 给，那条更靠后、同权重，一定赢。 */
.person-row { display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 4px 0; border-top: 1px solid var(--line); font-size: var(--t-body); }
/* 姓名永远不许从中间断开——宁可让后面的证件号省略号，也不能出现「赵志/明」。 */
.person-row strong { flex: 0 0 auto; font-weight: 500; white-space: nowrap; }
.person-meta { min-width: 0; overflow: hidden; color: var(--faint); font-size: 11px; font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; }
.person-spacer { flex: 1 1 auto; }
.person-owner { padding: 1px 6px; border-radius: 999px; background: var(--surface-subtle); color: var(--muted); font-size: 11px; }
.person-task-times { display: flex; gap: 4px; flex-wrap: wrap; }
.person-task-time { padding: 1px 6px; border-radius: 999px; background: var(--surface-subtle); color: var(--muted); font-size: 11px; white-space: nowrap; }
.remove-person { margin-left: auto; width: 26px; height: 26px; }
.remove-person img { width: 14px; height: 14px; }
.people-list .group-heading { margin: 6px 0; }

/* ── 弹层（验证码 / 官方订单）──────────────────────────────────────────── */
.modal:not([hidden]) { position: fixed; inset: 0; z-index: 40; display: grid; place-items: center; padding: 20px; background: rgba(16, 24, 40, .45); }
.modal-card { width: min(600px, 100%); max-height: calc(100vh - 40px); overflow: auto; padding: 18px 20px 20px; border-radius: 14px; background: var(--surface); box-shadow: var(--shadow); }
.modal-card.wide { width: min(760px, 100%); }
.modal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.modal-head h2 { font-size: var(--t-title); font-weight: 600; white-space: nowrap; flex-shrink: 0; }
/* 旁边的说明再长也只能自己换行，不能把标题挤成「新建任 / 务」两行 */
.modal-head small { color: var(--muted); min-width: 0; }
.modal-head .icon-button:first-of-type { margin-left: auto; }
.modal-subtitle { margin: -4px 0 2px; color: var(--muted); font-size: var(--t-meta); line-height: 1.5; }

/* 确认框：只有一句话和两个按钮，比弹窗窄 */
.confirm-card { width: min(420px, 100%); padding: 22px 24px 20px; }
.confirm-card h2 { margin-bottom: 8px; font-size: var(--t-item); font-weight: 600; line-height: 1.5; }
.confirm-detail { color: var(--muted); font-size: var(--t-body); line-height: 1.6; white-space: pre-line; }
.confirm-actions { margin-top: 20px; display: flex; justify-content: flex-end; gap: 8px; }
/* 确认框里那个按钮就是「按下去东西就没了」，必须是实心红，不能和「取消」长得一样 */
.button.danger-solid { border-color: var(--danger); background: var(--danger); color: #fff; }
.button.danger-solid:hover:not(:disabled) { filter: brightness(.92); }

/* 「添加手机号」和「绑定本人」两个框：各只装一件事，外壳压到最薄。 */
/* 里面就三四个字段。720px 时它们横成一排、每个 192px，两侧全是空白。
   窄下来之后 .phone-editor 的 flex-wrap 会自己把它们排成一列，不用另写布局。 */
/* 和联系人 / 挑同行人同宽：360 和 340 几乎一样宽，摆在一起只会显得没对齐。
   全站三档就够——300 紧凑（批量取号）、340 常规、440 宽（新建任务要排时段）。 */
.setup-card { width: min(340px, 100%); padding: 14px 16px 12px; }
.setup-body { display: grid; gap: 12px; }
/* 弹窗里的编辑器不用再画一层灰底卡片——外面已经是卡片了 */
.setup-body > .phone-editor { margin: 0; padding: 0; background: transparent; gap: 10px 12px; }

/* 批量取号的表单是上下排列的：四个字段横着挤成一行，每个都只剩一百多像素，
   「飞衡代理」「1 小时」这种值一眼看不全，而这几项是要先想清楚再填的，不是流水填空。 */
#batch-form { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: 8px 10px; }
#batch-form > .inline-field { display: contents; }
#batch-form > .inline-field > label { justify-self: start; color: var(--muted); font-size: var(--t-meta); white-space: nowrap; }
#batch-form > .modal-subtitle { grid-column: 1 / -1; }
/* 控件占满剩下的宽度，不再钉死 260px——钉死的那一版逼着卡片必须比它宽 */
#batch-form > .inline-field > input,
#batch-form > .inline-field > select,
#batch-form > .inline-field > textarea { width: 100%; min-width: 0; }
#batch-form > .inline-field > textarea { resize: vertical; font-variant-numeric: tabular-nums; }
/* 手机号那一栏是多行框，标签跟着居中会掉到框的正中间，看着不像是它的标签 */
#batch-phones-field > label { align-self: start; }
#batch-phones-field > .field-feedback { grid-column: 1 / -1; margin: 0; }
.field-feedback { color: var(--muted); font-size: 11px; line-height: 1.45; }
.field-feedback.is-error { color: var(--danger); }
/* 10-05：没话说的时候不占地方（号码框下面平时不再教格式） */
.field-feedback:empty { display: none; }
/* 提示和按钮跨满两列，按钮靠左跟字段对齐 */
#batch-form > .phone-hint { grid-column: 1 / -1; margin: 2px 0 0; }
#batch-form > .button { grid-column: 1 / -1; justify-self: stretch; min-height: 38px; }
/* 这张卡只有三四行字段和一张窄表。
   一路收到这里：720（进度表撑的）→ 500（长文案改短版）→ 300（标签列按最长标签自适应、
   控件从钉死 260 改成占满、进度行去掉序号列、等填码时验证码框直接顶掉那句提示）。
   再窄就装不下「13800138000 + 验证码框 + 确定」这一行了。
   09-26 用户要「能多窄就多窄」：从 440 收回到这个下限 300，边距 20→16。 */
#batch-panel .setup-card { width: min(300px, 100%); padding: 16px; }
/* 使用说明（09-26）：一页讲清怎么用，文字为主 */
.guide-card { width: min(400px, 100%); }
.guide-body { display: grid; gap: 8px; font-size: var(--t-body); line-height: 1.65; }
.guide-body h3 { margin-top: 10px; font-size: var(--t-item); font-weight: 600; }
.guide-body ul, .guide-body ol { margin: 0; padding-left: 20px; display: grid; gap: 4px; }
.guide-lead { color: var(--ink); }
.guide-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.guide-compare > div { padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-subtle); }
.guide-compare p { margin-top: 4px; color: var(--muted); font-size: var(--t-meta); }
.guide-rules { color: var(--muted); font-size: var(--t-meta); }
/* 「添加手机号」那段要醒目（09-26 用户要强调） */
.guide-callout { display: grid; gap: 6px; margin-top: 6px; padding: 12px 14px; border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); border-radius: 10px; background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.guide-callout h3 { margin-top: 0; color: var(--accent); }
@media (max-width: 560px) { .guide-compare { grid-template-columns: 1fr; } }
#batch-form > #batch-phones-field { display: grid; grid-column: 1 / -1; gap: 7px; }
#batch-phones { min-height: 60px; max-height: 160px; }
#batch-hint:empty { display: none; }
.batch-advanced { grid-column: 1 / -1; width: 100%; color: var(--muted); font-size: var(--t-meta); }
.batch-advanced > summary { cursor: pointer; padding: 8px 0; }
.batch-advanced > summary small { margin-left: 5px; color: var(--faint); }
.batch-advanced-body { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 4px 0 10px; }
.batch-advanced .inline-field { margin-bottom: 8px; }
.batch-advanced select { width: 100%; }
.manual-sms-editor { display: grid; gap: 12px; }
.manual-sms-editor input { width: 100%; height: 40px; }
.manual-sms-editor .field-feedback { margin: 0; }
.sms-help-actions { display: flex; gap: 20px; align-items: center; }
.sms-help-actions > button { min-height: 32px; }

/* ── 批量取号的进度表 ──────────────────────────────────────────────────── */
/* 10-03（用户：「取手机号弹窗…能在表格展示的就在表格展示」）：备用号在号码后面一个灰色小字标记 */
.phone-spare { flex: 0 0 auto; margin-left: 6px; padding: 0 5px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); font-size: 11px; line-height: 16px; }
/* 10-03（用户：「检查下手动添加手机号弹窗的ui，如果号码多了会不会变形」）：号一多、超出高度时，表格排版会把每行压到最小高度（24px），
   等填验证码那一行（约 100px）就盖住了下面几行；最长那行进度文字还把整列撑到 700 多 px，「验证并登录」被挤到看不见的右边、
   长文字也不出「…」。现在每行按自己的高度排（max-content），列宽限在框里（minmax(0, 1fr)） */
.batch-progress { margin-top: 12px; display: grid; grid-template-columns: minmax(0, 1fr); grid-auto-rows: max-content; gap: 2px;
  max-height: min(44vh, 380px); overflow: auto; }
.batch-summary { position: sticky; top: 0; z-index: 1; padding: 4px 0 6px; background: var(--surface);
  color: var(--muted); font-size: var(--t-meta); font-variant-numeric: tabular-nums; }
.batch-summary.needs-input { color: var(--ink); font-weight: 600; }
.batch-row { display: flex; align-items: center; gap: 6px; min-height: 24px; padding: 1px 0;
  border-top: 1px solid var(--line); font-size: var(--t-body); }
.batch-who { flex: 0 0 88px; font-size: var(--t-meta); font-variant-numeric: tabular-nums; }
/* 进度文案长短差得很远（「排队中」到「③ 正在等验证码短信，第 7 次查询…还剩约 96 秒。」），
   让它自己占满剩下的宽度并省略，别把前面的号码挤走。 */
.batch-stage { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--muted); font-size: var(--t-meta); }
.batch-row.is-running .account-dot { background: var(--live); box-shadow: 0 0 0 0 var(--live); animation: pulse 1.6s ease-out infinite; }
.batch-row.is-running .batch-stage { color: var(--ink); }
.batch-row.is-done .account-dot { background: var(--ok); }
.batch-row.is-failed .account-dot { background: var(--danger); }

/* 任务「需要处理」（status=attention，比如号掉线了）原来在卡片上一点区分都没有，
   和正常等待中的卡片长得一模一样——放票当天这是最不能混的一种状态。
   只加一条左边线，不铺底色：颜色已经够多了。 */
.task-card.paused { border-left: 2px solid var(--warning); padding-left: 6px; }
.batch-row.is-stopped .account-dot { background: var(--warning); }
/* 手动模式下这一行要人把验证码抄进来，框跟在进度文案后面、贴右边 */
.batch-code { flex: 1 0 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; padding: 8px 0; }
.batch-code > input { width: 100%; height: 40px; padding: 8px 10px; font-size: var(--t-body); font-variant-numeric: tabular-nums; }
.batch-code > .button { height: 40px; }
.batch-code > .field-feedback, .batch-code > .sms-help-actions { grid-column: 1 / -1; }
.batch-row:has(.batch-code) { flex-wrap: wrap; padding-top: 10px; }
.batch-row:has(.batch-code) .batch-who { flex: 0 0 auto; font-size: var(--t-body); }
.batch-row:has(.batch-code) .batch-stage { flex: 1 1 auto; }
.batch-row.is-failed .batch-stage { color: var(--danger); white-space: normal; }
/* 「从预约人选择」在行内编辑器里独占一整行（那儿窄）。弹窗有 720px，四个字段一排放得下。 */
.setup-body .inline-field.identity-pick { flex-basis: 150px; }
/* 原来这里 line-clamp:2 + overflow:hidden。说明文字超过两行就被**无声**截掉——
   实测「已取号…」那条要三行，第三句「选飞衡后会为这个号单独提取一个出口」
   在界面上压根不存在，而且没有省略号之外的任何提示。
   宁可弹窗高一点，也不能让解释性文字凭空少半句。文案本身已经改短了。 */
.setup-body .phone-hint { font-size: 11px; line-height: 1.45; }
.setup-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
/* 弹窗关掉之后那一行八成换了位置（状态变了要重新排序）。闪一下告诉人「就是它」。 */
.phone-row.just-done { animation: just-done 1.6s ease-out; }
@keyframes just-done {
  0%, 55% { background: var(--accent-soft); }
  100% { background: transparent; }
}

/* 联系人弹窗。
   它在 DOM 里排在配置弹窗前面，同为 z-index:40 时会被后者盖住——
   而「建任务时发现名单里少个人」正是从配置弹窗里点进来的，盖住就等于点了没反应。 */
/* 层叠一律写明确，别靠 DOM 顺序。谁能盖住谁是有实际含义的：
   挑同行人从新建任务里打开、联系人又从挑同行人里打开，一层压一层；
   订单和配置各自独立；验证码永远在最上（它没有关闭键，码必须做完）。
   全都留成默认的 40 的话，改一下 index.html 里 section 的先后，
   盖的关系就反了，而且不会有任何报错。
   app.js 里 MODAL_ESC_ORDER 的顺序必须和这里从高到低一致。 */
/* 10-02：「取手机号」「分配」两个弹窗从顶栏打开，压在别的弹窗下面（它们自己不再开别的弹窗，确认框 60 照样在上面） */
#prep-panel:not([hidden]) { z-index: 38; }
#prep-login-panel:not([hidden]) { z-index: 39; }
#task-panel:not([hidden]) { z-index: 40; }
#setup-panel:not([hidden]) { z-index: 41; }
#batch-panel:not([hidden]) { z-index: 42; }
#orders-panel:not([hidden]) { z-index: 43; }
#pick-panel:not([hidden]) { z-index: 44; }
#people-panel:not([hidden]) { z-index: 45; }
/* 导入名单从顶栏打开，独立一层；验证码（50）和确认框（60）仍盖在它上面 */
#roster-import-panel:not([hidden]) { z-index: 47; }
/* 10-22：使用说明从顶栏「?」打开，Esc 最先关它（MODAL_ESC_ORDER 第一个），层叠也得在最上（原来没写，靠 DOM 顺序） */
#guide-panel:not([hidden]) { z-index: 48; }
/* 验证码必须盖在所有弹窗之上。它没有关闭键是**故意的**——码必须做完，
   而 #people-panel 那条 41 是为了压过配置弹窗写的，顺手也压过了它：
   任务在跑时打开「联系人」，正好撞上点选验证码，面板就藏在联系人底下，
   屏幕上只有一层遮罩，人工接管的那 60 秒就这么过去了。 */
#captcha-panel:not([hidden]) { z-index: 50; }
/* 确认框会从订单、读表格并分配等弹窗内打开，必须盖住当前所有层。 */
.modal.confirm-modal:not([hidden]) { z-index: 60; }
/* 实测一行真正占地只有 242px（姓名 39 + 证件 59 + 时段 118 + 删除 26），
   760px 里有 447px 是纯空白。 */
.people-card { width: min(340px, 100%); display: grid; gap: 8px; }

#pick-panel .people-card { gap: 10px; }
#pick-panel .pick-grid { overflow: auto; }
.pick-add { display: flex; gap: 6px; align-items: flex-start; margin-top: 6px; }
/* 输入框占满剩下的宽度，但别把「添加并选上」挤断 */
.pick-add > input, .pick-add > textarea { flex: 1 1 auto; min-width: 0; }
/* 两行起步，粘多了自己长高；高度跟着内容走比一个死高度好——
   粘四个人进来还要在一个两行的框里滚动，是自找的麻烦。 */
.pick-add > textarea { min-height: 52px; max-height: 120px; resize: vertical; line-height: 1.5; }
.pick-add > .button { flex: 0 0 auto; white-space: nowrap; align-self: stretch; }
#pick-panel .list-foot { display: flex; justify-content: flex-end; }

/* ── 新建任务（弹窗） ─────────────────────────────────────────────────── */
/* 五个时段横排要宽度，所以比别的弹窗宽一点，但也就这些。 */
.task-card-panel { width: min(440px, 100%); padding: 14px 16px 12px; }
/* 时段一行两个：一行五个挤成竖排文字，一行一个又太占高度 */
#task-panel .time-options { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
#task-panel .time-option { min-width: 0; }
#pick-panel .list-foot > .link-action { flex: 0 0 auto; white-space: nowrap; }

/* 表单上只留「已选谁」加一个按钮 */
.pick-summary-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.pick-summary { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--t-body); }
.pick-summary.is-empty { color: var(--muted); }
.people-card .people-list { overflow: auto; }
/* 弹窗里只许有一条滚动条。
   原来是「卡片自己 overflow:auto」套着「列表再 max-height: 46vh」：内容一多两条
   同时出现，滚轮滚哪一条全看鼠标停在哪。联系人面板实测就是这样——1865px 的名单
   塞进 350px 的框，外面那层还能再滚 100 多像素。
   改成一根纵向 flex 柱子：头、导入框、搜索、页脚都固定，只有中间那块列表伸缩并
   滚动，卡片本身永远不滚。列表能拿到多少高度由弹窗剩下多少决定，不用再猜 vh。 */
.people-card { display: flex; flex-direction: column; }
.people-card > * { flex: 0 0 auto; }
/* 0 1：名单短的时候只占它自己需要的高度（卡片跟着缩，不留一大片空白），
   长了才被压缩并滚动。写成 1 1 会让十个人的名单也把卡片撑到满屏高。 */
.people-card > .people-list,
.people-card > .pick-grid { flex: 0 1 auto; min-height: 96px; }
/* 联系人名单为空时不应强行占 96px：窄窗口里这会把底部操作栏挤出卡片，
   用户只能看到按钮的一截。名单有内容时由行高自然撑开，过长再由列表滚动。 */
.people-card > .people-list { min-height: 0; }
/* 名单底下那一行：「清空名单」（10-22 原来藏着的「一键配号」删了） */
.people-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.chip-row { display: flex; flex-wrap: nowrap; gap: 6px; overflow-x: auto; margin-bottom: 10px; scrollbar-width: none; }
.chip-row::-webkit-scrollbar { display: none; }
.log-chip { flex: 0 0 auto; height: 24px; padding: 0 10px; border: 1px solid var(--line-strong); border-radius: 12px; background: var(--surface); color: var(--muted); font-size: var(--t-meta); white-space: nowrap; }
.log-chip.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.captcha-layout { display: grid; grid-template-columns: minmax(280px, 420px) minmax(150px, 1fr); gap: 16px; align-items: center; }
.captcha-stage { position: relative; width: 100%; max-width: 420px; aspect-ratio: 2 / 1; min-height: 155px; overflow: hidden; border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--surface-subtle); cursor: crosshair; }
.captcha-stage.slider-mode { cursor: default; }
.captcha-stage > img { width: 100%; height: 100%; object-fit: fill; display: block; user-select: none; -webkit-user-drag: none; }
/* 舞台 2/1、背景图 310×155 也是 2/1：按高度铺满不会变形，块停在哪就是提交到哪 */
/* 选择器要压过上面那条 .captcha-stage > img，否则点选模式下这块空 src 的图会以「裂图」形式压在左上角 */
.captcha-stage > .captcha-piece { position: absolute; top: 0; left: 0; height: 100%; width: auto; display: none; pointer-events: none; filter: drop-shadow(0 0 2px rgba(0, 0, 0, .45)); transition: left .06s linear; }
.captcha-stage.slider-mode > .captcha-piece.on { display: block; }
.marker { position: absolute; width: 22px; height: 22px; transform: translate(-50%, -50%); display: grid; place-items: center; border: 2px solid #fff; border-radius: 50%; background: var(--accent); color: #fff; font-size: 11px; font-weight: 600; pointer-events: none; box-shadow: 0 1px 4px rgba(0, 0, 0, .35); }
.captcha-instruction > span { color: var(--muted); font-size: var(--t-body); }
.captcha-instruction p { margin: 0; color: var(--muted); font-size: var(--t-body); }
.word-list { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
.word { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--surface); font-size: 17px; font-weight: 600; }
.word.done { border-color: var(--accent); background: var(--accent); color: #fff; }
.captcha-slider { width: 100%; height: 38px; margin: 10px 0; accent-color: var(--accent); cursor: ew-resize; touch-action: none; }
.captcha-slider:disabled { cursor: wait; }

.orders-body { display: grid; gap: 12px; }
.orders-list { display: grid; gap: 12px; }
.order-card { padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 20px; }
.order-card > .button, .order-card > .ticket-details { grid-column: 1 / -1; }
.order-field { display: flex; justify-content: space-between; gap: 12px; font-size: var(--t-body); }
.order-field > span { color: var(--muted); }
.order-field > strong { font-weight: 500; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.ticket-details { display: grid; gap: 10px; }
.ticket-pending-note { margin: 0; }
.ticket-qrs {
  /* 保持每张码至少 132px，五张码优先同排；较窄的弹窗自然换行。 */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 6px;
}
.ticket-qr { margin: 0; min-width: 0; display: grid; align-content: start; gap: 4px; text-align: center; font-size: var(--t-meta); }
.ticket-qr figcaption { overflow-wrap: anywhere; }
.ticket-qr img { width: 100%; aspect-ratio: 1; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; }
.ticket-no-qr { display: grid; place-items: center; aspect-ratio: 1; border: 1px dashed var(--line-strong); border-radius: var(--radius); color: var(--faint); }
.ticket-status { color: var(--muted); }
.ticket-refund-choice { display: inline-flex; align-items: center; gap: 4px; justify-content: center; }
.ticket-refund-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* ── 提示 ──────────────────────────────────────────────────────────────── */
.toast {
  position: fixed;
  left: 50%;
  bottom: 38px;
  z-index: 50;
  transform: translateX(-50%);
  max-width: min(420px, calc(100vw - 32px));
  padding: 10px 16px;
  border-radius: 10px;
  background: var(--dark);
  color: var(--canvas);
  font-size: var(--t-body);
  box-shadow: var(--shadow);
}
.toast.error { background: var(--danger); color: #fff; }

/* ── 窄屏 ──────────────────────────────────────────────────────────────── */
/* 10-05（界面复查）：721～1000 像素宽时顶栏按钮不换行，右边的按钮被挤出屏幕、左边「N 个待处理」被压得看不见：这一段也让它折行 */
@media (max-width: 1000px) {
  .topbar { flex-wrap: wrap; }
  .topbar-actions { flex-wrap: wrap; }
}
@media (max-width: 720px) {
  .app { width: min(100% - 24px, 1120px); padding-top: 12px; }
  .topbar-actions { width: 100%; }
  .phone-head { flex: 0 0 100%; padding-right: 30px; }
  .phone-tasks { flex-basis: 100%; }
  .phone-identity { flex: 1 1 auto; }
  .phone-brief { order: 2; flex-basis: 100%; }
  .phone-meta { order: 3; padding-left: 0; }
  .phone-note { order: 4; padding-left: 0; }
  /* 手机上表头会折成三行、181px 高。吸顶的话等于永久吃掉 22% 的屏幕——
     手机竖屏本来就缺的是高度，不是回滚一下的力气。这里退回普通滚动。 */
  .rows-head { display: none; }
  .topbar { position: static; flex-wrap: wrap; }
  .topbar-actions { flex-wrap: wrap; }
  /* 手机上一行塞不下告警和倒计时，让状态条自己折成两行 */
  .statusbar { flex-wrap: wrap; gap: 2px 10px; }
  .grab-status { margin-left: 0; flex-basis: 100%; }
  .task-card { margin-left: 0; }
  .task-head { flex-wrap: wrap; }
  /* 「5 人」只有两三个字，跟日期同一行就行；只有状态那句长的才换行。
     （这条换行规则是「同行人列成一串名字」时代留下的，名字去掉后就多余了。） */
  .task-status { order: 6; flex: 0 0 100%; }
  .task-actions { padding-right: 0; }
  .captcha-layout { grid-template-columns: 1fr; }
  .captcha-stage { min-height: 0; }
  .drawer-panel { width: 100%; }
  .ticket-qrs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .order-card { grid-template-columns: minmax(0, 1fr); }
  .button, .icon-button, input, select { min-height: 36px; }
  .button.compact { height: 32px; }
  #batch-panel .setup-card { width: min(100%, 440px); padding: 18px 16px 16px; }
  .batch-advanced-body { grid-template-columns: 1fr; gap: 4px; }
  /* 抽屉里的折叠标题在手机上是要用手指点的，19px 太窄 */
  .sub-details > summary { min-height: 36px; align-items: center; }
}

/* ── 导入名单（09-28）────────────────────────────────────────────────── */
/* 用户要「能多窄就多窄」：和添加手机号一样取最窄的一档 300 */
.roster-import-card { width: min(300px, 100%); display: grid; gap: 8px; }
.roster-import-card textarea { min-height: 112px; font-size: var(--t-body); }
.roster-import-preview:empty { display: none; }
.roster-import-preview { display: grid; gap: 2px; padding: 8px 10px; border-radius: 8px; background: var(--surface-subtle); }
.roster-import-preview p { margin: 0; font-size: var(--t-meta); line-height: 1.5; }
.roster-import-preview .lead { font-weight: 600; font-size: var(--t-body); }
.roster-import-preview .warn { color: var(--warning); }
.roster-import-preview .done { color: var(--ok); font-weight: 600; font-size: var(--t-body); }
.roster-import-pick { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.roster-import-pick label { color: var(--muted); font-size: var(--t-meta); white-space: nowrap; }
.roster-import-pick select { height: 28px; min-width: 0; flex: 1; max-width: 170px; }
.roster-import-foot { display: flex; justify-content: flex-end; }

/* 10-01 新流程「开抢准备」 */
.prep-alert { margin: 0 0 8px; padding: 8px 12px; border-radius: 8px; background: var(--danger-soft); color: var(--danger); font-weight: 600; cursor: pointer; }
/* 10-08 晚：程序在重登、等它登上——只是说一声，不红、不能点 */
.prep-alert[data-kind="wait"] { background: var(--warning-soft); color: var(--warning); cursor: default; }
/* 10-10：上一轮分配没分完、只是说一声的（还没到取号的时候、明天再分）：橙色 */
.prep-alert[data-kind="view-calm"] { background: var(--warning-soft); color: var(--warning); }
/* 结果没写进表格、程序每 5 分钟自己再写：不标红（点了照样马上重写） */
.prep-alert[data-kind="auto"] { background: var(--warning-soft); color: var(--warning); }
.prep-body { display: flex; flex-direction: column; gap: 8px; }
.prep-step { margin: 10px 0 0; font-size: 14px; }
.prep-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.prep-row input[type="number"] { width: 84px; }
.prep-field { color: var(--muted, #667085); font-size: 13px; }
.prep-summary { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
.prep-headline { margin: 0; font-weight: 600; }
/* 10-04（复查第 26 条）：读表格没成那一行（表格要撤掉一大批任务等你确认、读不了）用红字 */
.prep-headline.is-error { color: var(--danger); }
.prep-dates { margin: 0; }
.prep-issues, .prep-failures { margin: 0; padding-left: 18px; font-size: 13px; }
.prep-issues li.is-warn { color: var(--warning); }
/* 10-11：代理链接额度用完、天启余额不够（见 public/modules/prep.js DANGER_ISSUE_KINDS） */
.prep-issues li.is-danger { color: var(--danger); }
/* 10-10：天启余额低于提醒线、没查到（设置页代理那一块，见 public/modules/settings.js proxyBalanceLine） */
.settings-note.is-warn { color: var(--danger); }
/* 10-09 凌晨复查（B18）：只是说一声的（程序在用原号码自动重登）灰字 */
.prep-issues li.is-info { color: var(--muted); }
.prep-status { margin: 0; font-size: 13px; }
.prep-failures { color: var(--danger); max-height: 180px; overflow: auto; }
/* 10-04（复查第 25 条）：只是说一声的（本人自己的号用不了、这次跟别人一单当同行人——单已经建好了）不用红字 */
.prep-failures li.is-info { color: var(--muted); }
.prep-run-alert { margin: 0; font-size: 13px; color: var(--danger); font-weight: 600; }
/* 10-09 凌晨复查（B4）：程序自己在处理的那几句（会自己接着取）只说一声，不用红字 */
.prep-run-alert.is-calm { color: var(--warning); font-weight: 500; }
/* 10-04「同步模式」窗口（10-04 晚：只在表格里有要改的时候打开，只列要改的几处）；任务卡片日期下面的模式名见 .task-mode */
#prep-modes-panel .setup-card { width: min(440px, 100%); }   /* 同「分配」窗口：340 宽时要改的那几处一条折两三行 */
.prep-modes-problems-head { margin: 0; color: var(--warning); font-size: var(--t-body); font-weight: 600; }
.prep-modes-issues { max-height: 240px; overflow: auto; color: var(--ink); line-height: 1.55; }
.prep-modes-issues li::marker { color: var(--warning); }
/* 「使用说明」第 4 步：几个模式各是什么意思（服务端给的说明） */
.guide-modes { color: var(--muted); }
.guide-modes b { color: var(--ink); font-weight: 600; }
/* 10-04 晚：模式标签在日期下面单独一小行（不再塞进定宽的日期时段那一格，免得把「上次结果」整列顶歪） */
.task-mode-line { margin: -1px 0 3px; line-height: 1.4; }
.task-mode { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: top; padding: 0 5px; border-radius: 4px; background: var(--accent-soft); color: var(--accent); font-size: 11px; font-weight: 600; white-space: nowrap; }

/* 10-02：设置里四方的链接，一行一个 */
.sifang-links { display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 8px; margin-bottom: 8px; }
.sifang-links:empty { display: none; }
.sifang-link-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 6px 10px; font-size: 13px; }
.sifang-link-row + .sifang-link-row { border-top: 1px solid var(--line); }
.sifang-link-row code { color: var(--muted); font-size: 12px; }
.sifang-link-row .grow { flex: 1; }
.sifang-link-problem { flex-basis: 100%; margin: 0; color: var(--danger); font-size: 12px; }
/* 10-10：程序自己在处理的（下次用它时再连、先用别的链接的号）：橙色 */
.sifang-link-problem.is-calm { color: var(--warning); }
/* 10-03 用户：这条链接哪些号收不到短信——有才在这一行下面多一行提醒色小字，点开才列号码（灰色小字，一个号不拆成两行），别把设置搞乱 */
.sifang-link-nosms { flex-basis: 100%; font-size: 12px; }
.sifang-link-nosms > summary { display: inline-flex; align-items: center; gap: 6px; color: var(--warning); cursor: pointer; list-style: none; }
.sifang-link-nosms > summary::-webkit-details-marker { display: none; }
.sifang-link-nosms > summary::before { content: ""; width: 5px; height: 5px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform .12s; }
.sifang-link-nosms[open] > summary::before { transform: rotate(45deg); }
.sifang-link-nosms > p { display: flex; flex-wrap: wrap; gap: 2px 14px; margin: 4px 0 0; color: var(--muted); line-height: 1.6; }
.sifang-link-nosms > p > span { white-space: nowrap; }
.sifang-editor { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; padding: 10px; border: 1px solid var(--line-strong); border-radius: 8px; }

/* 10-02 用户：「设置里面的功能区分明显点，现在太累了」——抽屉底色浅灰，每块功能一张白卡片（圆角、块和块之间留空），
   标题大一档、前面一道蓝条；「其它设置」也是一张卡片，里面那几块不再套卡片，只用细线分开 */
.drawer-panel, .drawer-head { background: var(--canvas); }
.settings-section { padding: 14px 16px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); margin-top: 12px; }
.settings-section:first-of-type { border-top: 1px solid var(--line); margin-top: 0; }
.settings-heading h3 { font-size: var(--t-title); padding-left: 8px; border-left: 3px solid var(--accent); line-height: 1.25; }
.settings-more { border: 1px solid var(--line); border-radius: 10px; background: var(--surface); padding: 0 16px; margin-top: 12px; }
.settings-more .settings-section { border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent; padding: 14px 0; margin-top: 0; }
.settings-more .settings-heading h3 { font-size: var(--t-item); border-left: 0; padding-left: 0; }
/* 10-02 用户：「uiux 要统一」——按钮一律靠右（取消在左、主按钮在右），和确认框、导入名单、代理设置一个样子 */
.settings-row-end { justify-content: flex-end; }
.prep-row-end { margin-left: auto; }

/* 10-10：程序装了新版、这个页面还是旧的：顶上提示刷新（见 public/modules/render.js noteAppRelease） */
.page-notice { position: sticky; top: 0; z-index: 2000; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px;
  padding: 8px 16px; background: var(--danger-soft); color: var(--danger); border-bottom: 1px solid var(--danger); font-size: var(--t-body); }
/* 10-21：豪猪对接码认不认（见 public/modules/settings.js） */
.provider-code-item .provider-code-bad { color: var(--danger, #c0453f); font-style: normal; }
.provider-code-item .provider-code-good { color: var(--faint); font-style: normal; }
