/* ════════════════════════════════════════════════════════════════
   本地工作台 · 视觉系统 v6「雾蓝石墨」（深/浅双主题）
   柔和深灰蓝 / 浅灰蓝底 + 卡片明显浮起 + 10px 圆角 + 柔和阴影造深度。
   主色=雾蓝（深 #6aa5f0 / 浅 #3477c4），用在主操作、KPI 数字、当前导航位、
   区块标题竖条；语义色低饱和但不发灰。无装饰性发光、无呼吸闪烁。
   等宽字体只留三处：KPI 大数字 / 表格数字列 / 真代码与终端输出。
   两条硬约束：①单元格水平内边距锁 14px（宽表按列数放大，理由见 th, td 那条注释）
             ②不写 scrollbar-width（Chrome 认了它就忽略 ::-webkit-scrollbar-*）
   保留原样：财务红警语义、终端框两主题都深底。
   Mac 排版（2026-10-06）：沿用系统字体，标题 24/16/14px，数据与控件 13–14px；
   辅助文字收在 12px 附近，大屏内容区在侧栏右侧居中，正文与文档行宽保持原有密度。
   约束不变：零构建链、零外部资源（系统字体 + 纯 CSS），不强制字体抗锯齿。
   设计依据：docs/superpowers/specs/2026-08-03-dashboard-visual-refresh-design.md
   ════════════════════════════════════════════════════════════════ */

:root {
  /* 场域（深灰蓝分层阶梯：底最深，卡片浮一档，交互面再浮一档） */
  --bg: #171b24;            /* 页面底（石墨蓝，不是纯黑，长时间看不刺眼） */
  --surface: #212734;       /* 卡片/表格面（比底亮一档，靠明度差浮起来） */
  --surface-2: #2a3140;     /* 略亮一档的面（hover、thead） */
  --surface-3: #38404f;     /* 再亮一档（滚动条 hover 等，随线阶梯同步提亮）*/
  --sidebar-bg: #1b202a;    /* 侧栏（介于底与卡片之间，压住不抢主区） */
  /* 线阶梯：新系统深度主要靠明度分层 + 柔和阴影，线只做收边，
     所以比黑金时代刻意收敛。实测 vs surface：主描边 1.33:1、
     hover/按钮边 1.96:1（黑金时代分别是 2.08 / 3.41:1）——
     单看线是弱了，结构差额由卡片阴影与圆角补回来，改线值前先看阴影还在不在 */
  --line: #333b4b;          /* 主描边（比卡片略亮，收边用） */
  --line-soft: #2b3240;     /* 弱描边/表格行线（只提示分行，不切割版面） */
  --line-hover: #4a5466;    /* hover/聚焦描边、ghost 按钮边 */
  /* 文字（深灰蓝底上偏冷的中性阶梯；数据/标题强调=白） */
  --text: #eef1f7;
  --muted: #b0b9c8;         /* 表格正文用色，提亮是去沉闷的关键 */
  --faint: #838d9e;         /* 最弱文字（单位、辅助说明） */
  --strong: #ffffff;        /* 数据/标题强调=白（主色另有 --accent，不泛蓝） */
  /* 雾蓝主色（主操作 / KPI 数字 / 当前导航位 / 区块标题竖条） */
  --accent: #6aa5f0;        /* 雾蓝：主操作填充、当前位、KPI 强调 */
  --accent-ink: #93c0f7;    /* 主色做文字/描边时提亮一档（深底上够读） */
  --accent-on: #0d1119;     /* 主色底上的文字（深墨压蓝） */
  /* 语义色（低饱和但不发灰，三色彼此可分辨）
     ⚠ 这四个色**主要身份是「文字墨色」**（.ev.* / .tag.* / .tab-cnt.* / .job-result.*
     / .cnt-* / .diff-* 全都拿它当 color），所以定值判据是**压在自己的 --tone-* 淡底上
     仍过 WCAG AA 4.5:1**，不是「看着像不像那个颜色」。淡底是同色相的，同色压同色时
     对比度只剩明度差那一点——这是 2026-08-03 Task 6 用无头 Chrome 逐个量出来的：
     旧值在浅色下全线 3.0–4.3:1 不达标（详见浅色块里那段）。
     深色这边只有 --danger 不够（#f07a70 压 --tone-red 上 4.31:1），提亮到 #f28f87。 */
  --ok: #5ecfa0;            /* 完成/成功 */
  --warn: #f0b95e;          /* 警告/待办/待发货 */
  --danger: #f28f87;        /* ⚑ 财务红警（语义保留，绝不去饱和到看不出是红） */
  --blue: #7db8f5;          /* 链接/运单号/运输中等"可点"的冷蓝
                               （与主色同族但更亮，链接不被误当按钮） */
  /* 控件 */
  --input-bg: rgba(255, 255, 255, 0.05);
  --thead-bg: transparent;
  --btn-bg: transparent;
  --btn-bg-hover: rgba(255, 255, 255, 0.08);
  --btn-border: #4a5466;    /* 可交互轮廓（与 --line-hover 同值，边界一致） */
  --nav-active-bg: rgba(106, 165, 240, 0.16);  /* 当前导航位=主色淡底 */
  --total-row-bg: rgba(255, 255, 255, 0.05);
  /* 图表（Chart.js 运行时经 getComputedStyle 读取，必须是字面值） */
  --chart-fill: rgba(106, 165, 240, 0.14);  /* 主序列填充（主色淡铺） */
  --bar-strong: #6aa5f0;                    /* 图表主柱/主序列=雾蓝主色 */
  --bar-soft: #5ecfa0;                      /* 图表次柱/第二序列=薄荷绿 */
  --bar-track: #2b3240;                     /* 横向分布条底槽 */
  --accent-bar: #6aa5f0;                    /* 物流 InTransit 条，恒等于 --accent */
  --chart-text: #8d97a8;
  --chart-grid: rgba(255, 255, 255, 0.08);  /* 图表网格（浅到只做参考线） */
  /* 终端框（两个主题都保持深底） */
  --runbox-bg: #12161e;
  --runbox-text: #b9c4d2;
  /* 语义淡底（同色系底 + 同色系文字的组件用它；实际消费者是 .tab-cnt.* /
     .ev.* / .row-oos / .btn-danger / .secret，另外 .badge 与 .doc-body code
     只吃 --tone-gray 当恒灰底）。
     ⚠ .badge 不在这套色底里：它恒定灰底 + 左侧 6px 方色钉表语义，见 .badge 那节 */
  --tone-gray: rgba(255, 255, 255, 0.07);
  --tone-green: rgba(94, 207, 160, 0.16);
  --tone-blue: rgba(125, 184, 245, 0.16);  /* 与 --blue 同源 */
  --tone-yellow: rgba(240, 185, 94, 0.16);
  --tone-red: rgba(240, 122, 112, 0.16);
  /* 字体 */
  --sans: -apple-system, BlinkMacSystemFont, system-ui, "PingFang SC", "Segoe UI", "Microsoft YaHei", sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }
html { color-scheme: dark; }
/* 浅色的 color-scheme 写在文件末尾那个 html[data-theme="light"] 色板块里，
   刻意不在这儿单开一条同名单行规则——同选择器出现两次时，按选择器抓块的
   工具（守护测试 _block、日后的样式检查脚本）只会抓到先出现的那条 */

/* 正文放松一档：14/1.6 → 15/1.7，并删掉全局字距。
   ⚠ letter-spacing 写在 body 上会渗进**每一个**后代（表格、代码块、终端输出都在内），
   想在局部收紧只能逐条写 letter-spacing: 0 去顶，是最难回收的一种全局微调。
   要拉开某处的字距就写在那一处。 */
body {
  margin: 0;
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.7;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: var(--bg);
}

.layout { display: flex; min-height: 100vh; }

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ── 侧边导航 ── */
.sidebar {
  width: 216px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}
/* 品牌区是回首页的链接（<a class="brand">）。全局 `a` 那两条（蓝色、hover 下划线）
   都得压掉：颜色靠本规则的特异性自然赢，下划线要显式写——品牌名上挂一道下划线
   既难看又会把 .brand-sub 那行小字一起拖下去。可点提示只用 opacity（logo 是
   mask+background 上色的 span，opacity 对它同样有效），刻意**不动盒模型**：这块的
   padding 与负 margin 都被守护测试和布局体检盯着，加背景高亮就得连 margin/radius
   一起调，为一个 hover 反馈不值得。 */
.sidebar .brand {
  padding: 26px 20px 16px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--strong);
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  transition: opacity 0.15s;
}
.sidebar a.brand:hover { opacity: 0.72; text-decoration: none; }
.brand-text { display: flex; flex-direction: column; gap: 3px; align-items: center; }
/* letter-spacing 加在**每个**字符后面（含最后一个），文本右侧凭空多出一格空隙，
   而 .brand-text 是 align-items:center 的 flex 列——这一格会让两行整体左偏半格。
   负 margin 就是用来抵掉它的，所以两者**绑定**：字距改了这里必须跟着改成同一个数。
   由 test_brand_negative_margin_cancels_letter_spacing 钉住。
   .brand-main 不写字距、继承 .sidebar .brand 的 0.5px；.brand-sub 自己写 0.5px。 */
.brand-main { margin-right: -0.5px; }
.brand-text .brand-sub { margin-right: -0.5px; }
/* G6 logo：单文件 alpha 蒙版 + CSS 上色（跟随 --strong 单色，跨主题自动正确） */
.brand-logo {
  width: 22px; height: 22px;
  flex-shrink: 0;
  background: var(--accent);
  -webkit-mask: url("/static/g6-logo-mask.png") no-repeat center / contain;
  mask: url("/static/g6-logo-mask.png") no-repeat center / contain;
}
/* uppercase 在这里**保留**：VAPE-AUTOMATION 是产品名缩写，全大写是它本来的写法，
   不是被 CSS 喊出来的（全站唯一一条获准 uppercase 的规则，见守护测试白名单） */
.brand-sub {
  display: block;
  font-weight: 400;
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--faint);
}
.nav-groups { flex: 1; padding: 4px 0; overflow-y: auto; }
.sidebar ul { list-style: none; margin: 0; padding: 0 10px; }
/* 业务线分组小标题：低调灰色小字（与 .sidebar-foot 同一档标签语汇） */
.nav-group-label {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.5px;
  color: var(--faint);
  padding: 12px 22px 3px;
}
.nav-groups > .nav-group-label:first-child { padding-top: 6px; }
/* 导航项：整块圆角高亮，不再靠左侧那道 2px 硬边框标当前位——边框把 li 往右顶
   2px，且是全站唯一还在用「左硬边」表状态的地方（卡片那族已经全改阴影+底色）。
   ⚠ line-height 只在这里收到 1.5：全局 1.7 是给**成段正文**放松用的，导航是 22 条
   单行标签，行高摊到每条都是白涨。实测（无头 Chrome，1580×1000 真页面）单项
   43.80px → 41.00px，22 条合计**回收 61.5px**；侧栏那个滚动容器 .nav-groups 藏起来
   的部分从 586px 降到 525px，一屏内可见项 10 → 11 条。
   ⚠ **别再往下收**：1.4 还能多收 30px（单项 39.6px），但那已经低于 Task 5 之前
   （行高 1.6 时单项 42.4px）的基准线了——这一栏是鼠标+手机两用，拿点击面积换一屏
   不划算。1.5 落在基准线下方 1.4px，是「收回来了但没伤到手感」的那个点。 */
.sidebar li a {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 13px;
  margin: 2px 0;
  border-radius: 9px;
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
.sidebar li a:hover { background: var(--nav-active-bg); color: var(--strong); text-decoration: none; }
.sidebar li.active a {
  background: var(--nav-active-bg);
  color: var(--accent-ink);
  font-weight: 600;
}
.sidebar-foot {
  padding: 14px 20px;
  font-family: var(--sans);
  font-size: 12px;
  line-height: 1.9;
  color: var(--faint);
  border-top: 1px solid var(--line-soft);
}

/* 侧栏身份区（登录视角显示用户名+登出；本机 owner 显示「本机视角」） */
.sidebar-user {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 20px 12px;  /* 底距留足：贴 0 时用户行压着 sidebar-foot 分隔线 */
  font-size: 12px;
  color: var(--muted);
}
.sidebar-user .user-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.btn-link {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.btn-link:hover { color: var(--strong); }

/* ── 内容区 + 页头 ── */
/* 窄桌面仍占满侧栏右侧；宽屏封顶并居中，额外空间均分到内容两侧。
   文档正文另有 .doc-body 的 900px 上限，不跟数据区一起拉长。 */
/* 同源页面之间切换用浏览器原生的视图过渡交叉淡入，不再硬切一下（2026-10-16 用户反馈「点运行按钮画面会闪一下」）。
   点按钮（POST → 重定向回本页）与任务跑完的自动刷新都是整页跳转；配合 dashboard.js 把滚动位置原样摆回去，
   新旧两页几乎一样，看得出变化的只剩真变了的地方（按钮状态、运行胶囊）。时长压短到 140ms：是「不闪」不是「动画」。
   不支持的浏览器（Firefox、旧 Safari）照常硬切，功能不受影响；系统设了「减少动态效果」的不做。 */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 140ms; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
.content { flex: 1; padding: 34px 44px 90px; max-width: 1520px; min-width: 0; margin-inline: auto; container-type: inline-size; }

.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 16px;
  margin-bottom: 22px;
  border-bottom: 1px solid var(--line-soft);
}
.page-head h1 {
  margin: 0;
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0;
}
.page-head-meta {
  font-family: var(--sans);
  font-size: 12.5px;
  letter-spacing: 0;
  color: var(--faint);
  white-space: nowrap;
}
/* 详情页面包屑（h1 内：上级板块灰显可点，当前页白色） */
.page-head .crumb { color: var(--muted); font-weight: 500; }
.page-head .crumb:hover { color: var(--text); text-decoration: none; }
.crumb-sep { margin: 0 9px; color: var(--faint); font-weight: 400; }

/* 区块标题：主色圆头竖条（无 glow）+ 标题居左，注释/页签自动靠右 */
h2 {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0;
  margin: 34px 0 14px;
}
h2::before {
  content: "";
  flex-shrink: 0;
  width: 3px;
  height: 14px;
  border-radius: 999px;
  background: var(--accent);
}
h2 .note { margin-left: auto; text-align: right; }
h3 { font-size: 14px; font-weight: 600; letter-spacing: 0;
     margin: 0 0 10px; color: var(--text); }

/* ── 状态指示灯（新鲜度等）：单行排版 ── */
.freshness { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 24px; }
.chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 15px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 13px;
  white-space: normal;
  max-width: 100%;
  overflow-wrap: anywhere;
  color: var(--muted);
}
.chip::before {
  content: "";
  flex-shrink: 0;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--faint);
}
/* 筛选 chip 选中态（finance 收款账户等把 chip 当筛选器用时）——此前模板挂
   .active 但无任何样式定义，切了筛选看不出选中项 */
.chip.active { border-color: var(--accent); color: var(--strong); }
/* 全局错误条：错误文案可能带路径/堆栈片段，必须允许换行（.chip 默认 nowrap） */
.chip-err { white-space: normal; height: auto; line-height: 1.6;
            padding-top: 6px; padding-bottom: 6px; margin-bottom: 12px; }
.chip.active::before { background: var(--accent); }
.chip.ok::before    { background: var(--ok); }
.chip.warn::before  { background: var(--warn); }
.chip.stale::before { background: var(--danger); }
.chip strong { font-family: var(--sans); font-size: 13.5px; letter-spacing: 0;
               color: var(--text); font-weight: 600; }
/* .chip code 的规则 2026-08-03 随「技术痕迹改人话」删除：它当时是全站唯一把
   shell 命令行铺在页面上的地方（装 ops_data.SYNC_HINTS），现在命令收进
   .copy-cmd 按钮的 data-cmd 属性，chip 里已没有 <code>。别看见 chip 里排版
   偏窄就把它加回来——加回来等于把命令行又铺回页面。 */
/* 「复制命令」按钮（.copy-cmd）没有自己的样式：它就是一颗普通 .btn.btn-sm，
   刻意与页面上其他按钮同族——命令行的存在感本来就该退到一次点击之后。
   它的最终兜底 `input.cmd-manual-copy`（两条复制路都失败时就地展开的只读输入框）
   **不在这里**，在下面全局表单控件块之后——理由写在那条规则上，别挪回来。 */
/* 运单号注册：输入框与同区 chip/按钮高度对齐 */

/* ── 指标卡 ── */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; }
.sync-cards .card { display: flex; flex-direction: column; }

/* ── 同步卡片（客户管理）：kicker 行 + 紧凑状态胶囊 + 来源文本行 ──
   chip 不能放进 h3：会继承 mono/uppercase/letter-spacing 且 flex 撑满整行 */
.sync-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 7px; }
.sync-head h3 { margin: 0; }
.sync-head .chip { padding: 3px 11px; border-radius: 999px; font-size: 12px; gap: 7px; }
/* ── 逐站拉取成败（操作中心「零售同步」卡，spec 2026-08-06）──
   8 个站的胶囊排成一行、窄屏自然折行；紧凑内边距同 .sync-head .chip——
   标准胶囊的 9px 内边距在卡片里放不下 8 个。绿点=成功（带本轮单数），
   红点=失败（带原因）；点色沿用 .chip.ok / .chip.stale，不另造配色。 */
.site-status-note { margin-top: 8px; }
/* ⚠ 自带一条 .fail 而不是复用 .jr-error：后者的规则是 `.job-result.jr-error`，
   挂在裸 span 上层叠不中、红色静默失效（静态看代码看不出来，实测才发现）。 */
.site-status-note .fail { color: var(--danger); font-weight: 600; }
.site-status { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.site-status .chip { padding: 3px 10px; font-size: 12px; gap: 6px; }
.site-status .chip strong { font-size: 12px; }
/* 失败原因（SSLError / 疑似 SG WAF 挑战…）：正体、弱化，站名仍是主角 */
.site-status .chip em { font-style: normal; color: var(--muted); }
.sync-meta { font-size: 13.5px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; margin-bottom: 3px; }
.sync-meta .dim { color: var(--faint); font-weight: 400; margin: 0 7px; }
.sync-cards .sub.note { line-height: 1.7; }
.sync-actions { display: flex; flex-direction: column; gap: 0; margin-top: auto; padding-top: 6px; }
/* 操作行（版A）：每个操作一行，按钮列 + 摘要/输入列 1:1 绑定（form 本身做 grid 容器，hidden 不占列）*/
.op-row { display: grid; grid-template-columns: minmax(150px, max-content) 1fr; align-items: center;
  gap: 16px; padding: 9px 0; margin: 0; border-bottom: 1px solid var(--line-soft); }
.op-row:last-child { border-bottom: none; }
.op-row .op-btn { white-space: nowrap; }
.op-row .op-summary { min-width: 0; }
.op-row .op-summary .job-result { margin-top: 0; }
.op-row .op-arg { max-width: 280px; }
@media (max-width: 640px) {
  .op-row { grid-template-columns: 1fr; gap: 6px; }   /* 窄屏：按钮与摘要上下堆叠 */
}
/* 深度=柔和阴影，不是硬描边。两层刻意分工：贴身的 0 2px 4px 画出「离底一点点」
   的接触边，大范围的 0 10px 22px -12px（负扩散把上沿收回去）画出漫射投影。

   ── 深度分两档，别混用（2026-08-03 定）──
   **卡片档**（下面这条值）＝不透明的「容器面」，浮满一档：
     .card / table / .table-scroll / .tbl-wrap / .chart-box / .run-box /
     .empty / .newpanel
   **条幅档**（弱一档，值写在 .recon-alert 那条上方）＝贴在内容流里的提示条：
     .recon-alert / .recon-reminder / .secret。理由见那条注释。
   同档必须同值，由 test_depth_tiers_share_one_value 钉住；两档必须不同值
   （同值＝这个分档决定被悄悄抹平了），由同一条测试一起钉。

   两档都在各自规则里重复写**字面量**，刻意不抽 --shadow-* 变量：浅色补丁一旦
   改写变量而不是 box-shadow，守护测试 test_light_theme_shadows_are_not_pure_black
   的正则就捞不到了，等于用一次重构把既有守护静默架空。

   ── 深色这条值怎么来的（2026-08-03 第三轮复量，下面每个数字都是真渲染量的）──
   量法：无头 Chrome，只给被测那一个元素留阴影（其余全 box-shadow:none，免得
   相邻元素的投影混进来），从它下沿逐行向下扫像素，取三个 x 的中位，跟
   --bg #171b24 = rgb(23,27,36) 的**蓝通道 36** 比。**先拿 box-shadow:none 做对照组**
   自证量法可信——对照组每一行都正好扫出 36，说明扫到的确实是阴影本身而不是
   边框或下一个元素。（量之前记得把 transition 关掉，.card 有 box-shadow 0.16s，
   不然截到的是过渡中间帧——第一次就栽在这上面。）
   「拖尾」的定义＝**从下沿往下数到蓝通道重新等于 36 为止**的距离。换个容差
   （比如「差 2 级以内就算回到底色」）数字会小一半，比时必须用同一把尺。

   A. 单块的深度与拖尾（/overview 首张 .card）
     初版 .16/.55（blur 28 / -14px）  最深 B=24  拖尾 38px
     中间 .28/.72（blur 30 / -10px）  最深 B=18  拖尾 45px
     现值 .40/.95（blur 22 / -12px）  最深 B=14  拖尾 31px
   现值比初版深了一倍多（离底色 22 级 vs 12 级），拖尾反而最短——**这是刻意的**。

   B. 最挤的那条缝里会不会糊（/admin/users 的 .tbl-wrap→.newpanel，实测 gap 14px，
      两侧元素同时投影，量缝内最亮的一行）
     对照 无阴影  36（=底色）      初版 32      中间 28      现值 30
   ⚠ 三个候选**都没有**在这 14px 内回到底色 36——拖尾（31~45px）本来就比缝长，
   所以「谁能回到底色」是个伪命题，能比的只有「缝里还剩多少亮度」。现值 30 比
   中间版 28 高 2 级、比初版 32 低 2 级：**用缝里 2 级的代价换来深度翻倍**，这是
   本轮的取舍，不是「与初版持平」。真正糊掉的是中间版——它为了加深把漫射层摊到
   blur 30/-10px，拖尾 45px 是缝宽的三倍还多。
   ⚠ 所以别再用「加大 blur / 放宽 spread」去提深度，那条路撞的就是这 14px 的缝；
   要更深就动 alpha。
   ⚠ **14px 是「留了缝的那些地方里最挤的一档」，不是全站最小间距**（2026-08-03 复核
   订正，此前这里写的是「全站最挤的纵向间距就是这 14px」，不实）：`.card` 没有 margin、
   也没有 `.card + .card` 规则，于是 /landing、/arrivals、/dedupe、/shipped 上**连续
   两张 section.card 实测贴着（gap 0.00px）**。扫过的 16 个页面里，缝宽只有 0 / 14 /
   16（.charts）/ 28 这几档，14px 是非零里最小的那档（.cards 网格 gap 也是 14px）。
   ⚠ 但 0px 那些地方**没有暗带**，本条结论不受影响、反而更强：下面那张卡的不透明
   背景把上面那张的投影整个盖住了。对照实验（同一条缝上下各 8 行 × 横向每 37px 一列
   共 540 个采样点，把上面那张卡的 box-shadow 关掉再截一次）——**540 点逐点相同、
   0 处差异**，深浅两个主题、两个页面都一样。缝里那两行偏亮的像素是两张卡各自的 1px
   边框（深色 rgb(51,59,75) / 浅色 rgb(226,232,241)＝--line），不是阴影。
   所以「拖尾会不会糊」这条约束的对手始终是 14px 那档，不是 0px。
   ⚠ 深色的层次**主要靠 --surface(33,39,52) 比 --bg(23,27,36) 亮那一档**承载，
   阴影只是辅助，别当浅色那样使。浅色那份（文件末补丁区）本轮没动——同一套量法
   实测它最深处 rgb(216,220,225) 比底色 rgb(242,245,250) 暗 26 级，本来就够。
   ⚠ transition 里的 transform 是给下面 `.card:hover` 的 1px 上浮用的（2026-08-04
   订正：此前这里写「现在没有任何规则在用，是给 Task 6 预留」，那句在 Task 6 落地
   当天就过期了，而 17 行后的 hover 注释还回指着它说「就是这里」——两条互指的
   注释一起说了假话）。 */
.card {
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 18px 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
  transition: transform 0.16s, box-shadow 0.16s, border-color 0.16s;
}
/* hover 微上浮 1px + 阴影加深：位移刻意只给 1px——.cards 是 auto-fit 网格，
   同排卡片彼此挨着，抬多了相邻卡的投影会互相盖。transform 由上面 .card 的
   transition 负责补间。

   ⚠ **hover 的几何必须与静置逐字相同，深度只许从 alpha 拿**（2026-08-04 修）。
   此前这条写的是 `.16/.55`（blur 6/36、spread -16）——那**正是上面那段里被判
   「基本看不见」而废弃的初版值**，于是 hover 比静置还浅、拖尾还长，卡片划过去
   是「沉下去」而不是「浮起来」，只被 1px 位移遮掩了一半。同一轮把浅色对家也
   一起改了：它方向虽对，却是靠把 blur 26→36 / spread -18→-16 摊出来的，撞的正是
   上面那段明令禁止的那条路。

   无头 Chrome 逐像素扫卡片下沿（关 transition、prefers-reduced-motion: reduce，
   场上只放这一张卡；「最深」=底色 R 通道减该行最暗像素，「拖尾」=回到底色前的行数）：
                     深色最深 / 拖尾        浅色最深 / 拖尾
     静置             14 级 / 16.0px         25 级 / 16.0px
     改前 hover        9 级 / 25.0px         40 级 / 33.0px   ← 深色反而浅 5 级
     改后 hover       17 级 / 16.0px         44 级 / 19.0px
   ⚠ 深色这 +3 级看着少，那是**物理上限**逼的、不是没使劲：深色底 --bg 的 R 通道
   只有 23，纯黑压上去最多也就暗 23 级，静置已经吃掉 14 级。同一套量法实测
   contact .50/.55/.62/.70 依次拿到 15/16/16/17 级——0.70 是收益拐点，再往上只会
   把接触层压成一道近黑硬边（那是「贴着桌面」的读法，与「浮起来」相反）。
   要再深只能摊 blur/spread，而那条路被 14px 缝挡死了。
   ⚠ 浅色改后拖尾 19px 比静置的 16px 长 3px，**不是几何变宽**——几何逐字相同，
   是 alpha 抬高后尾巴更暗、更晚跌回底色阈值。
   守护 test_card_hover_is_deeper_and_not_smeared 深浅两套一起钉。 */
.card:hover {
  border-color: var(--line-hover);
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.70), 0 10px 22px -12px rgba(0, 0, 0, 1);
}
.card .big {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 25px;
  font-weight: 500;
  letter-spacing: -0.3px;
  line-height: 1.3;
  margin: 12px 0 6px;
  color: var(--strong);
}
.card .sub { color: var(--muted); font-size: 13px; margin-top: 3px; line-height: 1.6; }
/* 卡片内表单弹性沉底：同行等高卡片的按钮自动对齐到同一水平线。
   约定：form 必须是卡片最后一个子元素，且不要写内联 margin（会顶掉 auto） */
.card > form { margin-top: auto; padding-top: 14px; display: flex; align-items: center;
               flex-wrap: wrap; gap: 8px; }
.card-danger { border-left: 3px solid var(--danger); }
/* KPI hero：主指标卡左侧主色竖条 + 主数字染主色（KPI 强调，跟着 --accent 走） */
.card.hero { border-left: 2px solid var(--accent); }
.card.hero .big { color: var(--accent-ink); }
/* 卡片标题：域名/指标名 + 徽章/note 同排可换行，row-gap 给换行的徽章垂直呼吸
   （徽章升级为 inline-flex 后高度 > h3 行高，多徽章换行会垂直叠压，flex-wrap 修正）*/
.card h3 { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.card h3 .note { font-weight: 400; }
.card h3 .badge { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }

/* 对账红色警报横幅（账单独有=回款侧疑似删改）+ 差异表危险行 */
/* ── 条幅档阴影（比卡片档弱一档，接触层保留、漫射层砍半）──
   这一族（.recon-alert / .recon-reminder / 900 行外的 .secret）刻意不跟卡片同档：
   ① 它们的底是**半透明**的（color-mix(… 12%, transparent) / --tone-yellow 是
      rgba(…,.13)）——能看见页面透过去的东西，却投出跟实心卡一样重的影子，是自相矛盾；
   ② 它们是**贴在内容流里的提示条**，不是容器。浮到跟下面那排卡同高会跟卡片抢同一
      层、打断阅读——通知条该「微微离地」，不该「架起来」；
   ③ 它们的存在感本来就由饱和 1px 框 + 3~4px 左强调边 + 淡色底承载，全页最吵的
      就是它，不需要靠深度再喊一遍。
   「弱一档」是量出来的，不是拍的（同 .card 上方那套量法，/finance 的条幅下沿）：
     卡片档现值   最深 rgb( 9,11,14)  比底色暗 (R14,G16,B22)  拖尾 20px
     条幅档现值   最深 rgb(15,18,24)  暗 (R8,G9,B12)         拖尾 7px
   条幅档≈卡片档的六成深、拖尾只有三分之一——够看出离地，又不会跟卡片抢层。
   拖尾特意压到 7px：条幅底下常常紧跟着正文或一排卡，摊长了就是糊在一起。
   ⚠ .recon-alert 是财务红色警报（账单有、回款没有＝疑似回款记录被删改），**它的
   刺眼程度不许被柔化**。加阴影不碰颜色：实测 color / border / background 三项
   计算值与加阴影前逐字节相同（阴影是独立属性，不参与颜色合成），红照旧是红。
   同族三条必须同值（test_depth_tiers_share_one_value）。浅色版在文件末补丁区。 */
.recon-alert {
  margin: 10px 0 14px;
  padding: 12px 16px;
  border: 1px solid var(--danger);
  border-left: 4px solid var(--danger);
  border-radius: 10px;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  line-height: 1.6;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24), 0 5px 12px -8px rgba(0, 0, 0, 0.55);
}
.recon-reminder {
  margin: 10px 0 14px;
  padding: 12px 16px;
  border: 1px solid var(--blue);
  border-left: 4px solid var(--blue);
  border-radius: 10px;
  background: color-mix(in srgb, var(--blue) 12%, transparent);
  color: var(--blue);
  line-height: 1.6;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24), 0 5px 12px -8px rgba(0, 0, 0, 0.55);
}
.row-danger td { color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, transparent); }

/* 「内容与 SEO › SEO 数据」（_seo_data.html，2026-10-15）：全站表与指标卡之间留一档；
   机会词的选站页签可能有十几个，靠 .seg-tabs 自己折行；表下那行说明贴着表。 */
.seo-sites-wrap { margin-top: 14px; }
.opp-tabs { margin-bottom: 12px; }
.opp-foot { margin: 8px 0 0; }
/* 站名与域名各占一行且不断行：窄屏时表在自己的壳里横滚，不许把「FuryVape」挤成「FuryVap / e」（390 宽实测） */
.seo-site-cell a, .seo-site-domain { white-space: nowrap; }
.seo-site-domain { display: block; }

/* 「内容与 SEO › 站点总览」（_seo_sites.html，2026-10-15）
   顶部四张卡里的站名胶囊：紧凑档同 .site-status .chip（标准胶囊 9px 内边距一张卡放不下 9 个）；
   站卡底部的「平台 / 在售 / 单号 / 验真 / 自动化」是两列定义表，margin-top: auto 把它压到卡底，
   同一排卡片的分隔线因此对齐。只用色板变量，浅色主题自动跟上，不加阴影、不写等宽字体。 */
/* 顶部三张汇总卡（悉尼仓 / 墨尔本仓 / 不卖货）按站数给宽度：墨尔本 9 个站名在等宽三栏里会
   挤成竖排（1280 实测卡高 414px），给它两倍宽后三行放下。窄屏回落默认的单列堆叠。 */
.site-total { margin-top: 18px; }
@media (min-width: 900px) {
  .site-summary { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1.3fr); }
}
.site-index { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 8px; }
.site-index .chip { padding: 3px 10px; font-size: 12px; gap: 6px; color: var(--text); }
.site-index .chip:hover { border-color: var(--line-hover); text-decoration: none; }
.site-card { scroll-margin-top: 16px; }
.site-card:target { border-color: var(--accent); }
.site-card h3 { margin-bottom: 2px; }
.site-card .site-domain { font-size: 13px; overflow-wrap: anywhere; }
.site-card .site-purpose { margin: 10px 0 14px; color: var(--text); font-size: 13.5px; line-height: 1.6; }
.site-facts {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 5px 14px;
  margin: auto 0 0; padding-top: 12px; border-top: 1px solid var(--line-soft);
  font-size: 13px; line-height: 1.55;
  /* subgrid 下这格与同排最高的定义表一样高；行少的那张要靠上排紧，别把几行字拉散开 */
  align-content: start;
}
.site-facts dt { color: var(--faint); }
.site-facts dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
/* 同一排站卡的分隔线对齐：卡片各占外层网格 4 行（名称 / 域名 / 一句话 / 定义表）并借 subgrid
   共用行高，于是一句话长短不一、定义表行数不一时分隔线仍在同一水平线上（只靠 margin-top: auto
   压底只能对齐卡底，分隔线会随定义表行数上下错开，1440 实测错开 25px）。
   不支持 subgrid 的浏览器落回上面的 flex 列，只是分隔线不对齐，内容不受影响。 */
@supports (grid-template-rows: subgrid) {
  .site-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; row-gap: 0; }
  .site-card .site-facts { margin-top: 0; }
}

/* 对账卡组：操作表单钉底，输入框/按钮跨卡横向对齐（同 .sync-cards 模式） */
.recon-cards .card { display: flex; flex-direction: column; }
.recon-cards .card form { margin-top: auto; padding-top: 14px; }
.pod-done { border-left: 3px solid var(--ok); }
#finance-repairs { margin-top: 28px; }
#finance-repairs > summary { color: var(--text); font-size: 15px; font-weight: 600; }
#finance-retail-evidence { margin: 18px 0 28px; scroll-margin-top: 24px; }
.finance-evidence-order { margin-top: 14px; }
.finance-evidence-order h3 { flex-wrap: wrap; overflow-wrap: anywhere; }
.finance-evidence-intro { margin: 8px 0 12px; }
.finance-evidence-overview { display: grid; grid-template-columns: minmax(210px, 1fr) 3fr; gap: 24px; align-items: center; margin: 16px 0; }
.finance-evidence-status { padding: 12px 16px; border: 1px solid var(--line-hover); border-left-width: 4px; border-radius: 8px; }
.finance-evidence-status.is-pending { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, var(--surface)); }
.finance-evidence-status.is-received { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--surface)); }
.finance-evidence-status.is-unknown { background: var(--surface-2); }
.finance-evidence-status-label, .finance-evidence-status-hint { display: block; color: var(--muted); font-size: 12px; }
.finance-evidence-status-value { display: block; margin: 3px 0; color: var(--strong); font-size: 23px; line-height: 1.3; overflow-wrap: anywhere; }
.finance-evidence-totals { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin: 0; }
.finance-evidence-totals dt { color: var(--muted); font-size: 12px; }
.finance-evidence-totals dd { margin: 5px 0 0; font-size: 16px; font-weight: 600; overflow-wrap: anywhere; }
.finance-evidence-table { margin-top: 8px; }
.finance-evidence-table table { width: auto; }
.finance-evidence-table td { vertical-align: top; }
.finance-evidence-comparison th, .finance-evidence-comparison td { padding-top: 10px; padding-bottom: 10px; }
.finance-evidence-comparison td strong { color: var(--text); font-size: 12.5px; }
.finance-evidence-note { min-width: 120px; max-width: 360px; white-space: normal; overflow-wrap: anywhere; }
.finance-evidence-pair { margin-top: 3px; font-size: 12px; }
.finance-evidence-mark-diff { margin-top: 3px; font-weight: 600; color: var(--text); }
.finance-evidence-record { overflow-wrap: anywhere; user-select: text; }
.finance-evidence-details { max-width: 165px; margin-top: 3px; font-size: 12px; }
.finance-evidence-details summary, .finance-evidence-scope summary { color: var(--muted); font-size: 12px; }
.finance-evidence-details summary:focus-visible, .finance-evidence-scope summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.finance-evidence-detail-body { overflow-wrap: anywhere; color: var(--muted); font-size: 12px; }
.finance-evidence-detail-body p { margin: 6px 0; }
.finance-evidence-scope { margin-top: 12px; }
@media (max-width: 700px) {
  .finance-evidence-overview { grid-template-columns: 1fr; gap: 16px; }
  .finance-evidence-totals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .finance-evidence-table { overflow: visible; }
  .finance-evidence-comparison, .finance-evidence-comparison tbody { display: block; }
  .finance-evidence-comparison thead { display: none; }
  .finance-evidence-comparison tr { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px 14px; padding: 14px; border-bottom: 1px solid var(--line-soft); }
  .finance-evidence-comparison td { display: block; width: auto; padding: 0; border: none; white-space: normal; text-align: left; min-width: 0; max-width: none; overflow-wrap: anywhere; }
  .finance-evidence-comparison td::before { content: attr(data-label); display: block; color: var(--muted); font-size: 12px; margin-bottom: 2px; }
  .finance-evidence-comparison td[data-label="来源"]::before { display: none; }
  .finance-evidence-comparison td[data-label="备注 / 配对"] { grid-column: 1 / -1; }
  .finance-evidence-comparison td[data-label="原表"] { grid-column: 1 / -1; }
}

/* ── 库存/价格矩阵 dry-run 差异标注（待同步=琥珀 --warn / 已应用横幅=浅绿 --ok）── */
.diff-banner { margin: 2px 0 10px; font-size: 12.5px; color: var(--warn); line-height: 1.6; }
.diff-banner.applied { color: var(--ok); }
.diff-arrow { color: var(--warn); font-weight: 600; font-size: 12.5px; white-space: nowrap; }

/* 按钮内联小图标（_icons.html 宏，统一替代旧「↻」字符） */
.btn-ico { width: 12px; height: 12px; vertical-align: -1.5px; margin-right: 5px; }

/* 主操作按钮（全站唯一实心 CTA：主色底 + --accent-on 文字，如物流页/CRM「更新订单」） */
/* 主操作按钮：必须与 .btn 同用（class="btn btn-primary"）。用 .btn.btn-primary 提权——
   .btn 在本表里定义于 .btn-primary 之后且同 specificity，单 .btn-primary 会被 .btn 的透明底/
   白字/hover 反色盖掉；hover 选择器同时高过 .btn:hover:not(:disabled)（2026-06-16 修）。 */
.btn.btn-primary { background: var(--accent); color: var(--accent-on); border-color: transparent; font-weight: 600; }
.btn.btn-primary:hover:not(:disabled) { background: var(--accent); color: var(--accent-on); border-color: transparent; filter: brightness(1.08); }

/* ── 表格 ── */
table {
  width: auto;
  max-width: 100%;
  table-layout: auto;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
  overflow: clip;  /* clip 不产生滚动容器，thead sticky 才能工作 */
}
/* 水平内边距锁 14px（test_cell_padding_horizontal_locked_at_14px）。
   N 列每加 1px 就增加 2N px；2026-08-04 收款账户裸表因此撑破页面。
   2026-10-08 宽表均补滚动壳，但仍保留这个间距，避免无谓增加横滚。
   到货成本 14 列主表用 table.dense 的 6px 9px，独立于本规则。 */
th, td { padding: 13px 14px; text-align: left; border-bottom: 1px solid var(--line-soft); }
/* 表头：原本 mono + 全大写 + 0.8px 字距的「军工风」，改回正常字体。
   ⚠ 颜色必须同时从 --faint 提到 --muted，理由是**对比度**不是层次：--faint 压在
   卡片面上，浅色只有 3.01:1、低于 WCAG AA 的 4.5（--muted 是 5.40:1；深色
   4.46 → 7.56:1）。表头与正文的区分靠三样：600 字重 + 13/14px 字号差 +
   thead 的 inset 底线（走 --line，比行线的 --line-soft 重一档）。
   **没有底色差**：--thead-bg 两个主题都是 transparent，下面 thead th 那层渐变
   叠在 --surface 上解析出来与表身同色，是空操作。日后真要给表头再加一层次，
   最省事的一步是把 --thead-bg 填成 --surface-2（变量与叠层代码都已在位）；
   要靠文字颜色拉开就往 --text 走，别退回 --faint（守护测试已把它堵死）。 */
th {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--muted);
  background: var(--thead-bg);
  white-space: nowrap;
}
/* 长表滚动时表头钉在视口顶（collapse 边框会丢，用内阴影补底线） */
thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  /* 叠层：--thead-bg（当前两主题都是 transparent，留作日后加底色的挂点）
     + --surface 不透明底（sticky 滚动不透字，这一层才是真在干活的） */
  background: linear-gradient(var(--thead-bg), var(--thead-bg)) var(--surface);
  box-shadow: inset 0 -1px 0 var(--line);
}
td { font-size: 14px; color: var(--muted); font-variant-numeric: tabular-nums; }
tbody tr { transition: background 0.12s; }
/* 行 hover 走中性面，不用 --nav-active-bg：那是「当前导航位」的语汇，鼠标扫过
   表格时整行泛主色蓝，读起来像「这行被选中了」。 */
tbody tr:hover { background: var(--surface-2); }
tr:last-child td { border-bottom: none; }
td.detail { color: var(--muted); font-size: 13px; max-width: 380px; }
td.nowrap, .nowrap { white-space: nowrap; }
td .note { font-size: 12px; }

/* 浏览器按整列的表头与内容计算宽度，不把空余空间平均摊给短列。
   数字/日期/动作保留完整内容；名称和说明可换行，长单词也不撑破页面。 */
th.fit, td.fit { white-space: nowrap; }
th.num, td.num {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
td.num { font-size: 13.5px; color: var(--text); }
th.center, td.center { text-align: center; }
td.center .focus-act { justify-content: center; }
th.col-main, td.col-main { max-width: 32ch; overflow-wrap: anywhere; }
th.col-wrap, td.col-wrap {
  max-width: 56ch;
  white-space: normal;
  overflow-wrap: anywhere;
}
td.col-wrap .badge { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
/* 分组双层表头（到货成本页等）：短表不吸顶（双行 sticky 同 top:0 会互相覆盖）；
   组标签行居中、去掉与子表头行之间的分隔线；.col-sep 画组间竖线 */
table.grouped-head thead th { position: static; }
table.grouped-head thead th[colspan] {
  text-align: center; box-shadow: none; border-bottom: none; padding-bottom: 3px;
}
th.col-sep, td.col-sep { border-left: 1px solid var(--line-soft); }

/* 可排序表头 */
th .th-link { color: inherit; text-decoration: none; }
th .th-link:hover { color: var(--text); text-decoration: none; }
th.th-sorted .th-link { color: var(--text); }
th .arrow { margin-left: 3px; font-size: 12px; }
.pager { display: flex; gap: 14px; align-items: center; margin-top: 14px; }

/* 长表限高滚动容器：thead 钉在容器顶，右侧常显滚动条（统计区不被长表挤走）。
   阴影必须画在**这里**而不是里面那张表上：容器 overflow-y:auto 会把内层表的
   投影整个裁掉（实测浅色下表下沿由白直接跳回底色 #f2f5fa，一格过渡都没有），
   而这套壳恰恰包着全站最大的几张表——概览「本周关注」、物流、发货转达、库存
   预警全在里面。同一条值与 .card/table 一致；内层表的阴影另行归零，见下条。 */
.table-scroll {
  width: fit-content;
  max-width: 100%;
  max-height: 64vh;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
}
/* ⚠ 必须保持单行写法：守护测试 test_no_leftover_zero_radius 逐行扫零圆角声明，
   靠整行匹配 _ZERO_RADIUS_ALLOWED 认出这条是刻意豁免的内层表格（外层容器已画圆角，
   内层再画会在四角露白缝）。展开成多行后那句声明独占一行、认不出身份，测试立刻红，
   而且反过来指着这条豁免规则本身报「还有零圆角没改」。要重排先改 _ZERO_RADIUS_ALLOWED。
   顺带一提：该测试**不剥注释**，所以这段说明里也不能出现那句声明的字面写法。 */
.table-scroll table { border: none; border-radius: 0; box-shadow: none; }

/* 宽表横滚包裹层（模板里 7 处，分散在 3 个页面：用户与权限 ×2 / 到货预测 ×2 /
   到货成本 ×3）。**「7 处」是跨页去重计数，不是同屏张数**——单页最多 3 张
   （到货成本，且其中一张关在折叠 details 里、一张在 {% if %} 分支里）。
   ⚠ 用户与权限那两处是「账号 / 审计流水」两个 tab 的 if/else 分支，同屏只出一张，
   所以改完两个 tab 都要各看一眼才算验过。
   与 .table-scroll 是同一个物种、同一套办法——**外壳拿边框圆角与阴影，内层表归零**：
   `overflow-x: auto` 让 overflow-y 一并计算成 auto，所以它跟 .table-scroll 一样会把
   内层表的投影整个裁掉（实测 /admin/users 表下沿由白 255 直接跳回底色 242,245,250，
   一格过渡都没有），深度只能画在壳上。边框也一并搬上来：表比壳宽时（横滚的常态）
   表自己的左右边框会随内容滑走，画在壳上框才钉得住。

   壳随内容收缩，上限为可用空间；超宽时只在壳内横滚。
   到货成本表含可编辑参数，需要可用空间来舒展输入框，单独保留填满布局。

   ⚠ 这条规则 2026-08-02 之前**被声明了两次**（admin 段一条只写 overflow-x、到货成本段
   一条 width+overflow），后者无声压过前者。合并成这一条，别再在别处另起。
   守护：test_table_wrappers_declared_once。 */
.tbl-wrap {
  width: fit-content;
  max-width: 100%;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
}
.tbl-wrap:has(> table.dense) { width: 100%; }
/* ⚠ 同 .table-scroll table：必须保持单行写法，守护测试按行认豁免身份（详见上一条注释）。 */
.tbl-wrap table { border: none; border-radius: 0; box-shadow: none; }

/* 统计模块填满所分配的空间，短工具表仍用内容宽度。
   auto 布局按内容比例分配余宽，数字/日期列继续保留完整内容；窄屏只在壳内滚动。 */
.table-fill { width: 100%; min-width: 0; }
table.table-fill, .table-fill table { display: table; width: 100%; table-layout: auto; }
.table-fill th.col-main, .table-fill td.col-main { min-width: 10ch; }
.table-fill th.col-wrap, .table-fill td.col-wrap { min-width: 18ch; }
.records-table th.col-main, .records-table td.col-main { min-width: 12ch; }
.records-table th.col-wrap, .records-table td.col-wrap { min-width: 20ch; }
.inventory-panels th, .inventory-panels td { padding-inline: 12px; }
.inventory-reference th, .inventory-reference td { padding-inline: 10px; }
.compact-table th, .compact-table td { padding-block: 8px; }
.compact-table td { font-size: 13.5px; }
.report-scroll { max-height: 360px; }
.cards.task-cards { grid-template-columns: minmax(0, 1fr); align-items: start; }
.task-cards > .sub { grid-column: 1 / -1; }
.cards.packing-cards { align-items: start; }
.packing-cards > .card > form { margin-top: 8px; }
.inventory-workspace { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.inventory-column { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.inventory-column > section { margin: 0; }
.inventory-tools { grid-template-columns: minmax(0, 1fr); }
.inventory-alert-panel > .filters { margin: 12px 0 8px; padding: 0; }
.inventory-alert-panel { container: stock-alert / inline-size; }
.stock-alert-meta { display: none; }
.inventory-alert-panel > .note { margin: 0 0 8px; }
.inventory-alert-scroll { max-height: 256px; }
.inventory-alert-scroll th.col-main, .inventory-alert-scroll td.col-main { min-width: 22ch; }
@container stock-alert (max-width: 430px) {
  .stock-alert-model, .stock-alert-id { display: none; }
  .stock-alert-meta { display: block; color: var(--muted); font-size: 12px; line-height: 1.5; }
  .inventory-alert-scroll th.stock-alert-desc, .inventory-alert-scroll td.stock-alert-desc { min-width: 0; }
}
.inventory-alert-panel .inventory-clear { margin: 8px 0 0; color: var(--muted); font-size: 13px; }
.price-brand { color: var(--muted); font-size: 12px; }
.data-panels { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.data-panels > * { min-width: 0; }
.data-panels > :only-child { grid-column: 1 / -1; }
.metric-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@container (min-width: 1000px) {
  .data-panels { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container (min-width: 900px) {
  .cards.metric-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .cards.metric-cards.metric-cards-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .customer-top, .customer-distribution { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .logistics-panels { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
  .customer-top > .customer-summary:only-child { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .cards.task-cards, .customer-preferences { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .inventory-workspace { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }
}
.crm-reports { display: grid; gap: 16px; }
.crm-coverage > summary { padding: 0; color: var(--text); font-size: 14px; font-weight: 600; }
@container (min-width: 1000px) {
  .recall-support-panels { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}
@container (min-width: 900px) and (max-width: 999px) {
  .customer-distribution { grid-template-columns: minmax(0, 1.16fr) minmax(0, .84fr); }
}
@container (min-width: 451px) and (max-width: 899px) {
  .cards.metric-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container (max-width: 450px) {
  .cards.metric-cards { grid-template-columns: minmax(0, 1fr); }
}
.customer-summary { display: grid; gap: 14px; align-items: start; }
.customer-summary > .card:only-child { grid-column: 1 / -1; }
.customer-summary > .note { grid-column: 1 / -1; order: -1; margin: 0; }

/* 月份是一组数据，总计是主数、卡板/散单是分项。文字、底色和组间线共同表达层级。 */
.comparison-table .period-start { border-left: 1px solid var(--line); }
.comparison-table thead th { background: var(--surface-2); }
.comparison-table .month-total { font-weight: 650; color: var(--strong); }
.comparison-table td.month-part { color: var(--muted); font-size: 12.5px; }
.comparison-table th.month-part { font-weight: 400; font-size: 12px; }
.period-date { display: block; font-size: 12px; font-weight: 400; color: var(--muted); }
.product-comparison .share-meter { min-width: 12ch; }

/* ── 嵌套：卡片**里**的卡片档成员降到条幅档（2026-08-03 补，同日复核补 .run-box）──
   它们此前一律拿完整卡片档，可投影是落在**父卡自己的面**上、不是落在页面底上，
   于是「卡里的一块浮得比装着它的卡还高」，而分档模型里压根没有这一层。
   降到**条幅档**（直接复用 .recon-alert 那条值，不为它另立第四档）。
   实测（量法同 .card 上方那套，只留被测元素的阴影、逐像素扫下沿、参照面取 computed
   背景色、取三通道最大差；.run-box 三处关在 {% if job %} 里，按 actions.html:12-17 的
   真实 markup 注入 DOM 再量）：
                     深色              浅色
     父卡（投在页面底）    暗 20 级          暗 21 级     ← 基准，卡里的谁都不该超过它
     .card 里的表       暗 16 级          暗 17 级     ✓
     .card .run-box 改前 暗 26 级（超了 6）  暗 14 级
     .card .run-box 改后 暗 10 级          暗 10 级     ✓

   ── 清单（两种数法都列，各有各的盲区）──
   ① 结构级（扫模板标签栈，含 {% if %} 另一支）：19 处，只有四类成员会嵌进 .card——
      table ×10 / .tbl-wrap ×5 / .run-box ×3 / .table-scroll ×1。
      .empty / .chart-box / .newpanel / .doc-body / 嵌套 .card **一处都没有**（模板里
      零 {% include %}，不存在跨文件嵌套）。
   ② 真渲染级（无头 Chrome 遍历今天的 DOM）：5 个页面 18 个实例——
      /dedupe（裸 table ×6）、/landing（.tbl-wrap ×3）、/arrivals（.tbl-wrap ×2 + 裸
      table ×1）、/crm（裸 table ×2，悉尼/墨尔本两条线各一份）、/shipped（.table-scroll
      ×1，同样两条线各一份）。**这个数随数据浮动**（/dedupe 的 6 个是今天的候选簇数）。
   ⚠ 两种数法必须都做：只数 DOM 会漏掉 {% if %} 里的——.run-box 三处（actions.html:16
   / finance.html:18；seo.html 那处 2026-10-15 随页面按钮撤掉）全在 {% if job %} 后面，**只有跑过任务才渲染**，
   于是整轮浏览器扫描一个都没见到，前一版清单也就没有它。而那恰恰是用户盯着操作
   中心看的时刻。只数模板则会把互斥分支、空数据一并高估。

   ⚠ 第二条规则不是多余的保险：`.card table` 与上面那条 `.tbl-wrap table` **specificity
   完全相等**（都是 0-1-1：一个类 + 一个元素），同权重时**后写的赢**——而它就写在后面，
   于是「内层表归零」被顶掉，壳里的表又长出投影，在壳的圆角处露白缝、投影还被壳的
   overflow 裁成硬切。`.card .tbl-wrap table` 是 0-2-1，高一档，压得住。用层叠语言补，
   不动上面那条、也不用 !important（与文件末 `html[data-theme="light"] .tbl-wrap table`
   同一个套路）。浅色版在文件末补丁区，同样两条一起。
   .run-box 不需要配「内层归零」条：它是 <pre>，里面只有文本、没有会长投影的子元素。 */
.card table,
.card .tbl-wrap,
.card .table-scroll,
.card .run-box {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24), 0 5px 12px -8px rgba(0, 0, 0, 0.55);
}
.card .tbl-wrap table,
.card .table-scroll table { box-shadow: none; }

.total-row td {
  border-top: 1px solid var(--line);
  background: var(--total-row-bg);
  font-weight: 600;
  color: var(--strong);
}
.delta-up { color: var(--ok) !important; }
.delta-down { color: var(--danger) !important; }
/* 经营总览本周卡同比 badge（颜色复用 delta-up/down） */
.wow { font-size: 12px; font-weight: 600; margin-left: 3px; white-space: nowrap; }
.wow-flat { font-size: 12px; margin-left: 3px; }

/* ── 徽标（「灰底胶囊 + 方色钉」：底恒灰，语义靠左侧 6px 方色钉）──
   走 999px 胶囊档：模板里最高频的一个类，每张表里都和同为 999px 的 chip/tab
   并排；而且它自己的变体 .badge.ev-pill 早就是 999px 了，不补这一条等于同一个
   类内部自己分裂成方的和圆的两种。 */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--tone-gray);
  color: var(--text);
  padding: 2px 8px;
  margin-left: 4px;
  letter-spacing: 0.2px;
  white-space: nowrap;
}
.badge::before {
  content: "";
  flex-shrink: 0;
  /* 方色钉刻意保持直角（不写 border-radius，直角是默认值）：外框是胶囊之后，
     方钉的对比反而更强——语义色靠这颗钉子读，不靠底色 */
  width: 6px; height: 6px;
  background: var(--muted);
}
td .badge { margin-left: 0; }
.big .badge { margin-left: 0; font-size: 12.5px; padding: 3px 10px; }  /* 凭据/状态卡：大字位徽章放大、左边缘对齐 .sub */
.badge.red::before    { background: var(--danger); }
.badge.yellow::before { background: var(--warn); }
.badge.green::before  { background: var(--ok); }
.badge.blue::before   { background: var(--blue); }
.badge.gray::before   { background: var(--muted); }
.note { color: var(--faint); font-size: 12px; font-weight: 400; letter-spacing: 0; }
p.note { color: var(--muted); line-height: 1.7; }
.empty {
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
  padding: 24px;
  text-align: center;
  color: var(--muted);
}

/* ── 优先级点（7px 语义色圆点 + 高/中/低文字，纯色不发光）── */
.prio { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; color: var(--muted); }
.prio::before {
  content: "";
  flex-shrink: 0;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--faint);
}
.prio-high::before { background: var(--danger); }
.prio-mid::before  { background: var(--warn); }
.prio-low::before  { background: var(--ok); }
/* h2 右侧的优先级计数（点 + 数字） */
.gcounts {
  display: inline-flex;
  gap: 12px;
  margin-left: auto;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
}
.gcounts .prio { gap: 5px; }

/* ── 折叠块 ── */
details summary {
  cursor: pointer;
  color: var(--blue);
  font-size: 13px;
  padding: 6px 0;
  user-select: none;
}
details summary:hover { text-decoration: underline; text-underline-offset: 3px; }
details[open] summary { margin-bottom: 8px; }

/* ── 图表 ── */
.charts { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.charts .note { grid-column: 1 / -1; }
.chart-box {
  min-width: 0;  /* Chart.js 写入的 canvas 行内宽度不能成为网格项的最小宽度。 */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
  padding: 14px 18px 14px;
}
.chart-box h2 { margin: 2px 0 12px; }
/* 尺寸归 Chart.js 的父容器，canvas 像素尺寸由库按设备像素比同步。
   强制 canvas CSS 尺寸会在容器扣除 padding 后把绘图结果再次拉伸。 */
.chart-frame { position: relative; height: 250px; min-width: 0; }
.chart-box canvas { display: block; }
@media (max-width: 1080px) { .charts { grid-template-columns: 1fr; } }

/* ── 表单控件 / 按钮 ── */
.filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 14px; }
/* ── 全局表单控件：按「排除非文本控件」收敛，**不再枚举 type**（2026-08-04）──
   旧写法是 `input[type=text|number|date]`，于是所有没被点名的框整个掉在网外：
   `password` / `email` / `search` / `tel` 全漏，**连 type 都没写的更漏**——财务页
   行内直编那个框（finance.html 的 pa_edit 宏，两张收款账户表每行调 2 次）实测长成
   UA 默认样子：**2px inset 斜面边框 / 0 圆角 / Arial 13.33px / 21px 高**，而同排的
   `.btn.btn-sm` 是 26px——方角斜面 Arial 小方框夹在两颗胶囊按钮中间，整行读起来是
   坏的。补一个 type 到枚举里只能治那一个框，下一个漏网的照样漏——所以
   反过来写：**默认全收，只排除掉不该套文本框外观的那几种**（勾选/单选/文件/滑块/
   取色/三种按钮型/图片按钮/隐藏域）。
   ⚠ 特异性刻意与旧写法逐字相等 (0,1,1)：`input`(0,0,1) + `:not([type=…])` 取参数里
   最高的 (0,1,0) + `:not(:where(…))` 被 :where 抹成 0。层叠关系因此一点没动，
   `.fld input` / `.login-card input` 这些同为 (0,1,1) 的局部规则仍靠「写在后面」赢。
   **别把 :where 去掉**：`:not(.search-panel input)` 会把本条抬到 (0,2,2)，反过来压死
   它们。2026-08-04 无头 Chrome 实测这一步的后果——`.fld input` 从 36px 高 / 8px 圆角 /
   13.5px 字塌成 30px / 10px / 12.5px，`.login-card input` 从 37px 塌成 30px；
   `.fld select` 不受影响（它匹配的是裸 `select` (0,0,1)，本来就压不过类选择器），
   于是 admin 表单里 input 和 select 一高一矮错开——这正是最难一眼看出的那种坏法。
   ⚠ `.search-panel input` 必须显式排除：它 `border:none` 只留一条下划线，套上 10px
   圆角会让下划线两端顺着圆弧卷边（实测见该规则上方那段）。它此前躲过纯属
   **markup 恰好没写 type** 的巧合——收敛之后巧合就没了，这里把它写成契约。
   守护 test_global_form_controls_are_not_type_enumerated。
   ⚠ textarea 只并进下面的外观块、**不并进高度块**：它是多行控件，套 `height:30px`
   会被压成一行。 */
input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"],
          [type="color"], [type="submit"], [type="button"], [type="reset"],
          [type="image"], [type="hidden"]):not(:where(.search-panel input)),
select {
  height: 30px;
  padding: 0 11px;
}
input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"],
          [type="color"], [type="submit"], [type="button"], [type="reset"],
          [type="image"], [type="hidden"]):not(:where(.search-panel input)),
select, textarea {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--input-bg);
  color: var(--text);
  font-size: 13.5px;
  font-family: var(--sans);
  outline: none;
  transition: border-color 0.15s;
}
input:focus, select:focus, textarea:focus { border-color: var(--strong); }
/* 聚焦环走主色：原先是 2px var(--line-hover)，跟静置态的 --line 只差一档明度，
   键盘 Tab 过去几乎看不出焦点在哪。3px 的半透明主色环 + 主色描边才是「这格在等你输入」。 */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  outline: none;
}
/* 键盘聚焦可见环（仅键盘触发，鼠标点击不出现）：实色描边，贴合「1px 描边、零阴影」语汇 */
.btn:focus-visible, .filters button:focus-visible, .tab:focus-visible,
.theme-toggle:focus-visible, .focus-act button:focus-visible,
.search-entry:focus-visible, .nav-toggle:focus-visible,
.sidebar li a:focus-visible {
  outline: 2px solid var(--btn-border); outline-offset: 2px;
}
input::placeholder { color: var(--faint); }
input[type="checkbox"] { accent-color: var(--strong); }
label { color: var(--muted); font-size: 13px; }

/* 「复制命令」按钮的**最终兜底**（dashboard.js 里 .copy-cmd 的处理器建的元素）：
   两条复制路（navigator.clipboard → execCommand）都失败时，就地展开成只读输入框、
   内容已全选，让人 Cmd+C 手拿。等宽是因为它装的是命令行——空格与短横线要数得清。
   现在所有入口都是安全上下文，复制失败是边角情形；但它一旦出现，这个框就是命令唯一的
   出路（命令只活在 data-cmd 里），所以样式不能退化。

   ⚠ 位置与写法都是被逼出来的，别"顺手整理"回声明区去（2026-08-04）：
   ① 它没写 type，于是被上面那条全局 input 规则命中（收敛成排除式枚举之后才命中的，
      此前靠"没写 type"躲过）。裸类 `.cmd-manual-copy` 是 (0,1,0)，输给全局的 (0,1,1)，
      于是 font-family 被 var(--sans) 顶掉——真渲染实测：等宽塌成 sans、
      22px 高 / 11.5px 字 / 3px 8px 内边距 变成 30px / 12.5px / 0 11px。
      一串要逐字符核对的命令用比例字体渲染，把"命令可粘可跑"这件事直接抵消掉。
   ② 写成 `input.cmd-manual-copy` 提到 (0,1,1) **还不够**——与全局同分时后写的赢，
      而声明区在全局块**之前**。原位加 `input.` 前缀实测仍然输，必须连位置一起搬。
   守护 test_manual_copy_box_wins_the_mono_declaration（真渲染量 computed style，
   静态断言看不见层叠——本文件到处都在说的那条边界，这处正是它当场复发的地方）。 */
input.cmd-manual-copy {
  width: 340px; max-width: 100%;
  /* height 是初始值 = 「没写过 height」，写出来只为把全局那条 30px 顶掉。
     原先它靠内容撑高（实测 22px），别改成写死像素——字号一动就对不上了。 */
  height: auto;
  padding: 3px 8px;
  border-radius: 10px;
  background: var(--input-bg);
  border: 1px solid var(--line);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11.5px;
}

/* 默认按钮：淡底实心（--surface-2 面）+ --line 收边。
   ⚠ 这条描边不是装饰，它是按钮在表格行里**唯一**的轮廓：`.btn` 的面与
   `tbody tr:hover` 的面是**同一个变量**（--surface-2）。鼠标划过表格行时行内按钮的
   面对比恰好 1.00:1（深色 #2a3140 压 #2a3140、浅色 #eef2f8 压 #eef2f8），按钮形状
   整个消失、只剩浮在行上的字——2026-08-03 无头 Chrome 实测确认 /shipped 那颗 ✓
   「已跟进」就是这样没的（同排的 WhatsApp 按钮还在，因为 .wa-btn 自带绿边框）。
   影响面（审查在真页面上数的）：/shipped 136 个行内按钮、/recall 103 个、/finance 89 个。
   三种底上的**面差**（深/浅）：页面底 --bg 1.32/1.03、卡片面 --surface 1.15/1.12、
   行 hover --surface-2 1.00/1.00——**三种都靠面撑不住，得有边**。
   补上 --line 之后的**边差**（描边与底、描边与面取更弱的那侧）：深 1.16 / 浅 1.10，
   三种底一致（描边与按钮面都不随容器变）；真渲染因抗锯齿略低一档，实测行 hover 上
   深 1.13 / 浅 1.09。数字薄，但薄和没有是两回事——守护测试
   test_button_keeps_a_visible_edge_on_every_bed 把下限钉在 1.05。
   ⚠ 边框宽度恒为 1px（连 .btn-primary / .btn-danger 那种透明边的变体也是）：按钮参与
   .dlg-actions / .card>form 这类 flex 行的高度对齐，写 none 会矮 2px、和同排的
   .filters button（仍有 1px 线）错半档。
   三档按钮层级：主操作=主色实心 / 默认=淡底实心+细边 / 筛选=透明+细线 ghost。 */
.btn {
  height: 30px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--strong);
  font-size: 13.5px;
  font-weight: 600;
  font-family: var(--sans);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.btn:hover:not(:disabled) {
  background: var(--surface-3);
  border-color: var(--line-hover);
  color: var(--strong);
}
/* 禁用态「退成空壳」：抽掉面、留住边——要让人看出「这里有个按钮，只是现在不能点」，
   一点边界都没有就只像一段灰字了。
   ⚠ 「比常态淡一档」落在**面**上，不落在线上：常态是「面 + 边」，禁用只剩「边」，
   再加文字降到 --faint，层级已经拉开，边不必也跟着退。
   线阶梯上确实还有更淡的 --line-soft，但它撑不住：实测 1.04:1（浅色页面底）/
   1.13:1（白卡）/ 1.01:1（行 hover 底），压根划不出边界——那正是这次要修的病，
   不能换个地方再犯一遍。再往上取 --line-hover（浅色 1.47 页面底 / 1.61 白卡）
   反而比常态还抢眼，且与 ghost 的 .filters button（透明底 + --line 边，那是
   **能点**的）撞脸。所以停在 --line。
   WCAG 对禁用控件豁免，所以这里的判据是「看得出是个按钮」，不是 4.5:1。 */
.btn:disabled {
  background: transparent;
  border-color: var(--line);
  color: var(--faint);
  cursor: not-allowed;
}
/* ghost 按钮（筛选等次级）：透明底 + 1px --line */
.filters button {
  height: 30px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  font-family: var(--sans);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.filters button:hover { border-color: var(--btn-border); color: var(--text); }
/* 危险按钮：淡红底 + 红字，不再是「实色红描边线框」——线框版在一排按钮里像颗警报灯，
   而财务页一屏里有六个（删除账单独有 / 补录账单 / 勾选回款 / 补勾到账 …），六颗灯一起亮
   反而谁都不突出。淡底一样一眼可辨，且与旁边 --surface-2 的默认按钮是**色相**之差，
   不靠亮度硬拼。
   ⚠ 它与 .btn 同 specificity (0,1,0)，靠**写在后面**赢，所以必须自带 background——
   只写 color 会被 .btn 那条淡底盖住（.btn-primary 是另一回事：(0,2,0) 提权，写在
   .btn 前面照样赢）。
   ⚠ 第二个参数是 **var(--surface) 而不是 transparent**：底半透明的话，同一个按钮在
   卡片（--surface）里和在危险确认弹窗（dialog.confirm 是 --surface-2）里合成出的红
   深浅不同——弹窗那一档实测 hover 只有 4.10:1（而「确认执行」正是全站最该看清的
   那颗按钮）。混进 --surface 之后底色不透明、与容器无关，两处一模一样。
   ⚠ 百分比是量出来的不是拍的：同色墨压同色淡底，底越浓字越糊。实测
   12% → 浅 5.05 / 深 5.25，18% → 浅 4.61 / 深 4.63，再往上 20% 就掉到 4.5 以下。
   所以 hover 的「更浓一点」只到 18% 为止，剩下的加强感交给同时浮现的描边——
   **别把 hover 底调到 20% 以上**。 */
.btn-danger {
  border-color: transparent;
  background: color-mix(in srgb, var(--danger) 12%, var(--surface));
  color: var(--danger);
}
.btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 18%, var(--surface));
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--danger);
}

.tab {
  display: inline-block;
  padding: 5px 15px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.4;
  transition: border-color 0.15s, color 0.15s;
}
.tab:hover { color: var(--text); text-decoration: none; border-color: var(--btn-border); }
.tab-on {
  background: var(--btn-bg);
  border-color: var(--btn-border);
  color: var(--strong);
  font-weight: 600;
}

/* ── 状态分布横条 ── */
.bar-row { display: flex; align-items: center; gap: 8px; margin: 5px 0; font-size: 12.5px; }
.bar-list { display: grid; grid-template-columns: max-content minmax(3rem, 1fr) max-content; gap: 5px 8px; }
.bar-list .bar-row { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; margin: 0; }
.bar-label { color: var(--muted); white-space: normal; overflow-wrap: anywhere; }
.bar { flex: 1; height: 7px; border-radius: 999px; background: var(--bar-track); overflow: hidden; }
.bar i { display: block; height: 100%; border-radius: 999px; }
.bar-ok { background: var(--ok); }
.bar-accent { background: var(--accent-bar); }
.bar-warn { background: var(--warn); }
.bar-danger { background: var(--danger); }
.bar-gray { background: var(--faint); }
/* 这一列上下成行（物流状态分布 / CRM 占比），确实需要按位对齐——但对齐该由
   tabular-nums 负责，不该拿等宽字体当对齐工具：等宽连中文与百分号一起改掉，
   代价远超收益。sans + tabular-nums 是同样的对齐、正常的字形。 */
.bar-val { flex: 0 0 auto; text-align: right; white-space: nowrap; font-family: var(--sans); font-size: 12px;
           font-variant-numeric: tabular-nums; }
/* 百分比独立成一列；按 100.0% 的字符数留位，条形轨道与数值右边缘均对齐。 */
.bar-pct { text-align: right; white-space: nowrap; }
.share-meter { display: grid; grid-template-columns: minmax(3.5rem, 1fr) max-content; align-items: center; gap: 8px; min-width: 15ch; }
.share-meter > .bar-pct, .share-meter > .bar-val { min-width: 6ch; }

/* （.progress 不定长进度条 2026-10-15 随「内容与 SEO」页的 GSC 手动刷新卡一起删除——它是唯一用户。
   任务阶段状态文字刻意没有样式：原先复用 .chip.stale 的 pulse 闪烁，2026-08-03 随呼吸动画取消。） */

/* ── 「最后事件」：6px 语义色圆点（红/黄，纯色不发光）+ 纯文字，无底色 ──
   选择器用 .badge.ev-pill 压过 .badge.<color> 的 tone 底色（模板类名是
   "badge <color> ev-pill"，单 .ev-pill 特异性不够，底色会漏出来） */
.badge.ev-pill {
  display: inline-block;
  margin-left: 0;
  padding: 0;
  border-radius: 999px;
  border: none;
  background: none;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.55;
  white-space: normal;       /* 长事件文案允许换行 */
  color: var(--muted);
}
.badge.ev-pill.red { color: var(--danger); }
.badge.ev-pill::before { display: none; }  /* ev-pill 用独立 .ev-dot 色点，不要 badge 默认方色钉 */
.ev-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: 1px;
}
.ev-dot-red { background: var(--danger); }
.ev-dot-yellow { background: var(--warn); }

/* ── 运行输出 / 文档（终端框两个主题都保持深底）──
   ⚠ .run-box 走**卡片档**只对「独立成块」的用法成立；模板里那几处（actions.html:16 /
   finance.html:18 的任务状态卡；seo.html 那处 2026-10-15 已撤）**都嵌在 .card 里**，由上面
   `.card .run-box` 降到条幅档（(0,2,0) 压得住这条 (0,1,0)）。三处全关在 {% if job %}
   后面、只有跑过任务才渲染，所以浏览器全页扫描看不到它们——清单见 `.card table`
   上方那段。 */
.run-box {
  background: var(--runbox-bg);
  color: var(--runbox-text);
  border: 1px solid var(--line-soft);
  padding: 12px 14px;
  border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.7;
  white-space: pre-wrap;
  max-height: 340px;
  overflow: auto;
}
/* 走**卡片档**：它跟 .card / .run-box 一样是不透明 --surface 面上的容器（只是里面
   装的是长文），不是贴在流里的提示条。2026-08-03 之前它是「--surface 底 + 边框 +
   圆角 + 一条阴影都没有」，是 .chip 之外最后两个平着的 surface 容器之一。 */
.doc-body {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 28px 36px;
  max-width: 900px;
  line-height: 1.8;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95);
}
.doc-body h1, .doc-body h2, .doc-body h3 { color: var(--strong); font-family: var(--sans);
  letter-spacing: 0; text-transform: none; }
.doc-body h2 { display: block; font-size: 17px; }
.doc-body h2::before { display: none; }
.doc-body h3 { font-size: 14.5px; }
/* 行内代码走 6px（同 .search-entry kbd / .secret code 这一档「行内等宽小硬块」），
   不走 999px 胶囊：① 胶囊档的语义是「短元素交互载体」，成员全是 chip/btn/tab，
   `python3 scripts/push_prices.py` 这种长命令串做成半圆会被读成可点标签；
   ② 它只有 16.5px 高，999px 被钳到 8.25px 有效圆角，比 19.2px 高的 kbd 还圆，
   视觉权重倒挂；③ 同家族的 .doc-body pre 是 10px，唯独它走满圆很突兀。 */
.doc-body code {
  font-family: var(--mono);
  font-size: 12.5px;
  background: var(--tone-gray);
  border-radius: 6px;
  padding: 1px 5px;
}
.doc-body pre {
  background: var(--runbox-bg);
  color: var(--runbox-text);
  border: 1px solid var(--line-soft);
  padding: 12px 14px;
  border-radius: 10px;
  overflow-x: auto;
}
.doc-body pre code { background: none; padding: 0; }
.doc-body table { font-size: 13px; }
.doc-body blockquote {
  margin: 0;
  padding: 2px 16px;
  border-left: 3px solid var(--btn-border);
  color: var(--muted);
}

/* ── 占位页 ── */
.placeholder { margin-top: 100px; text-align: center; color: var(--muted); }

/* ── 入场编排（唯一的页面级动效；stale 指示灯脉冲是唯一的常驻动效）── */
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.content > * { animation: rise 0.45s cubic-bezier(0.22, 0.7, 0.3, 1) backwards; }
/* 点运行按钮回跳本页、任务跑完自动刷新（base.html <head> 判好打 data-soft-nav）：不要入场动画——每块从透明淡入上浮，
   就是用户说的「画面闪一下」（2026-10-16）。正常从别的页点进来照旧有 */
html[data-soft-nav] .content > * { animation: none; }
.content > *:nth-child(1) { animation-delay: 0.02s; }
.content > *:nth-child(2) { animation-delay: 0.06s; }
.content > *:nth-child(3) { animation-delay: 0.10s; }
.content > *:nth-child(4) { animation-delay: 0.14s; }
.content > *:nth-child(5) { animation-delay: 0.18s; }
.content > *:nth-child(6) { animation-delay: 0.22s; }
.content > *:nth-child(n+7) { animation-delay: 0.26s; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ── 滚动条：12px 轨道里的细胶囊（透明边内缩，不贴边贴角）──
   滑块靠 `border: 3.5px solid transparent` + `background-clip: content-box` 内缩：
   轨道占 12px 好抓，实际画出来只有 12 − 3.5×2 = 5px 的一根胶囊，两侧各留 3.5px 空。
   width 管竖条、height 管横条，**两条都要跟着边宽走**：只改一条就会出现「竖条
   是细胶囊、横条还是老粗条」，而横条恰恰是最少被看见、最容易漏的那根。
   ⚠ 真渲染出来的游程按元素浮动，合计恒为 12px：2026-08-03 无头 Chrome 逐像素实测
   六处容器 × 深浅双主题，页面竖条 / 侧栏 / .table-scroll / .run-box / .doc-body pre
   都是 3|6|3，/landing 的 .tbl-wrap 是 4|6|2。这是**逐个元素的亚像素落位**——
   .doc-body pre 与 .tbl-wrap 同为横条却落位不同，「横轴按轴取整所以比竖轴粗 1px」
   那套说法站不住。守护测试的 3–6px 区间把这些落位都圈得住。
   两条别改的地方：
   ① 刻意不写 scrollbar-width——Chrome 认了那个标准属性就**整个忽略**下面这些
      伪元素，胶囊直接消失。要给 Firefox 兜底只能包进
      `@supports not selector(::-webkit-scrollbar)`（那个块按定义碰不到 Chrome，
      守护测试 test_no_scrollbar_width_property 单独放行它，照这句写不会撞墙）。
   ② hover 那条必须把 background-clip 再写一遍：background 是简写，会把 clip
      重置回 border-box，只换颜色的话滑块一 hover 就胀回贴边粗条。
   颜色走变量，**除了一族容器**深浅两个主题都自动成立：--runbox-bg 是全站唯一
   不随主题变的容器底（终端输出两个主题下都像真终端），于是浅色主题下它仍是深底、
   而 hover 的 --muted 跟着浅色主题变深 → 那一格越 hover 越看不见。救法在文件末尾
   浅色补丁区那条 `.run-box / .doc-body pre` 覆盖上（搜「恒深底容器」）。
   除那一条外不留局部覆盖（整改前 .table-scroll / dialog.confirm .dlg-scroll /
   浅色补丁区各有一份只改 background 的覆盖，四条已收掉；它们只覆盖颜色不覆盖
   形状，坏起来最不显眼）。
   守护：tests/test_dashboard_visual_system.py 的五条 scrollbar 测试。 */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-hover);
  border-radius: 999px;
  border: 3.5px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* ── 全局搜索（Cmd+K 命令面板）── */
.search-entry {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 12px 12px;
  padding: 9px 13px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  color: var(--faint);
  font-size: 13px;
  font-family: var(--sans);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.search-entry:hover { border-color: var(--btn-border); color: var(--text); background: var(--surface-2); }
.search-ico { width: 15px; height: 15px; flex: none; }
.nav-ico { width: 16px; height: 16px; flex: none; opacity: 0.75; }
.sidebar li.active .nav-ico, .sidebar li a:hover .nav-ico { opacity: 1; }
.page-head h1 { display: flex; align-items: center; gap: 10px; }
.page-ico { width: 20px; height: 20px; flex: none; opacity: 0.85; }
.nav-toggle .search-ico { width: 17px; height: 17px; vertical-align: -3px; }
.search-entry kbd {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--faint);
}
.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 12vh;
}
.search-overlay[hidden] { display: none; }
.search-panel {
  width: min(560px, 92vw);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
/* ⚠ 这个输入框**刻意不写 border-radius**（默认值就是 0，写 0 会被守护测试当成
   零圆角残留逮住）：它 border:none 只留一条 border-bottom，没有可裁剪的盒子。
   给它画圆角，Chrome 会让那条 1px 下划线在两端顺着圆弧向上翘起并渐隐——
   Cmd+K 搜索框的下划线两端从平直变成卷边毛头（2026-08-03 无头 Chrome 6× 渲染
   逐像素比对实证：745 个像素变化全部落在左右各约 7.3px 的弧上）。
   圆角由外层 .search-panel（12px + overflow:hidden）统一给。
   ⚠ 它此前之所以拿不到全局那 10px，靠的是**这段 markup 恰好没写 type**（旧全局
   规则枚举 `input[type=…]`）——2026-08-04 把全局收敛成「默认全收」之后那个巧合
   就不成立了，改由全局选择器里的 `:not(:where(.search-panel input))` 显式排除。
   **这里与全局那条是一对，改任一边都要看另一边**：全局那条把排除去掉，这个框
   立刻拿到 10px 圆角，而本段所有守护（_NO_RADIUS_BY_DESIGN 查的是「压根没声明
   border-radius」）仍会全绿——静态文本断言看不见层叠。 */
.search-panel input {
  width: 100%;
  height: 48px;
  border: none;
  border-bottom: 1px solid var(--line-soft);
  background: transparent;
  font-size: 15px;
  padding: 0 16px;
  outline: none;  /* 它被全局那条显式排除在外，拿不到全局的 outline:none，自己写一份 */
}
.search-panel input:focus { border-color: transparent; border-bottom-color: var(--btn-border); }
.search-results { max-height: 52vh; overflow-y: auto; padding: 6px; }
.sr-group {
  padding: 9px 12px 4px;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.5px;
  color: var(--faint);
}
.sr-item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 9px 12px;
  border-radius: 8px;
  color: var(--text);
  transition: background 0.15s;
}
.sr-item:hover, .sr-item.sr-active { background: var(--nav-active-bg); text-decoration: none; }
.sr-item .note { white-space: nowrap; }
.sr-empty { padding: 20px; text-align: center; color: var(--muted); }

/* ── toast 通知 ── */
#toastRoot {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 90;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.toast {
  padding: 11px 18px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text);
  font-size: 13px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.5);
  animation: rise 0.3s cubic-bezier(0.22, 0.7, 0.3, 1);
}
.toast.ok  { border-color: var(--ok); }
.toast.err { border-color: var(--danger); }

/* ── 危险操作确认对话框 ── */
dialog.confirm {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--text);
  padding: 22px 24px;
  max-width: 430px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
}
dialog.confirm::backdrop { background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(3px); }
dialog.confirm h3 { color: var(--danger); font-size: 14px; }
/* 弹窗内长清单：限高内滚，右侧常显滚动条（thead 钉在容器顶） */
dialog.confirm .dlg-scroll {
  max-height: 48vh;
  overflow-y: auto;
  margin: 12px 0;
  border: 1px solid var(--line);
  border-radius: 8px;
}
/* ⚠ 同 .table-scroll table：必须保持单行写法，守护测试按行认豁免身份（详见那条注释）。 */
dialog.confirm .dlg-scroll table { border: none; border-radius: 0; box-shadow: none; }

/* 库存预警型号 tab 的角标：红=缺货 / 黄=低库存（与 h2 徽章同序）/ 绿=无预警。
   间距：文本→首角标 9px、角标之间 5px（2026-07-05 用户嫌挤） */
.tab .tab-cnt {
  font-family: var(--sans); font-size: 12px; font-weight: 700;
  padding: 1px 6px; margin-left: 9px; letter-spacing: 0;
  border-radius: 999px;   /* 装在已是胶囊的 .tab 里的计数角标，跟着走满圆 */
}
.tab-cnt + .tab-cnt { margin-left: 5px; }
.tab-cnt.yellow { background: var(--tone-yellow); color: var(--warn); }
.tab-cnt.red    { background: var(--tone-red);    color: var(--danger); }
.tab-cnt.ok     { background: var(--tone-green);  color: var(--ok); }

/* 预警合并表（缺货+低库存一张表，2026-07-05 用户定调）：
   缺货行淡红底+红 0，低库存行黄色剩余数——h2 红黄徽章即图例 */
.row-oos td { background: var(--tone-red); }
.cnt-oos { color: var(--danger); font-weight: 700; }
.cnt-low { color: var(--warn); font-weight: 700; }

dialog.confirm .sub { color: var(--text); font-size: 13.5px; line-height: 1.7; margin: 10px 0 6px; }
dialog.confirm .dlg-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; }

/* ── 操作中心锚点直达高亮（库存预警等跳转来源）── */
.card:target { border-color: var(--strong); }

/* ── 操作流水批次（总览）── */

/* ── 本周关注：处理动作 ── */
.focus-act { display: flex; gap: 6px; }
.focus-act form { display: inline; }
.focus-act button {
  height: 24px;
  padding: 0 9px;
  font-size: 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s, color 0.15s;
}
.focus-act button:hover { border-color: var(--btn-border); color: var(--text); }
tr.focus-done td { color: var(--faint); }

/* ── 主题切换（sidebar 底部）：一颗按钮，明暗两态直切 ──
   图标显示当前态（太阳 = 浅色 / 月亮 = 深色），点击翻转；从没选过时按系统外观落地（见 base.html） */
.theme-wrap { position: relative; margin: 0 12px 10px; }
.theme-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-family: var(--sans);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.theme-toggle:hover { border-color: var(--btn-border); color: var(--text); }
/* 图标为内联 SVG（currentColor 随文字色，主题切换不反色） */
svg.theme-icon { width: 13px; height: 13px; flex-shrink: 0; display: block; }
.theme-icon-slot { display: inline-flex; width: 13px; height: 13px; flex-shrink: 0; }
.theme-icon-slot svg { display: none; }
.theme-icon-slot[data-mode="light"] svg.ti-light,
.theme-icon-slot[data-mode="dark"] svg.ti-dark { display: block; }

/* ════════════════════════════════════════════════════════════════
   浅色主题：变量整体覆盖（badge/tone/按钮等全部经由变量自动成立；
   run-box / doc-body pre 保持深底——终端输出两个主题下都像真终端）
   ════════════════════════════════════════════════════════════════ */
html[data-theme="light"] {
  color-scheme: light;
  /* 浅灰蓝底 + 白卡（雾蓝石墨浅色配套；主色降一档保证白底上的对比） */
  --bg: #f2f5fa;            /* 浅灰蓝底（不是纯白，白卡才浮得起来） */
  --surface: #ffffff;       /* 卡片面纯白 */
  --surface-2: #eef2f8;     /* 第二层浅灰蓝（hover/thead） */
  --surface-3: #dde4ee;     /* 第三层（滚动条 hover 等） */
  --sidebar-bg: #eaeff7;    /* 侧栏比页面底再深一点点，压住不抢主区 */
  /* 线阶梯：同深色思路，深度主要交给阴影与明度分层，线只做收边。
     实测 vs 白卡：主描边 1.23:1、hover/按钮边 1.44:1（黑金时代 1.78 / 2.70:1）。
     白底上尤其淡，输入框轮廓靠它——阴影没到位之前控件会显得飘 */
  --line: #e2e8f1;          /* 主描边（白卡上的收边） */
  --line-soft: #edf1f7;     /* 弱描边/行线 */
  --line-hover: #c3cddb;    /* hover/ghost 边 */
  --text: #232b38;          /* 主文字近黑冷调 */
  --muted: #5f6b7d;         /* 表格正文用色（白底对比达 AA） */
  /* 最弱文字。2026-08-04 从 #8896a8 压深一档——那个值是换色板时随手带进来的，
     浅色下比整改前**更浅**，而全站没有任何守护看得见它：对比度守护只遍历
     `color: var(--语义色)` 的规则，--faint 不是语义色，整个在 _INK_BEDS 覆盖面之外。
     影响面（真渲染扫 23 路由 × 深浅两态数出来的）：2511 个元素 / 42 类低于 AA，
     全部只源于这一个变量——.note 独占 728 个（14 个页面里每张图表下的解释），
     侧栏分组标签 + 搜索占位符 + ⌘K 是 23 个页面全都有的 2.61:1。
     三种床的实测对比（白卡 --surface / 页面底 --bg / 侧栏 --sidebar-bg）：
       整改前 #8a8472（放到今天这三种床上算，隔离出变量本身的影响）  3.73 / 3.42 / 3.23
       上一版 #8896a8                                              3.01 / 2.76 / 2.61
       现值   #798595                                              3.75 / 3.43 / 3.25
     ⚠ **刻意不压到满 AA 4.5**：那要走到 #6d7886，它与 --muted 的 ΔE76 会从 10.4 掉到
     5.8，两档弱化文字糊成一档——「最弱」与「次级」分不出来，等于用可读性换掉层次。
     同一条理由这轮早些时候否决过给 .tally.smsed 用 --blue（ΔE 只有 5.7/2.1）。
     判据钉在 test_faint_is_no_paler_than_before_the_refresh：三种床都不许低于整改前，
     **不是**钉死 4.5——把绝对阈值写进守护会逼下一个人做上面那笔亏本交易。 */
  --faint: #798595;         /* 最弱文字 */
  --strong: #131a26;        /* 数据/标题强调=近黑（主色另用 --accent） */
  /* 雾蓝主色（浅色：整体降一档，白底上才够读）
     ⚠ 白字压主色是主操作按钮的常规文本（13–14px，不吃大文本 3:1 放宽），
     必须过 WCAG AA 4.5:1。#3f82cf 只有 3.96:1，2026-08-03 沿同色相压到
     #3477c4（色相 212.08° 原样守住，白字实测 4.58:1）。守护测试
     tests/test_dashboard_visual_system.py::test_accent_on_meets_aa 钉死。 */
  --accent: #3477c4;        /* 主操作填充 / 当前位 / KPI 强调 */
  /* 主色做文字/描边时再降一档（随主色同步压深，免得两者糊成一色）。
     2026-08-03 再降 2.5 pt 明度：当前导航位改成「主色淡底 + 主色字」之后，它得压在
     --nav-active-bg 上（主色 13% 铺在 --sidebar-bg 上），#2a66ab 在那儿只有 4.34:1。 */
  --accent-ink: #2760a1;
  --accent-on: #ffffff;     /* 主色底上的文字（白字压蓝） */
  /* 语义色（浅色版：压暗保证白底可读，饱和度不丢）
     ⚠ 2026-08-03 Task 6 整体压深一档，判据是**压在自己的 --tone-* 淡底上过 AA 4.5:1**
     （四个色的主要身份是文字墨色，不是色块——见深色块里那段）。旧值实测（无头 Chrome
     真渲染取像素，不是纸面算）：
       --warn  #a8742a  .tab-cnt.yellow 3.19 / .tag.warn 3.39 / .diff-banner 3.70
       --ok    #2f9268  .tab-cnt.ok 3.06 / .ev.login 3.06 / .diff-banner.applied 3.54
       --danger #c14f45 .tab-cnt.red 3.74 / .ev.deny 3.74 / .cnt-oos 3.74
       --blue  #2c6cb5  .ev.run 4.13 / .recon-reminder 4.20
     全部低于 4.5。压深沿各自色相与饱和度原样下移明度（H/S 一个不动，只降 L），
     压到「自己的淡底上 ≥4.6」为止——再浅一点点就掉到 4.4 出头。
     ⚠ **别再把 --tone-* 的 rgb 与这四个值「对齐同步」**：淡底刻意还留在压深前那一档
     （见下面 --tone-* 那段）。底浅一档、墨深一档，正是这套配色能同时「看得出颜色」
     和「读得清字」的原因。
     ⚠ 说清楚这条的分量，免得被当成硬约束或被当成随口一说：同步回去**不会**当场
     跌破 AA——实测四族会从 4.67/4.68/4.81/4.71 降到 4.54/4.53/4.72/4.67，还在线上，
     余量却只剩一个进位。所以守护测试**咬不住**它（2026-08-03 变异测试实证：把
     --tone-yellow 同步成 #855c21，那条遍历式对比度守护照样全绿）。它是靠这段注释
     传下去的约定，不是被钉住的不变量——真要动请连同这四个墨色一起重新量。 */
  --ok: #247151;            /* 完成/成功 */
  --warn: #855c21;          /* 警告/待办 */
  --danger: #a84138;        /* ⚑ 财务红警（语义保留） */
  --blue: #2963a7;          /* 链接/运输中冷蓝 */
  --input-bg: #ffffff;
  --thead-bg: transparent;
  --btn-bg: transparent;
  --btn-bg-hover: rgba(35, 43, 56, 0.05);
  --btn-border: #cfd8e4;
  --nav-active-bg: rgba(52, 119, 196, 0.13);  /* 当前导航位=主色淡底 */
  --total-row-bg: rgba(35, 43, 56, 0.04);
  --chart-fill: rgba(52, 119, 196, 0.12);
  --bar-strong: #3477c4;
  --bar-soft: #247151;      /* 恒等于 --ok（同深色那份的约定），随它一起压深 */
  --bar-track: #e6ebf3;
  --accent-bar: #3477c4;    /* 恒等于 --accent */
  --chart-text: #8896a8;
  --chart-grid: rgba(35, 43, 56, 0.07);
  --runbox-bg: #1c2330;
  --runbox-text: #c3ccd9;
  /* 淡底：rgb **刻意停在语义色压深前那一档**（#2f9268 / #a8742a / #c14f45 / #2c6cb5），
     不跟着上面四个新值走。淡底要浅、墨色要深，两者拉开明度差才有 AA。
     这不是没改干净，是分工——理由与「同步回去要付多少余量」写在上面语义色那段。 */
  --tone-gray: rgba(35, 43, 56, 0.06);
  --tone-green: rgba(47, 146, 104, 0.13);
  --tone-blue: rgba(44, 108, 181, 0.13);
  --tone-yellow: rgba(168, 116, 42, 0.13);
  --tone-red: rgba(193, 79, 69, 0.11);
}

/* 少量硬编码场景重绘（阴影减淡、滚动条换浅灰）
   .login-card 2026-08-03 并进来：它与 .search-panel / dialog.confirm 同属浮层档
   （理由写在 .login-card 那条上方）。
   ⚠ **.toast 只在浅色下跟它们同值，深色下比它们轻一档**（2026-08-04 订正：此前这
   两处注释各说一半——这里写「跟这三位同属浮层档」而并列的是四条，测试那边的
   _DEPTH_TIERS 又写「.toast 比这三位轻一档」）。CSS 事实：深色 .toast 是
   `0 10px 34px rgba(0,0,0,.5)`，浮层档那三位是 `0 24px 70px rgba(0,0,0,.55)`，
   确实轻一档；到了浅色这条补丁，四条共用同一个值，「轻一档」在这里不成立。
   刻意如此——浅色的阴影本来就浅，再分一档就都看不见了；.toast 因此不进
   _DEPTH_TIERS 的浮层档名单（同档同值那条断言会逼着谁将就谁）。 */
html[data-theme="light"] .search-panel,
html[data-theme="light"] .toast,
html[data-theme="light"] dialog.confirm,
html[data-theme="light"] .login-card {
  box-shadow: 0 18px 56px rgba(35, 43, 56, 0.20);
}
/* 卡片/表格那套柔和阴影的浅色版：**绝不能沿用深色的 rgba(0,0,0,…)**——纯黑压在
   白卡上会把边缘染成灰脏一圈，读起来像脏印子而不是投影。这里换成与 --text 同源的
   蓝墨 rgba(35,43,56,…) 并整体减淡，白底上才是「浮起来」而不是「蒙了层灰」。
   守护测试 test_light_theme_shadows_are_not_pure_black。
   两档与深色一一对应（见 .card 上方那段分档说明）：**漏掉哪个选择器，那个元素在
   浅色下就顶着深色那条纯黑阴影**，在白底上是一圈灰脏印子——
   test_light_theme_covers_both_depth_tiers 逐个钉。
   深色 2026-08-03 加强过一轮，浅色刻意没跟着动：它本来就有 26 级落差、够看了，
   跟着加只会把白底压脏。 */
/* ── 卡片档 ── */
html[data-theme="light"] .card,
html[data-theme="light"] table,
html[data-theme="light"] .table-scroll,
html[data-theme="light"] .tbl-wrap,
html[data-theme="light"] .chart-box,
html[data-theme="light"] .run-box,
html[data-theme="light"] .empty,
html[data-theme="light"] .newpanel,
html[data-theme="light"] .doc-body {
  box-shadow: 0 1px 2px rgba(35, 43, 56, 0.06), 0 10px 26px -18px rgba(35, 43, 56, 0.30);
}
/* 卡片 hover 上浮的浅色对家（深色那条在 .card:hover 上，是纯黑）。同样换蓝墨：
   白底上照抄深色那条纯黑会在卡下缘糊一道灰印。
   ⚠ 几何与上面那条浅色静置**逐字相同**（1px/2px 与 10px/26px/-18px），只抬 alpha
   ——2026-08-04 修：此前是 `0 2px 6px … 0 16px 36px -16px`，靠摊大 blur/spread 拿
   深度，拖尾 33px 是静置 16px 的两倍多，撞的正是 .card 上方明令禁止的那条路。
   实测见 .card:hover 上方那张表。 */
html[data-theme="light"] .card:hover {
  box-shadow: 0 1px 2px rgba(35, 43, 56, 0.12), 0 10px 26px -18px rgba(35, 43, 56, 0.50);
}
/* 内层表的「深度归容器」在浅色下会被上面那条压回来：`.table-scroll table` /
   `.tbl-wrap table` 是 (0,1,1)，而 `html[data-theme="light"] table` 是 (0,1,2)，
   后者胜——2026-08-03 无头 Chrome 实测，深色下内层表算出来是 none（对的），浅色下
   又变回了那条淡蓝墨值。今天看不出来（外壳 overflow 把投影裁光了），但**规则写着
   一套、算出来另一套本身就是坑**：哪天外壳加了 padding，投影就冒进容器里面。
   这里用同样的层叠语言补回去 (0,2,2)，不动上面那条、也不用 !important。
   `dialog.confirm .dlg-scroll table` 不在名单里是因为它本来就是 (0,2,2)、压得住。 */
html[data-theme="light"] .table-scroll table,
html[data-theme="light"] .tbl-wrap table { box-shadow: none; }
/* ── 条幅档（同样弱一档：接触层保留、漫射层砍半并收紧扩散）──
   末四条是**卡片里的表 / 表壳 / 终端框**（2026-08-03 补，同日复核补 .run-box）：
   它们投在父卡的白面上，跟着卡片档会浮得比装着它们的卡还高，一并降到条幅档。
   理由、清单与实测见 .tbl-wrap table 下方那段。
   ⚠ `html[data-theme="light"] .card .run-box` 是 (0,3,1)，压得住上面卡片档那条
   `html[data-theme="light"] .run-box` (0,2,1)——顺序无关，靠权重赢。 */
html[data-theme="light"] .recon-alert,
html[data-theme="light"] .recon-reminder,
html[data-theme="light"] .secret,
html[data-theme="light"] .card table,
html[data-theme="light"] .card .tbl-wrap,
html[data-theme="light"] .card .table-scroll,
html[data-theme="light"] .card .run-box {
  box-shadow: 0 1px 2px rgba(35, 43, 56, 0.05), 0 6px 14px -12px rgba(35, 43, 56, 0.22);
}
/* 同深色那两条的道理，连数字都一样：`html[data-theme="light"] .card table` 与上面
   那条 `html[data-theme="light"] .tbl-wrap table` specificity 相等（都多一个属性选择器，
   相对关系不变），后写的赢 → 内层表被重新点亮。多带一个类压回去。 */
html[data-theme="light"] .card .tbl-wrap table,
html[data-theme="light"] .card .table-scroll table { box-shadow: none; }
html[data-theme="light"] .search-overlay { background: rgba(22, 26, 34, 0.32); }
html[data-theme="light"] dialog.confirm::backdrop { background: rgba(22, 26, 34, 0.34); backdrop-filter: blur(3px); }
/* ── 滚动条 hover 的唯一例外：浅色主题 + **恒深底容器**（2026-08-03 复核补）──
   滚动条颜色走变量、两个主题自动成立，只有这一格不成立：--runbox-bg 是全站唯一
   不跟主题走的容器底（#1c2330，终端输出两个主题下都像真终端），而滑块颜色是跟着
   主题走的。于是浅色下静置的 --line-hover #c3cddb 压在深底上有 9.81:1，hover 的
   --muted #5f6b7d 却只剩 2.92:1 —— **越 hover 越看不见**，方向跟其余三格（深色两
   族 + 浅色浅底）正好相反。换成 --surface-3 #dde4ee → 12.31:1，hover 才是更明显。
   （--surface-3 在两个主题的注释里本就写着「滚动条 hover 等」，这里是回到它的本职。）
   ⚠ 别改成 --runbox-text（#c3ccd9 → 9.73:1）：看着像「跟容器配套」，实际比静置的
   9.81:1 还低一丝，等于把这条覆盖写成空转。
   ⚠ 两族容器缺一不可：.run-box 是任务输出框（模板挂在 {% if cur %} 上，跑过一次
   任务后就长期常驻），.doc-body pre 是文档页的长代码块（横向滚动条）——它俩是仅有
   的两个吃 --runbox-bg 的容器。
   background 是简写会把 clip 重置回 border-box，所以照例把 background-clip 补一遍。
   守护 test_scrollbar_hover_gets_more_visible 按「四格都得越 hover 越明显」钉。 */
html[data-theme="light"] .run-box::-webkit-scrollbar-thumb:hover,
html[data-theme="light"] .doc-body pre::-webkit-scrollbar-thumb:hover {
  background: var(--surface-3);
  background-clip: content-box;
}

/* ════════════════════════════════════════════════════════════════
   移动端（≤900px）：sidebar 抽屉化 + 表格横滚 + 触控目标放大
   ════════════════════════════════════════════════════════════════ */
.mobile-topbar { display: none; }
.nav-scrim { display: none; }

@media (max-width: 900px) {
  .mobile-topbar {
    display: flex;
    align-items: center;
    gap: 12px;
    position: sticky;
    top: 0;
    z-index: 50;
    padding: 10px 14px;
    background: color-mix(in srgb, var(--sidebar-bg) 92%, transparent);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--line-soft);
  }
  .nav-toggle {
    width: 38px;
    height: 38px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
    color: var(--text);
    font-size: 16px;
    cursor: pointer;
  }
  .mobile-brand { font-weight: 600; letter-spacing: 1px; }

  /* sidebar → 左侧抽屉（默认收起，body.nav-open 滑出）
     这条 0 0 44px 是抽屉的环境投影（居中无偏移，四周一圈），**不是**被整改掉的那种
     装饰性发光——发光那族全是 6~7px、贴着 6~7px 的状态点画的，量级差一个数量级。 */
  .sidebar {
    position: fixed;
    top: 0; left: 0; bottom: 0;
    z-index: 70;
    height: 100dvh;
    transform: translateX(-105%);
    transition: transform 0.25s ease;
    box-shadow: 0 0 44px rgba(0, 0, 0, 0.55);
  }
  /* ⚠ 浅色补丁必须写在**这个媒体查询里面**：抽屉只在 ≤900px 存在，写到文件末的
     浅色补丁区会连桌面端那个根本没有阴影的 sidebar 一起染上，成了「只有浅色的
     桌面侧栏才有阴影」。同 --text 那套蓝墨 rgba(35,43,56,…)，纯黑压在浅底上发脏。
     2026-08-03 补：此前这里只有上面那条纯黑，浅色下 computed 就是 rgba(0,0,0,0.55)
     （800px 宽 + 浅色实测），是全站最后一处纯黑阴影逃逸。 */
  html[data-theme="light"] .sidebar {
    box-shadow: 0 0 44px rgba(35, 43, 56, 0.26);
  }
  body.nav-open .sidebar { transform: none; }
  .nav-scrim { position: fixed; inset: 0; z-index: 65; background: rgba(0, 0, 0, 0.55); }
  /* 三个遮罩里它是**唯一**没有浅色对家的（.search-overlay 与 dialog.confirm::backdrop
     早就有，都是 rgba(22,26,34,.32~.34) 那档蓝墨）。触发面窄——浅色 + ≤900px + 开着
     抽屉——但分支自己的论点就是「浅色不许用纯黑，白底上发脏」，遮罩不该有例外。
     浅色遮罩另外还得更淡：白底页面被压成 .55 会黑得像断电，跟另两个对齐到 .34。
     与 .sidebar 那条同理，**必须写在这个媒体查询里面**：.nav-scrim 只在 ≤900px 有形。
     守护 test_every_pure_black_background_has_a_light_counterpart（2026-08-04 补——
     此前「浅色不许纯黑」只守了 box-shadow 轴，background 轴一条都没守）。 */
  html[data-theme="light"] .nav-scrim { background: rgba(22, 26, 34, 0.34); }
  body.nav-open .nav-scrim { display: block; }

  .content { padding: 18px 14px 70px; }
  .page-head { flex-direction: column; align-items: flex-start; gap: 5px; padding-bottom: 12px; }
  /* 竖排页头里标题行也要拉满整行，胶囊才有地方（它的容器自身不计宽，不拉满就只剩 0） */
  .page-head-title { flex: none; align-self: stretch; }
  .page-head h1 { font-size: 19px; }
  .crumb-sep { margin: 0 6px; }
  .cards { grid-template-columns: 1fr; }
  .card .big { font-size: 22px; }

  /* 宽表格容器内横滚（display:block 容器化，模板零改动） */
  table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* 触控目标 ≥38px；输入字号 ≥16px 防 iOS 聚焦自动放大。
     ⚠ 这里 2026-08-04 跟着全局那条一起从枚举改成排除式。它原是
     `input[type=text|number|date]`，与全局犯的是同一个错——注释里那句「date 输入
     此前漏掉」正是给枚举打补丁的现场：补完 date，没写 type 的框（财务页行内直编）
     照样拿不到 38px 触控高，手机上只有 30px。
     ⚠ 排除项与全局那条必须一致：`.search-panel input` 有自己的 48px，而本条写在
     它后面、特异性又相等（都是 (0,1,1)，媒体查询不加权），不排除就会把 Cmd+K
     搜索框从 48px 压到 38px。`.fld input` / `.login-card input` 不用管——它们的
     `height: auto` 写在本条之后，照样赢。 */
  .btn, .filters button, select,
  input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"],
            [type="color"], [type="submit"], [type="button"], [type="reset"],
            [type="image"], [type="hidden"]):not(:where(.search-panel input)) {
    height: 38px;
  }
  /* ⚠ 这条 16px **对绝大多数输入框是失效的**，且不是本轮改坏的（2026-08-04 查明，
     baseline 同样如此）：它是 (0,0,1)，而上面全局外观块那条字号规则
     是 (0,1,1)，压不过。08-04 实测手机视口下 `input[type=text]` 拿到的是 12.5px
     （10-06 公共字号改为 13.5px，层叠关系保持原样），
     iOS 聚焦照样会自动放大——本轮把全局收敛之后，财务页那个框从「碰巧命中这条
     16px」变成和其它框一样的 12.5px，是**从例外回到大多数**，不是新引入的问题。
     真要修得让本条压过全局那条（会改动手机上所有输入框的字号与布局），
     不在本轮范围内，留给专门做移动端那一轮。 */
  input, select, .search-panel input { font-size: 16px; }
  .tab { padding: 7px 16px; }
  .sidebar li a { padding: 12px 13px; }
  .focus-act button { height: 38px; padding: 0 12px; }
  /* 抽屉导航打开时锁背景滚动（否则手指滚的是抽屉底下的内容） */
  body.nav-open { overflow: hidden; }
}

/* 表格行内小按钮（finance「核对排除」等）：全尺寸按钮塞行内太高 */
.btn-sm { height: 26px; padding: 0 10px; font-size: 12px; }

/* 任务最近一次结果摘要（_result.html job_result 宏） */
.job-result { margin-top: 6px; }
.job-result .jr-time { color: var(--faint); margin-right: 7px; font-variant-numeric: tabular-nums; }
.job-result .jr-bad { color: var(--danger); }
/* 摘要严重度配色：绿=顺利 黄=有警告 红=失败/严重警报（2026-06-12 用户需求） */
.job-result.jr-ok    { color: var(--ok); }
.job-result.jr-warn  { color: var(--warn); }
.job-result.jr-error { color: var(--danger); }
/* 已被之后的任务复查取代的旧结果：转灰不删（2026-10-07，账单对账复查勾选回款） */
.job-result.jr-superseded { color: var(--faint); }

/* ── 美化补充（2026-06-13）── */
.job-card { margin-bottom: 16px; }                /* 任务状态卡：与下方正文留一档分隔 */
/* 404/500 的大号码：单个展示数字，不与任何东西成列，等宽 + 4px 字距纯粹是旧军工风 */
.err-code { font-family: var(--sans); font-size: 64px; font-weight: 600; color: var(--faint); letter-spacing: 1px; }
/* 卡片区块紧邻卡片/图表区块时补一档间距（无 h2 承接的场景，如物流页）*/
section.cards + section.cards, section.cards + section.charts, section.charts + section.cards,
section.data-panels + section.cards, section.cards + section.data-panels,
section.data-panels + section.data-panels { margin-top: 28px; }
/* 概览本周卡指标行：大数字 + 小单位 + 同比 badge 基线对齐（替代数字/单位/百分比挤一行的拥挤）*/
.metric-row { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; margin: 10px 0 2px; }
.metric-row .big { margin: 0; }
.unit { font-size: 13.5px; color: var(--muted); font-family: var(--sans); letter-spacing: 0; margin-left: -3px; }
/* 对齐补充（2026-06-13）：月份输入统一宽度 */
input.w-month { width: 90px; }
select.w-month { width: 108px; }  /* select 比 input 多留下拉箭头宽度 */

/* 物流页专属两行筛选栏（不动全局 .filters；其余 4 页保持原状）2026-06-20 */
.filter-stack { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
/* 过滤器选中态：有值=主色高亮，未选=默认灰退后 */
.filter-stack select.filter-on, .filters select.filter-on { border-color: var(--accent); color: var(--accent); }
.filter-stack .row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.filter-stack .group-label {
  color: var(--muted); font-size: 12px; letter-spacing: .04em;
  white-space: nowrap; margin-right: 2px;
}
.filter-stack .spacer { flex: 1 1 auto; }   /* 行1 把「清空」推到右侧 */

/* ── 客户去重对比表 ── */
.dedupe-cluster { border-top: 1px solid var(--line); padding-top: 8px; margin-top: 8px; }
table.cmp { width: auto; border-collapse: collapse; margin: 6px 0; font-size: 12.5px; }
table.cmp th, table.cmp td { border: 1px solid var(--line); padding: 3px 8px; vertical-align: top; }
table.cmp thead th { color: var(--muted); font-weight: 600; }
table.cmp td:first-child, table.cmp th:first-child { color: var(--muted); }
table.cmp tr.shd td:first-child { color: var(--ok); font-weight: 600; }
.sig-weak { color: var(--faint); }
.sig-strong { color: var(--strong); }
.dedupe-review { margin-top: 8px; }
.dedupe-review summary { cursor: pointer; color: var(--muted); font-size: 12.5px; }

/* ── 登录页 ──
   变量对齐全站主题：描边 --line / 卡面 --surface / 输入底 --input-bg；
   圆角随全站分档：卡 12px（同浮层档）/ 输入 8px（视觉系统详见文件头）
   阴影也走**浮层档**（同 .search-panel / dialog.confirm 那条值），2026-08-03 补：
   ① 名实相符——它的圆角本来就按浮层档写的 12px，却一条阴影都没有；
   ② 它跟那两位是同一个物种：定宽、居中、独自浮在一张空页面上的面板，
      身边没有别的容器可以互相定位，卡片档那种贴身的接触边在空页上读着还是平的；
   ③ 这是远程员工看到的**第一屏**，全站视觉语汇的第一印象。 */
.login-wrap { display: flex; justify-content: center; padding: 8vh 16px 0; }
.login-card { width: 340px; max-width: 100%; display: flex; flex-direction: column;
  gap: 12px; padding: 26px 24px; border: 1px solid var(--line);
  border-radius: 12px; background: var(--surface);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55); }
.login-card label { display: flex; flex-direction: column; gap: 5px;
  font-size: 13px; color: var(--muted); }
/* 同 .fld 那条的道理：只写与全局不同的四项。border / background 原本在这重抄一遍
   （值逐字相同），留着会把全局的主色聚焦描边顶回去。
   ⚠ `color` 那条 2026-08-04 订正：它**不是**逐字重抄，原文是 `color: inherit`，
   而这个框在 <label> 里、`.login-card label` 是 `--muted`，所以它此前解出来是
   **弱化灰**。删掉之后由全局接管成 `--text`——正在输入的文字本就该比标签更清楚，
   方向是对的，但这是删重复声明时**顺带**改掉的行为，不是空操作，别当无事发生。
   真渲染实测：深色 rgb(176,185,200)→rgb(238,241,247)、浅色 rgb(95,107,125)→
   rgb(35,43,56)，另带 font-family Arial→sans（旧枚举没收这两个框）与高 36→37px。
   height: auto 是 2026-08-04 全局收敛后必须补的——收敛前这两个框（用户名没写 type、
   密码是 type=password）都不在旧枚举里、拿不到全局高度，靠 padding 自然撑高；
   收敛后它们进了全局，30px 的 border-box 塞不下 9px 上下 padding + 14px 字，会切字。 */
.login-card input { padding: 9px 10px; border-radius: 8px;
  font-size: 14px; height: auto; }
.login-hint { font-size: 12px; color: var(--muted); margin: 2px 0 0;
  text-align: center; }

/* ── owner 管理页（/admin/users）重设计 2026-07-06 ──
   沿用全站语汇：1px 实色描边、主色只点主操作/选中态/密码条幅。
   本页用到的圆角四档（2026-08-03 补齐，此前 .secret / .newpanel / .tg 是方的）：
     面板与磁贴 10px（.secret / .newpanel / .data-tg / .tg）
     控件 8px（.tpl / .fld input|select）
     胶囊 999px（.chip / .btn / .secret .copy / .ev / .data-tg .sw）
     行内等宽码 6px（.secret code）
   刻意保持近方的三个：.tg .box 勾选框 / .tg.danger .dot 菱形 / .badge::before 方色钉。
   房子 tab 约定是 .tab-on（不再单设 .tab.on）。 */
.tabbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }

/* 概览：本周订单/支数与产品/销售员明细是一组，宽屏同排，窄屏按阅读顺序堆叠。 */
.overview-tabs .tab { font-size: 16px; }
/* 悉尼概览标题里的本周/本月切换：跟在标题后，注释仍被 h2 .note 推到最右。 */
.period-toggle { display: inline-flex; gap: 6px; }
.period-toggle .tab { padding: 2px 12px; }
.overview-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.overview-week-grid { display: grid; gap: 16px; align-items: start; }
.overview-week-grid > section { margin: 0; }
.overview-board { display: flex; flex-direction: column; gap: 24px; }
.overview-board > * { margin-block: 0; }
.overview-board > h2 { margin-bottom: -12px; }
.overview-board > section > h2:first-child { margin-top: 0; }
.overview-board > .charts, .overview-board > .overview-focus { margin-top: 0; }
.overview-breakdowns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.overview-breakdowns > div { min-width: 0; }
.week-detail > .note { margin-bottom: 0; }
.week-detail th, .week-detail td { padding-block: 10px; }
.week-detail .table-scroll { max-height: none; }
.overview-focus-empty { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px;
  padding: 10px 14px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; }
.overview-focus-empty > h2 { flex: 1; margin: 0; }
.overview-focus-empty > .focus-clear { margin: 0; font-size: 13px; color: var(--muted); }
@container (min-width: 900px) {
  .overview-metrics.metric-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .overview-week-wholesale { grid-template-columns: minmax(210px, .85fr) repeat(2, minmax(0, 1fr)); }
  .overview-week-wholesale .overview-metrics { grid-template-columns: minmax(0, 1fr); }
  .overview-week-wholesale .overview-breakdowns { display: contents; }
}
.overview-charts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@container (max-width: 700px) { .overview-charts { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 680px) {
  .overview-metrics, .overview-breakdowns { grid-template-columns: minmax(0, 1fr); }
}
.hint { color: var(--muted); font-size: 12px; font-weight: 400; line-height: 1.5; }
.chip.fresh::before { background: var(--ok); }

/* 一次性密码主色边条幅（替代小 chip）
   10px 与 .recon-alert / .recon-reminder 同档——三者是同一个模式：1px 框 +
   加粗左强调边 + 相近内边距，形状不该三选一地各走各的。
   阴影同理走**条幅档**（比卡片弱一档，理由写在 .recon-alert 那条上方）：
   它的底 --tone-yellow 是 rgba(…,.13) 半透明，且是贴在流里的通知条不是容器。 */
.secret { display: flex; align-items: center; gap: 14px; padding: 13px 16px;
  margin-bottom: 18px; background: var(--tone-yellow); border: 1px solid var(--accent);
  border-left-width: 3px; border-radius: 10px; flex-wrap: wrap;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24), 0 5px 12px -8px rgba(0, 0, 0, 0.55); }
.secret .k { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* 6px：行内等宽小硬块，同 .search-entry kbd / .doc-body code 那一档。
   ⚠ 底必须是**不透明的 --surface**，别写 rgba(0,0,0,.x)：这块是一次性初始密码——
   那一屏最需要被一个字符不差地读出来的串。半透明黑叠在条幅的 --tone-yellow 黄底上
   合成出一片浊棕（浅色实测 #979490），主色蓝压上去只剩 1.52:1，等于看不见。
   根因是**它自己那层黑**，不是「主色配黄底」——换主色照样停在 1.6 上下。
   换成卡片面之后浅 6.41:1 / 深 7.92:1，且两个主题都自动跟着面色走。
   ⚠ 浅色这个数 2026-08-04 订正：此前写的 5.85 是 --accent-ink 浅色**旧值** #2a66ab
   压白的结果，同一轮把它压深到 #2760a1（为主操作按钮补 AA）之后没回来重量。 */
.secret code { font: 14px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--accent-ink); letter-spacing: .5px; background: var(--surface);
  padding: 6px 10px; border: 1px solid var(--line-soft); border-radius: 6px; }
.secret .note { font-size: 12px; color: var(--muted); }
/* 它是个按钮，只是没挂 .btn 类（全站按钮现在都是 999px 胶囊） */
.secret .copy { border: 1px solid var(--btn-border); background: transparent; border-radius: 999px;
  color: var(--text); padding: 6px 12px; font-size: 12.5px; cursor: pointer; margin-left: auto; }
.secret .copy:hover { background: var(--btn-bg-hover); }

/* 身份区两列表单网格 */
.admin-edit { display: flex; flex-direction: column; gap: 16px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 20px; }
.fld { display: flex; flex-direction: column; gap: 6px; }
.fld label { font-size: 12px; color: var(--muted); }
/* 只写**与全局那条不同**的四项。background / border / color / font-family 原本在这
   重抄了一遍（值逐字相同），2026-08-04 删掉——它们不是冗余而已，那条 `border:` 会
   把聚焦态吃掉：`.fld input` 与 `input:focus-visible` 同为 (0,1,1)，而它写在后面，
   主色聚焦描边一到这里就被顶回 --line。 */
.fld input, .fld select { padding: 9px 11px; font-size: 13.5px; border-radius: 8px;
  height: auto; }  /* height:auto 覆盖全局那条 30px——border-box 下 30px 高塞不下
  9px 上下 padding，文字上下会被切；input 与 select 拉齐由 padding 撑高 */
/* ⚠ 这里**刻意不写 :focus 规则**（2026-08-04 删）。原本是
     `.fld input:focus, .fld select:focus { border-color: var(--line-hover);
       box-shadow: 0 0 0 2px rgba(255,255,255,.06) }`
   特异性 (0,2,1)，压过全局的 `input:focus-visible` (0,1,1)，于是 /admin/users 与
   /admin/user_edit 那 6 个框**从来没用过全站那套主色聚焦环**。而那 2px 白环在浅色
   下是白压白卡：实测对比 1.00:1，等于完全不可见（深色也只有 1.20:1）。
   它还是全文件唯一一条硬编码、不随主题走的 box-shadow——浅色对家守护当时只查
   「纯黑」，纯白从缺口逃了整整一轮（现由 test_no_pure_white_shadow 补上）。
   要给这批框单独调聚焦态，请先想清楚为什么全站那套不适用，别再写死颜色。 */
.fld input:disabled { color: var(--faint); }
.fld .hint { font-size: 12px; color: var(--faint); }
.admin-edit .card > h2 { font-size: 14px; color: var(--strong); padding-bottom: 11px;
  margin: 0 0 15px; border-bottom: 1px solid var(--line-soft); display: flex;
  align-items: baseline; gap: 10px; flex-wrap: wrap; }
.admin-edit .card > h2 .cnt { margin-left: auto; font-size: 12px; font-weight: 500; color: var(--muted); }
.admin-edit .card > h2 .cnt b { color: var(--accent-ink); font-weight: 600; }

/* 角色模板可点卡 */
.tpl-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr)); gap: 10px; }
.tpl { border: 1px solid var(--line); background: var(--surface-2); padding: 12px 13px;
  cursor: pointer; text-align: left; transition: border-color .15s, background .15s; border-radius: 8px; }
.tpl:hover { border-color: var(--line-hover); }
.tpl.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.tpl .nm { font-size: 13.5px; font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 7px; }
.tpl.on .nm::before { content: "✓"; color: var(--accent-ink); font-weight: 700; }
.tpl .ds { font-size: 12px; color: var(--muted); margin-top: 5px; line-height: 1.5; }

/* 权限开关：短名 + 分组 + 对齐网格；真 checkbox 包在 label 里 */
.perm-block + .perm-block { margin-top: 18px; }
.perm-block > .bh { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
/* 与 .nav-group-label 同一档标签语汇（12px / 0.5px / --faint）。
   原先带 text-transform: uppercase——内容是「页面/操作/数据」纯中文，大写本就是
   空操作，但留着它就等于给后人一条「这里的标签该全大写」的错误示范。 */
.perm-block > .bh h3 { font-size: 12px; color: var(--faint); letter-spacing: 0.5px;
  margin: 0; }
.perm-block > .bh .gcnt { font-size: 12px; color: var(--muted); }
.perm-block > .bh .danger-mark { color: var(--danger); }
.perm-grp { margin-bottom: 12px; }
.perm-grp > .gl { font-size: 12px; color: var(--muted); font-weight: 600; margin-bottom: 8px;
  display: flex; align-items: center; gap: 8px; }
.perm-grp > .gl::after { content: ""; flex: 1; height: 1px; background: var(--line-soft); }
.toggles { display: grid; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr)); gap: 8px; }
/* 圆角 10px 而不是胶囊：.tg 高 38.8px、住在 minmax(158px,1fr) 的网格里，是权限
   **磁贴**不是行内胶囊——999px 会渲染成 19px 圆角的长条药丸，里面还装着 15×15
   的方勾选框和 45° 菱形危险点。与同页 .data-tg（数据权限开关，10px）对齐：
   同一页的两种权限开关该长得一样。⚠ 改这一档要同步改 tests/test_admin_css.py。 */
.tg { display: flex; align-items: center; gap: 9px; border: 1px solid var(--line-soft);
  background: var(--surface-2); padding: 8px 11px; cursor: pointer; font-size: 13px;
  color: var(--muted); user-select: none; border-radius: 10px;
  transition: border-color .12s, color .12s; }
.tg:hover { border-color: var(--line-hover); color: var(--text); }
.tg input { position: absolute; opacity: 0; width: 0; height: 0; }  /* 真 checkbox 隐身、留提交/键盘 */
.tg .box { width: 15px; height: 15px; border: 1px solid var(--line-hover); flex: none;
  display: grid; place-items: center; font-size: 11px; line-height: 1; }
.tg:has(input:checked) { color: var(--text); border-color: var(--accent); }
.tg:has(input:checked) .box { background: var(--accent); border-color: var(--accent); color: var(--accent-on); }
.tg:has(input:checked) .box::after { content: "✓"; font-weight: 700; }
.tg:focus-within { box-shadow: 0 0 0 2px var(--line-hover); }
.tg.danger .dot { width: 6px; height: 6px; background: var(--danger); flex: none;
  margin-left: auto; transform: rotate(45deg); }
.tg.danger:has(input:checked) { border-color: var(--danger); }
.tg.danger:has(input:checked) .box { background: var(--danger); border-color: var(--danger); }
.tg .chain { font-size: 12px; color: var(--faint); margin-left: auto; letter-spacing: .3px; }
.tg.danger .chain { margin-left: 0; }

/* 数据权限 switch */
.data-tg { display: flex; align-items: flex-start; gap: 12px; padding: 11px 13px;
  border: 1px solid var(--line-soft); background: var(--surface-2); cursor: pointer;
  margin-bottom: 8px; border-radius: 10px; }
.data-tg input { position: absolute; opacity: 0; width: 0; height: 0; }
/* 轨道走满圆、滑块正圆——方形开关是全页最扎眼的形状遗留 */
.data-tg .sw { width: 34px; height: 19px; background: var(--surface-3);
  border: 1px solid var(--line-hover); flex: none; position: relative; margin-top: 1px;
  border-radius: 999px; transition: background .15s; }
.data-tg .sw::after { content: ""; position: absolute; top: 1px; left: 1px; width: 15px;
  height: 15px; border-radius: 50%;
  background: var(--muted); transition: transform .15s, background .15s; }
.data-tg:has(input:checked) { border-color: var(--accent); }
.data-tg:has(input:checked) .sw { background: var(--accent); }
.data-tg:has(input:checked) .sw::after { transform: translateX(15px); background: #000; }
.data-tg:focus-within { box-shadow: 0 0 0 2px var(--line-hover); }
.data-tg .dt { font-size: 13px; color: var(--text); }
.data-tg .dh { font-size: 12px; color: var(--muted); margin-top: 3px; }

/* 底部操作条 + 危险操作行 */
.actionbar { position: sticky; bottom: 0; padding: 16px 0 6px; margin-top: 4px;
  display: flex; align-items: center; gap: 10px;
  background: linear-gradient(0deg, var(--bg) 60%, transparent); }
.actionbar .sep { flex: 1; }
.admin-danger { display: flex; gap: 10px; margin-top: 8px; }

/* 新建账号面板（替代 op-row 错位网格）
   10px 与 .empty 同档——两者都是虚线框占位面板，形状该一致。
   阴影同理走**卡片档**（不是条幅档）：它跟 .empty 一样是不透明 --surface 面、
   是个容器而不是提示条，两个虚线面板浮在不同高度才是真别扭。虚线框不会把投影
   也画成虚的，实测正常。 */
.newpanel { border: 1px dashed var(--line); background: var(--surface); padding: 16px 18px;
  margin-top: 14px; border-radius: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.40), 0 10px 22px -12px rgba(0, 0, 0, 0.95); }
.newpanel .nh { font-size: 12.5px; color: var(--muted); margin-bottom: 12px; }
.newpanel .row { display: flex; gap: 12px; flex-wrap: wrap; align-items: end; }
.newpanel .fld { flex: 1; min-width: 150px; }
.newpanel .own { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); padding-bottom: 9px; }

/* 审计事件徽章：描边=自己那个语义色的淡化版，底=对应 --tone-*。
   五种事件靠色相区分（绿/红/蓝/黄/灰），描边一律走变量、别写死色值 */
.ev { display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px; font-size: 12px;
  border: 1px solid; border-radius: 999px; }  /* 与 .badge 同物种（h=24.4 的行内徽章），同走胶囊 */
.ev.login { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); background: var(--tone-green); }
.ev.deny { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); background: var(--tone-red); }
.ev.run { color: var(--blue); border-color: color-mix(in srgb, var(--blue) 35%, transparent); background: var(--tone-blue); }
/* .ev.admin 描边跟 --warn 走（不是 --accent）：它的底是 --tone-yellow，跟着主色
   变蓝会在黄底上描一圈蓝边，还会与同为蓝色的 .ev.run 撞脸 */
.ev.admin { color: var(--accent-ink); border-color: color-mix(in srgb, var(--warn) 30%, transparent); background: var(--tone-yellow); }
.ev.exp { color: var(--muted); border-color: var(--line-soft); background: var(--tone-gray); }
.st { display: inline-flex; align-items: center; gap: 7px; }
.st .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.st .dot.ok { background: var(--ok); }
.st .dot.off { background: var(--faint); }
/* 通用复用类：admin 列表页首次引入（brief 误设为已有，实测缺，Step 5 补最小定义）。
   .tbl-wrap 也是这么来的，但它后来被到货预测/到货成本页也用上了、还被重复声明过一次，
   已上移到表格区与 .table-scroll 并排——**别在本段再写一条**。
   .chip.gold 只覆盖配色，圆角随 .chip 基类的 999px 胶囊。 */
.mono { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.linklike { color: var(--blue); cursor: pointer; }
/* ⚠ 类名 .gold 是黑金时代的历史遗留（改名要动模板，暂不动）——它表达的是
   「用主色强调」，颜色已跟着 --accent 走，主色不是金了名字也就只剩字面意思 */
.chip.gold { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 30%, transparent); }

/* ── 客户召回页（recall.html）最小样式 ──
   chip.red 沿用 chip 圆点语汇（同 .chip.warn/.stale 用 danger 色）；
   op-rows 与 sync-actions 同布局（操作按钮纵向列）；其余为行内表单/流水列表微样式 */
.row-dim { opacity: .45; }
.op-rows { display: flex; flex-direction: column; gap: 0; margin-top: auto; padding-top: 6px; }
.inline-form { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.log-list { list-style: none; margin: 6px 0; padding: 0; }
.log-list li { padding: 2px 0; }
.contact-cell { white-space: nowrap; }
/* 行内小徽标（客户名旁）：刻意不用 .chip（那是筛选条的大块头，行内太吵）——
   但「不用 .chip」是**尺寸**决定，形状仍跟队走 999px 胶囊 */
.tag { display: inline-block; font-size: 12px; line-height: 1; padding: 3px 6px;
  margin-left: 6px; border: 1px solid; border-radius: 999px; vertical-align: 2px;
  white-space: nowrap; font-family: var(--sans); letter-spacing: .3px; }
/* .warn 是警告徽标，整套颜色走 --warn。原先文字用 --accent-ink 只在主色是金的
   年代说得通，主色换蓝之后「警告」会变成蓝色 */
.tag.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 35%, transparent);
  background: color-mix(in srgb, var(--warn) 8%, transparent); }
.tag.red { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 8%, transparent); }
/* WhatsApp 品牌绿图标链接（电话旁），hover 微亮 */
.wa-link svg { vertical-align: -3px; }
.wa-link:hover svg { filter: brightness(1.15); }
/* 等级宝石图标：同形状色阶递进；钻石冰蓝带闪、黑钻暗紫钻体+主色闪（sp=闪光星）；
   「普通」不配图标压暗，让高等级客户一眼跳出来 */
.tier-ico { vertical-align: -2px; margin-right: 4px; }
.tier-ico .sp { fill: currentColor; }
.tier-ico.plain { color: var(--faint); opacity: .55; }
.tier-ico.silver { color: #a8b2bd; }
.tier-ico.gold { color: var(--accent-ink); }
.tier-ico.platinum { color: #8fd8de; }
.tier-ico.diamond { color: #6fb9ff; }
.tier-ico.black { color: #9d6bff; }
.tier-ico.black path:not(.sp) { fill: rgba(76, 56, 128, .45); }
.tier-ico.black .sp { fill: var(--accent); }
.tier-plain { color: var(--faint); }
/* 主表 10 列 + 行内表单：桌面无横滚容器（thead sticky 依赖 table overflow:clip），
   靠紧凑化把总宽压回 100%——收紧 padding/控件字号、邮箱压暗截断（title 留全文）。 */
.recall-table th, .recall-table td { padding: 8px 10px; }
@media (min-width: 761px) and (max-width: 1350px) {
  .recall-table th, .recall-table td { padding-left: 8px; padding-right: 8px; }
}
.run-filters select { min-width: 0; max-width: 100%; text-overflow: ellipsis; }
.recall-table .contact-cell { font-size: 12.5px; }
.email-dim { display: inline-block; max-width: 21ch; overflow: hidden;
  text-overflow: ellipsis; vertical-align: bottom;
  color: var(--faint); font-size: 12px; }
/* ⚠ 「跟进状态 · 回访」那格里有**两个** form：选择器/日期一个，静默按钮一个。
   `.inline-form` 是 inline-flex，两个并排排不下 —— 静默按钮会横着盖到右邻的
   「跟进记录」列上，把「＋ 记一笔」挤成竖着的一个「笔」字（2026-08-26 无头
   Chrome 截图看见；这不是本轮引入的，静默按钮 2026-08-25 上线那天就这样）。
   ⚠ 布局体检照不出这一类：表**总宽**没变，溢出发生在单元格之间。
   让这一格里的 form 各占一行，两件事就不再抢同一条基线。 */
.recall-table td .inline-form { display: flex; margin-bottom: 4px; }
.recall-table td .inline-form:last-child { margin-bottom: 0; }
.inline-form select, .inline-form input, .inline-form .btn { font-size: 13px; }
.inline-form select { padding: 3px 4px; }
/* ⚠ 两个框都得用**百分比宽**，绝对宽度在表格里是一堵墙：列宽按内容算，
   而绝对宽的控件不可压缩，于是「跟进记录」那一列的 min-content 直接等于框宽。
   原先备注框写死 200px，收着看不出来，一点开「＋ 记一笔」就把 10 列主表从
   964px 顶到 1045px、**页面横向溢出 37px**（2026-08-04 无头 Chrome 实测，
   展开一个就够）。百分比宽在内在尺寸阶段算不出具体值，列宽于是由别的内容
   决定；日期控件那 126px 改用 max-width 封顶——既不再顶死列，1440px 下也不会
   跟着列一起拉成一条大长条（备注框反而该跟着长，能多看几个字）。
   实测改后：1280px 展开一个 964px、全展开也 964px，页面零横滚。 */
.inline-form input[type="date"] { width: 100%; max-width: 126px; min-width: 0; padding: 3px 4px; }
.inline-form input[type="text"] { width: 100%; min-width: 0; padding: 3px 6px; }
/* 验真工作台查码框：.inline-form 的 width:100%/min-width:0 是给表格单元格用的，
   放在卡片里会被压成一指宽——这里给一个能放下 16 位码与 v1_ 新码的宽度；
   等宽是因为旧码区分大小写，0/O、1/l/I 看错就是另一个码（见 _MONO_ALLOWED） */
.verification-lookup input[type="text"] { width: 22em; max-width: 100%; font-family: var(--mono); }
/* 验真码库表（按批次，2026-10-09）：估测时间那格两行（「约 2024-11 ～ 2025-04」+ 一行依据），
   操作那格放一颗小按钮或一句为什么不能点。行内表单不撑列宽：按钮文字不换行，说明文字可以换。 */
.batch-library td .note { display: block; line-height: 1.5; }
.batch-library .batch-age { white-space: nowrap; }
.batch-toggle-form { margin: 0; }
.batch-toggle-form .btn { white-space: nowrap; }
.batch-library td.batch-op { min-width: 10em; }
/* 表头允许折成两行（「近 90 天 / 客户查询」），不然七列的最小宽度加起来超过 1280 屏的表壳；批次列留够宽，
   品牌分组行（「CRWN · 14 批」+ 两行灰字）不被挤成一个字一行 */
.batch-library th { white-space: normal; }
.batch-library th.col-main, .batch-library td.col-main { min-width: 14em; }
@media (max-width: 700px) { .batch-library th { white-space: nowrap; } }       /* 手机上表本来就在壳里横滚，表头折成四五行只会把表头撑得很高 */
.batch-library .batch-name { white-space: nowrap; }
.batch-library .batch-name.wrap { white-space: normal; }
/* 14 批一屏看全：表格外壳默认 64vh 封顶会让码库在笔记本屏上只露半张（同 .payment-accounts-table） */
.batch-library-scroll { max-height: none; }
/* 验真工作台改版（2026-10-10）。
   工具条：查码框和按钮同一行，窄屏自然折行；错误提示是徽章，长句要能换行。 */
.verification-toolbar .verification-lookup { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.verification-toolbar .lookup-error { margin: 6px 0 0; font-size: 13px; line-height: 1.6; }
.verification-toolbar .trial-batch { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin-bottom: 6px; }
/* 工具条精修（2026-10-10）：右列（刷新按钮 + 一行说明）靠右对齐，不再堆在右栏左边、中间空一大片；
   上下留白收紧到和两行内容相称（原先 18px 内边距 + 按钮与说明之间的 16px 空隙，两行内容撑出 116px 高的卡）。
   op-row 原本是「按钮列 + 摘要列」的两列网格，这里改成靠右的弹性行：任务有结果时，摘要折到按钮下一行、同样靠右。 */
.verification-toolbar { margin-bottom: 16px; padding-top: 12px; padding-bottom: 12px; row-gap: 6px; }
.verification-toolbar .supply-toolbar-actions { gap: 0; align-content: start; }
.verification-toolbar .supply-toolbar-actions .op-row { display: flex; flex-wrap: wrap; align-items: center;
  justify-content: flex-end; gap: 0 12px; border-bottom: none; padding: 0 0 4px; }
.verification-toolbar .supply-toolbar-actions .op-summary { flex: 1 0 100%; text-align: right; }
.verification-toolbar .supply-toolbar-actions > .note { margin: 0; text-align: right; }
.verification-toolbar .supply-toolbar-meta > .note { margin: 0; }
@container (max-width: 750px) {      /* 工具条叠成一列时，右列跟着靠左，别孤零零挂在右边 */
  .verification-toolbar .supply-toolbar-actions .op-row { justify-content: flex-start; }
  .verification-toolbar .supply-toolbar-actions .op-summary,
  .verification-toolbar .supply-toolbar-actions > .note { text-align: left; }
}
/* 四个数：大数下面最多两行小字，卡高从 256px 降到 180px 以内；更长的说明在卡片的悬停提示里。
   小字 12px + 左右内边距 16px，是为了让「查无此码 0 · 查满又扫 0 · 扫到失效批次 0」在 1440 宽的卡里不折行 */
.verification-metrics .card { padding: 14px 16px; }
.verification-metrics .card .big { margin: 8px 0 4px; }
.verification-metrics .sub { font-size: 12px; margin-top: 2px; }
.verification-metrics .part { display: inline-block; }
/* 「谁查的」与「数据从哪来」：一行细折叠，不套卡片（短内容不独占整宽卡）。紧贴上面的块，小字、左对齐 */
details.verification-who, details.verification-about { margin: 10px 2px 0; }
details.verification-who > summary, details.verification-about > summary { padding: 2px 0; font-size: 12px; }
details.verification-who + section.cards { margin-top: 20px; }
section.cards + details.verification-about { margin-top: 12px; }
details.verification-who .note, details.verification-about .note { margin: 6px 0 0; max-width: 72em; }
.tip { cursor: help; color: var(--faint); }
/* 码库一张表：品牌分组行就是小计（粗体、上边框、不换底色）；从查码结果点批次名跳过来时，
   :target 那一行用主色淡底高亮；状态与操作合成一列，宽度随内容、窄屏在表壳里横滚 */
.batch-library tr.batch-group td { font-weight: 600; border-top: 1px solid var(--line); }
.batch-library tr:target td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.batch-state { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.batch-library .batch-state .note { display: inline; }
.batch-state .badge { margin-left: 0; }
.batch-state .batch-toggle-form { display: inline-flex; }
.verification-who-table { width: auto; }
/* 测试码全表：30 行限高滚动，被「后台测一次」点中的那一行用同样的淡底标出来 */
.trial-scroll { max-height: 520px; overflow: auto; }
.trial-row-hit td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* 二维码的白底（扫码要的安静区）画在 SVG 本身里（trial_codes.qr_svg），全表里展开的每一张同样有；
   这里的白底只是给那张大图四周留一圈白 */
.trial-qr-now { display: block; margin: 8px 0; padding: 6px; border-radius: 6px; background: #fff; }
/* 测试码页签「下一个没用过的」：二维码 | 码、链接、按钮 | 用量，一张横向的卡。用量原来是并排的另一张卡，
   卡下面空一大块；并进来之后整张卡只有二维码那么高。窄了自动折行，折行时用量上面画一条分隔线 */
.verification-next { flex-direction: row; flex-wrap: wrap; align-items: flex-start; gap: 16px 28px; }
.verification-next .trial-qr-now { flex: none; margin: 0; }
.verification-next .next-main { flex: 0 1 auto; min-width: 0; }
.verification-next .next-usage { flex: 1 1 280px; min-width: 0; align-self: stretch; padding-left: 28px; border-left: 1px solid var(--line-soft); }
.verification-next h3 { margin: 0 0 8px; }
.verification-next p { margin: 0 0 8px; }
.verification-next .next-main > :last-child, .verification-next .next-usage > :last-child { margin-bottom: 0; }
@container (max-width: 900px) {
  .verification-next .next-usage { flex-basis: 100%; padding: 12px 0 0; border-left: none; border-top: 1px solid var(--line-soft); }
}

/* ── 推广返点页（referral.html）人工调整表单 ──
   刻意**不**复用 .inline-form：上面那两条百分比宽是为「表格单元格里的行内
   表单」调的（绝对宽在表格里是一堵墙，见上方长注释），可放到整页宽度下，
   width:100% 的备注框会独占一行、把三个控件挤成三行。这里是页面级控件行，
   走 .filters 的节奏（flex + 8px 间距），控件外观交给全局输入框规则。 */
.adjust-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 10px 0 14px; }
.adjust-form input[name="amount"] { width: 180px; }
.adjust-form input[name="note"] { width: 340px; max-width: 100%; }

@media (max-width: 680px) { .grid2 { grid-template-columns: 1fr; } }

/* WhatsApp 转达按钮（发货转达页）：品牌绿描边，与页内 .btn 同骨架；
   号码认不出可用手机时渲染禁用态，绝不给点了打不开的入口 */
.wa-btn { border-color: #25D366; color: #25D366; white-space: nowrap; }
.wa-btn:hover { background: rgba(37, 211, 102, .12); }
.wa-btn svg { vertical-align: -2px; margin-right: 3px; }
.wa-btn-off { color: var(--muted); font-size: 12px; white-space: nowrap; }

/* ── 发货转达页（spec 2026-07-29）──
   表格里原先一颗满宽绿按钮太抢眼、与全站 ghost 按钮语汇打架，改成
   28px 见方的图标按钮组；状态与操作分列，已跟进行淡化沉底。 */
.relay-summary { display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin: 4px 0 12px; font-size: 12.5px; color: var(--muted); }
.relay-summary .spacer { flex: 1 1 auto; }
.relay-summary .tally b { font-family: var(--sans); font-size: 15px;
  font-weight: 600; color: var(--text); margin-right: 5px;
  font-variant-numeric: tabular-nums; }
/* 三档计数各有各的色，语义按「还要不要人动手」排：
     待转达  --accent-ink 主色蓝  = 该你办
     短信已发 --muted     灰      = 系统已代办，不用管
     已跟进  --ok         绿      = 人已闭环
   ⚠ 中间这档 2026-08-04 才补（shipped.html 一直有 .tally.smsed，CSS 里却没有它）：
   它此前回落到上面那条的 --text，于是**三档里唯一「不用管」的那个反而最亮**
   （--text 与 --muted 在深/浅两套的 ΔE 分别是 20.9 / 27.6，不是看不出的差别）。
   ⚠ 别改用 --blue：它与 --accent-ink 的 ΔE 只有 5.7（深）/ 2.1（浅），
   待转达和短信已发会糊成同一个颜色，三档就白分了。 */
.relay-summary .tally.pending b { color: var(--accent-ink); }
.relay-summary .tally.smsed b { color: var(--muted); }
.relay-summary .tally.relayed b { color: var(--ok); }
/* 第四档「窗外老单」（2026-08-14 时间窗放宽后才会出现）：--faint 最弱一档。
   它只在选了超出短信窗的时间范围时渲染，语义是「纯查询、不是待办」——排在
   已跟进（绿）之后还更亮的话，一按「全部日期」整行计数就以它为视觉重心，
   而那恰恰是唯一不用看的一档。 */
.relay-summary .tally.outside b { color: var(--faint); }

/* 行内状态标签：比 .chip 轻一档（.chip 是卡片级 9px 内边距，塞进表格太胖） */
.status-tag { display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; white-space: nowrap; color: var(--muted); }
/* 50%：与三个同族状态点（.chip::before / .prio::before / .ev-dot）一致，
   全站「语义色圆点」只有一种形状 */
.status-tag::before { content: ""; flex-shrink: 0; width: 6px; height: 6px;
  border-radius: 50%; background: var(--faint); }
.status-tag.todo { color: var(--text); }
.status-tag.todo::before { background: var(--accent); }
.status-tag.ok::before { background: var(--ok); }
.status-tag.sms::before { background: var(--ok); opacity: .45; }
.status-tag.none::before { background: var(--danger); }

.relay-acts { display: flex; align-items: center; gap: 6px; }
.relay-acts .inline-form { display: inline-flex; margin: 0; }
/* 等宽等高图标按钮：文字按钮的 padding 会把单个字符挤歪，这里锁死 28×28。
   ⚠ 类名里的 -sq（square）是历史名——它继承 .btn 的 999px，28×28 现在渲染成
   一个**正圆**。改名要动模板（超范围），先在此说清名实不符，别照名字推形状。 */
.btn-ico-sq { width: 28px; height: 28px; padding: 0; display: inline-flex;
  align-items: center; justify-content: center; }
.wa-btn-ico svg { margin-right: 0; }
.wa-btn-off-ico { border-color: var(--line); color: var(--faint);
  cursor: not-allowed; opacity: .5; }
.wa-btn-off-ico svg { margin-right: 0; }
.relay-ok { color: var(--muted); font-size: 13px; line-height: 1; }
.relay-ok:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-ink); }

/* 已跟进行淡化沉底：还看得见（要能撤销），但不再抢注意力；hover 恢复可读 */
tr.relayed { opacity: .5; }
tr.relayed:hover { opacity: .9; }

/* 「已修正」角标：号码国家码被下单表单写坏、系统按澳洲手机救回来的标记，
   title 里带客户填的原值，可审计 */
.fix-tag { margin-left: 6px; padding: 1px 5px; font-size: 12px;
  border: 1px solid var(--line); border-radius: 999px;  /* 行内小徽标，同 .tag 一档 */
  color: var(--muted); cursor: help; white-space: nowrap; }

/* ── 收款账户筛选条（财务页）──
   两行 = 公私账 / 给谁用，复用 .chip 语汇但收窄一档：用默认 chip 尺寸会占掉半屏，
   压到 4px padding 后才跟一张表的份量相称。状态维度不设 chip——可用与停用已拆成两张表。 */
.pa-filters { display: flex; flex-direction: column; gap: 6px; margin: 0 0 14px; }
.pa-frow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.pa-flabel { flex-shrink: 0; width: 40px; font-size: 12px; color: var(--faint);
  letter-spacing: .3px; }
.pa-filters .chip { padding: 4px 11px; font-size: 12px; gap: 7px; }
.pa-filters .chip b { font-family: var(--sans); font-size: 12px; font-weight: 600;
  color: var(--text); font-variant-numeric: tabular-nums; }
/* 该组合下一个账户都没有：留在原位保持 chip 不跳动，压暗表示点了也是空 */
.pa-filters .chip.zero { opacity: .38; }
.pa-filters .chip.zero b { color: var(--muted); }
/* 可用 / 已停用两张表的小标题：比 h2 轻一档，两表之间留出呼吸 */
.pa-sub { margin: 18px 0 8px; font-size: 13px; font-weight: 600; letter-spacing: .3px; }
.pa-sub .note { margin-left: 8px; font-weight: 400; }
/* 停用/暂锁行压暗（原来写在模板 style 属性里，挪进样式表） */
.pa-off { opacity: .55; }
/* PayID 挂在账号下方：小字压暗，不跟账号抢视线 */
.pa-payid { display: block; font-size: 12px; color: var(--faint);
  letter-spacing: .2px; margin-top: 2px; }
/* 行内直编只读态随实际内容定列宽；文本伸展，✎ 对齐到该列右侧。 */
.pay-edit-view { display: flex; align-items: center; gap: 5px; }
/* ⚠ 必须显式写：作者样式的 display 会盖掉 hidden 属性自带的 display:none
   （UA 样式表优先级更低），少了这条，点 ✎ 会只读态和输入框同时显示 */
.pay-edit-view[hidden] { display: none; }
.pay-val { flex: 1 1 auto; min-width: 0; }
/* 「给谁用」是多值：只读态排成小标签，「Elsa, Yuki」不再糊成一串看不出边界。
   inline-flex + wrap：单元格窄时自然折行，不去顶横向滚动条 */
.pa-used { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.pa-tag { display: inline-block; padding: 0 7px; border-radius: 999px;
  background: var(--line-soft); border: 1px solid var(--line);
  font-size: 12px; line-height: 17px; white-space: nowrap; }
/* 共用 / 独立站 / 备用：不是具体归属，压暗一档（沿用改造前「备用」那身灰） */
.pa-tag.soft { color: var(--faint); background: none; border-color: transparent; }
/* 编辑态整块竖排：候选一行行铺，「保存/取消」落在下面。
   ⚠ 必须**脱开 td 的 nowrap 并给定宽**：留着 inline-flex + nowrap 的话，浏览器按
   max-content 算这一格，把「保存/取消」一路推到备注列、压住那一列的 ✎（2026-09-01
   无头 Chrome 截图实测；layout_audit 量不到——编辑态默认是 hidden 的）。 */
.pa-used-box { display: inline-block; width: 250px; white-space: normal;
  vertical-align: top; }
/* ⚠ 同 .pay-edit-view：作者样式的 display 会盖掉 hidden 属性自带的 display:none，
   少了这条，点 ✎ 之前候选就一直摊在表上 */
.pa-used-box[hidden] { display: none; }
.pa-picker { display: flex; flex-wrap: wrap; gap: 4px; }
.pa-pick { display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  padding: 1px 8px 1px 5px; border-radius: 999px; border: 1px solid var(--line);
  font-size: 12px; line-height: 17px; white-space: nowrap; }
.pa-pick input { margin: 0; }
.pa-pick:has(input:checked) { border-color: var(--accent); color: var(--strong); }
/* 三个独占标签用虚线框跟销售名分开：勾中它们会清掉其余选择，先给个视觉预告 */
.pa-pick.excl { border-style: dashed; }
.pa-picker-act { display: flex; gap: 4px; margin-top: 6px; }
/* ✎ 直编图标：19 行 × 两列 = 38 个，做成带边框的小按钮会把整张表吵翻，
   所以默认淡到不抢视线，鼠标移到那一行才浮出来、指到图标上再变清晰。
   padding 撑出够大的点击区（图标本身才十几像素） */
.pay-ico {
  border: none; background: none; cursor: pointer;
  font: inherit; font-size: 12px; line-height: 1; padding: 3px 4px;
  color: var(--faint); border-radius: 4px; transition: color .12s, background .12s;
}
tbody tr:hover .pay-ico { color: var(--muted); }
.pay-ico:hover, .pay-ico:focus-visible { color: var(--strong); background: var(--line-soft); }
/* 复制两条路都失败时就地展开、内容已全选，让人 Cmd+C 手拿（等宽对齐 BSB/账号）。
   10px 圆角 = 全局输入框那一档。
   ⚠ 这里的 background / border / color / border-radius 是 2026-08-04 之前的历史：
   当时全局输入框规则是枚举式的（`input[type=text|number|date] + select`），textarea
   整个掉在网外，不自己写就顶着 UA 默认样式（深灰实线边框，深色主题下是个白底黑字
   的方框）。现在全局那条已把 textarea 并进**外观块**（高度块刻意不并，多行控件套
   30px 会被压成一行），这几条于是变成「与全局同值的重复声明」——留着无害（类
   选择器 (0,1,0) 恒压全局的 (0,0,1)，与顺序无关），但**别照它的样子再写第二个
   textarea**：直接继承全局那份就行。 */
.pay-manual-copy { display: block; margin-top: 6px; width: 260px; padding: 4px 6px;
  border-radius: 10px; background: var(--input-bg); border: 1px solid var(--line);
  color: var(--text);
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; resize: vertical; }

/* 到货成本试算器：输入框窄一点，免得挤走结果列 */
/* 试算/汇率输入：数字右对齐 + 原生上下箭头。
   ⚠ 箭头压住数字时，**加 padding-right 是反的**——Chrome 把 spinner 画在
   content box 内的右侧，右内边距会把箭头连同文字一起往左推，间隙纹丝不动
   （2026-08-01 用无头浏览器实测确认：加到 21px 后 27.07 仍紧贴、4.583 仍被压）。
   要给的是**内容区宽度**：content ≥ 文字宽 + 箭头宽(~15px) + 间隙。
   所以这里反过来收窄内边距、加宽框，另给箭头一点左外边距把它和数字分开。 */
/* 宽度按「这一列真正可能出现的最长值」定，不一刀切——一刀切 7.6em 会把 14 列的
   主表撑到要横向滚（2026-08-02 用户反馈：装不下、要左右滑）。

   ⚠ 两个实测教训，改这里之前先读：
   ① **去掉上下箭头**。那对箭头连间隙吃掉 19px，占的正是最贵的一段宽度；这些格子
      装的是出厂价、运费、扣关率这类财务参数，人是直接键入的。step 保留，键盘上下
      键照样能调。
   ② **用 px 不用 em，并固定 font-size**。原来是 `width: 7.6em` + `font: inherit`，
      在表格里继承的字号比别处大，5em 会渲染成 65px——纸上按 em 算完全对不上实际。
   判据见 tests/test_input_spinner_room.py。 */
/* ⚠ 必须写成 `input.sim-in`：全局 `input[type="number"]` 的
   `padding: 0 11px` 特异性(0,1,1) 高于裸类 `.sim-in`(0,1,0)，
   光写 .sim-in 那 22px 左右内边距吃不掉——实测内容区只剩 20px。 */
input.sim-in {
  /* 表**外**的（汇率那两个在 .filters 里）用固定宽度：给 100% 会把 label 撑开、
     标签和输入框分成上下两行。表内的另见下面 `table.dense input.sim-in`。 */
  width: 52px;
  min-width: 44px;
  max-width: 78px;
  box-sizing: border-box;
  font-size: 12.5px;
  padding: 0 6px;
  text-align: right;
  appearance: textfield;
  -moz-appearance: textfield;
}
input.sim-in.narrow { width: 46px; min-width: 40px; max-width: 66px; }  /* 运费/扣关/赔付 */
input.sim-in.wide { width: 64px; min-width: 56px; max-width: 84px; }    /* 汇率/单支重量 */
/* 表**内**才自适应：填满单元格，上下限兜底。目标是「刚好不用左右拖」而不是压到
   最小——2026-08-02 第一版压成了 859px，用户反馈「太小了、紧巴巴」。列宽由表格
   按内容分配，空间富余时输入框跟着舒展。 */
table.dense input.sim-in { width: 100%; }
/* 产品名不许折成两行——「CRWN Infinity」拆两行会把整行撑高一倍 */
td.prod { white-space: nowrap; }
/* 列很多的表（成本主表 14 列）：默认 14px 的单元格内边距一列就吃 28px，
   14 列光内边距 392px，比所有输入框加起来还多。实测这是「装不下要左右滑」的
   头号原因，不是输入框本身。 */
/* 本页的横滚包裹层是全站共用的 .tbl-wrap，规则在表格区（与 .table-scroll 并排），
   width:100% 那条（含它的来历）也在那儿——**别在这里再写一条**，2026-08-02 之前
   正是这样重复声明了一次，后写的无声压过先写的。 */
table.dense { width: 100%; }
table.dense th, table.dense td { padding: 6px 9px; }
input.sim-in::-webkit-inner-spin-button,
input.sim-in::-webkit-outer-spin-button {
  appearance: none;
  margin: 0;
}
/* 这里原有一条 `tr.sim-mismatch { outline: 2px solid var(--red, #c00) }`，2026-08-04 删。
   两头都是死的：① `sim-mismatch` 全仓零引用（扫过 templates/ 全部 .html、scripts/ 全部
   .py、static/ 全部 .js——landing.html 从没渲染过这个类；tests/test_landing_cost.py 里
   出现的 `sim-mismatch-banner` 是另一个 token，且那条断言它**不存在**）；
   ② `--red` 是当时全文件**唯一**「被 var() 引用却从未定义」的变量（剥注释后逐个比对
   38 个引用 vs 48 个定义，只有它没有对家），永远走 fallback #c00，而 #c00 不在本轮
   任何色板里。删掉之后「引用却未定义」归零。
   2026-08-02 试算器退役、删前端镜像横幅时它被落在原地。
   ⚠ 同名前缀的 `.sim-in` 是活的（_supply_cost.html 的参数输入框），别误删。 */


/* ── 客户「马甲」软关联（2026-08-26）──
   横幅贴在内容流里、不铺卡片面，所以走**条幅档**的语汇：淡底 + 细边，
   不给卡片档那条阴影（深度分档 _DEPTH_TIERS 是按名单钉同值的，混进一个
   不在名单里的会让「同档同值」名存实亡）。 */
/* ⚠ **block 而不是 flex**：横幅里是一句话夹着一个按钮，flex 会把那句话拆成
   若干匿名 flex item，按钮被挤到下一行（2026-08-26 截图看见）。block + 让
   按钮 inline-flex 才是「按钮跟在句子后面」。 */
.alias-banner { margin: 0 0 14px; line-height: 2; }
.alias-banner .inline-form { display: inline-flex; vertical-align: middle;
  margin-left: 6px; }
.alias-chip { display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border: 1px solid var(--line); border-radius: 999px; }


/* 客户自助编辑表单（2026-08-28）。
   ⚠ **只写布局，不写外观**：输入框的边框/背景/圆角全局已有（`input, select,
     textarea` 那块），在这儿再定义一遍就是两份会漂移的样式——而漂移的表现是
     「这一个表单跟全站长得不一样」，没人会报 bug，只会觉得别扭。 */
.edit-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.edit-form label { display: flex; flex-direction: column; gap: 4px;
                   font-size: 13px; color: var(--muted); }
.edit-form label.inline { display: inline-flex; flex-direction: row;
                          align-items: center; gap: 5px; }
.edit-form textarea { resize: vertical; }
.card > form.edit-form { align-items: stretch; }
.edit-form .btn { align-self: flex-start; }


/* ── 首页（/home，2026-10-04；版式 2026-10-05 按「没对齐」重排）──
   ⚠ 只写布局，不写外观：卡片、徽章、按钮、chip 全用全站现成的，首页不长自己的配色
     ——它是几个业务页的入口合集，按钮长得跟财务页、打包页上那颗一样才认得出是同一件事。
   对齐靠三条，改哪条都要整页量一遍（无头 Chrome 按 1280 / 1512 / 1920 + 手机 390 量过）：
   ① 上下两段（对账+打包 / 本周两张）用**同一个**等宽两栏网格，竖向分界线才在一条线上；
      原先上面 3:2、下面 1:1，1512 宽下两条分界线差了 120px。
   ② 上面两张卡拉成等高（grid 默认 stretch），脚注靠 margin-top:auto 压到卡底。
   ③ 每一行都是「行首一列 + 内容一列」：内容第一行是状态、第二行是按钮——两张卡里的
      按钮于是都从同一条竖线起，左右两张卡的行高也一致（原先财务按钮挤在状态行右端、
      打包按钮掉到第二行从最左边起，两张卡各是各的样子）。 */
.home-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 1100px) { .home-grid { grid-template-columns: minmax(0, 1fr); } }
/* 锁被占的那句比同步时刻长，窄屏得能折行（.chip 默认不折） */
.home-status .chip { white-space: normal; }
/* 卡片标题右侧的「去那一页」——.card h3 是 flex，靠 margin-left:auto 推到最右 */
.home-more { margin-left: auto; font-size: 12.5px; font-weight: 500; }
/* align-items 用 baseline 不用 center：状态徽章多到折成两行时，center 会把行首「10 月」
   放到两行中间，看着跟哪一行都没对上；baseline 让它始终跟状态第一行的字对齐 */
.home-row { display: grid; grid-template-columns: 52px minmax(0, 1fr); align-items: baseline;
            gap: 10px 12px; padding: 14px 0; border-top: 1px solid var(--line-soft); }
.home-row > .home-row-main, .home-row > .home-row-ops, .home-row > .recon-alert { grid-column: 2; }
.home-row-label { font-weight: 600; color: var(--strong); }
.home-row-main { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; min-width: 0; }
.home-row-main .badge { margin-left: 0; }
.home-row-ops { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0; }
.home-row-ops form { margin: 0; }
.home-row-ops input[type="date"] { width: 136px; }
/* 对账那两种红色警报（账单独有 / 回款✓账单✗）：用财务页同一条横幅，落在内容那一列 */
.home-row .recon-alert { margin: 0; }
/* 脚注压到卡底（两张卡等高时脚注落在同一水平线）；要写成两段选择器——
   单写 .home-foot 压不过 .card .sub 那条 margin-top:3px */
.home-card > .home-foot { margin-top: auto; padding-top: 12px; }

/* 本次打包结果：按钮下的小摘要，两页共用；长型号允许换行，窄屏不撑破内容列。 */
.packing-summary { min-width: 0; margin-top: 12px; padding-top: 9px;
  border-top: 1px solid var(--line-soft); font-size: 12.5px; line-height: 1.6; }
.home-row > .packing-summary { grid-column: 2; margin-top: 0; }
.packing-summary-heading { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
.packing-summary-heading > strong, .packing-summary-total { color: var(--strong); }
.packing-summary-meta { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
.packing-summary-products { display: flex; flex-wrap: wrap; gap: 3px 14px;
  list-style: none; margin: 5px 0; padding: 0; }
.packing-summary-products li { display: flex; flex-wrap: wrap; gap: 0 5px; min-width: 0; }
.packing-summary-products li > span { overflow-wrap: anywhere; }
.packing-summary-products li > strong { white-space: nowrap; font-weight: 600; }
.packing-summary-total, .packing-summary-empty, .packing-summary-warning { margin: 4px 0 0; }
.packing-summary-empty { color: var(--muted); }
.packing-summary-warning { color: var(--warn); }

/* 首页账单明细与财务任务：业务结果占主视图，逐步输出收在日志里。 */
/* 运行时只亮一处：全站一颗小胶囊，挂在页头标题旁（base.html 的 .page-head-title 那一行）。
   ⚠ 亮起 / 熄灭都不许改变页头高度、不许把正文往下挤——第一版是页头下面一整条横幅，一亮整页往下跳
   （2026-10-16 用户反馈后改）。做法是**胶囊只吃标题剩下的空间、标题一像素都不让**：
   · 标题行 .page-head-title 撑满时间戳左边的全部空间（竖排的窄屏也拉满整行），宽度跟胶囊亮不亮无关；
   · 胶囊的容器 .page-activity 基准宽 0、尺寸隔离成容器（container-type）：它自己的内容永远不计入
     行宽，h1 的宽度在亮与不亮时完全一样，所以标题绝不会因为胶囊被挤成两行；
   · 标题旁剩的地方不够：先收任务名；连「转圈 + 脚本运行中」都放不下（普通详情页在手机上、长标题页），
     就挪到页头分隔线右端、骑在线上（绝对定位，用的是页头下沿本来就空着的那条带，不占版面）。
     **绝不整颗藏起来**——藏起来的话页面照样会在锁空后自动刷新，看着就是无缘无故刷了一下（复审抓到）。
   胶囊高 28px（挪到分隔线上时 24px），比两种字号的标题行都矮，撑不高这一行。
   守护：tests/test_dashboard_page_activity_bar.py 真浏览器逐页对拍（含普通 / 长标题详情页），320–1440 十一档宽度。
   旋转环表示仍在执行，不虚构进度百分比。 */
.page-head { position: relative; }   /* 分隔线上那颗的定位祖先 */
.page-head-title { display: flex; align-items: center; flex: 1 1 auto; min-width: 0; }
.page-head-title > h1 { flex: 0 1 auto; min-width: 0; }
.page-activity { display: flex; align-items: center; flex: 1 1 0; min-width: 0; overflow: hidden;
  container-type: inline-size; }
/* position: relative 不能省：收起来给读屏的字是绝对定位的 1px 小块，胶囊不是定位祖先的话它们会逃出
   容器的 overflow 裁剪，跑到视口右边外头撑出横向滚动（长标题页 600px 宽实测多出 4px） */
.job-activity { position: relative; display: inline-flex; align-items: center; flex: none; min-width: 0;
  max-width: calc(100% - 12px); margin-left: 12px;
  box-sizing: border-box; height: 28px; padding: 0 12px 0 9px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--surface)); border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--accent-ink); font-size: 13px; line-height: 1; white-space: nowrap; }
.job-activity-face { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.job-activity[data-state="waiting"] { border-color: color-mix(in srgb, var(--warn) 45%, var(--surface));
  background: color-mix(in srgb, var(--warn) 10%, var(--surface)); color: var(--warn); }
.job-activity[data-state="done"], .job-activity[data-state="failed"] { display: none; }
/* 锁空了、但页面上有刷新会丢的东西所以没自动刷新：收成一颗安静的灰胶囊，转圈停下 */
.job-activity[data-state="ended"] { border-color: var(--line); background: var(--surface); color: var(--muted); }
.job-activity[data-state="ended"] .job-activity-icon { animation: none; border-right-color: currentColor; opacity: .45; }
.job-activity-icon { display: block; flex: 0 0 16px; width: 16px; height: 16px;
  box-sizing: border-box; border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: job-activity-spin .9s linear infinite; }
.job-activity-label { flex: none; font-size: inherit; font-weight: 600; }
.job-activity-task { min-width: 0; max-width: 32ch; overflow: hidden; text-overflow: ellipsis;
  color: var(--text); font-weight: 500; }
/* 不占版面、但读屏照读：提示句一直这样；地方不够时任务名也这样收起来 */
.job-activity-hint { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
/* 两道门槛按最长的标题字算（ended「已结束 · 待手动刷新」约 120px）：带任务名要 标题 + 至少 36px 任务名 + 边距 ≈ 225；
   只放标题要 ≈ 180。文案改长了门槛就得跟着改——tests/test_dashboard_page_activity_bar.py 对运行 / 等待 / ended
   三种标题逐档量「字不溢出边框、任务名不剩一小截」，会当场红 */
@container (max-width: 230px) {
  .job-activity-task { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); }
}
/* 标题旁连「转圈 + 脚本运行中」都放不下：挪到页头分隔线右端、骑在线上。定位祖先是 .page-head
   （.page-activity 的 container-type 不带布局隔离，实测不会把它截掉）；页头下沿到正文之间本来就空着
   padding + margin 两段，24px 高的胶囊上下各探 12px，碰不到标题行、时间戳和正文 */
@container (max-width: 185px) {
  .job-activity { position: absolute; right: 0; bottom: 0; transform: translateY(50%);
    margin: 0; max-width: 60%; height: 24px; padding: 0 10px 0 7px; font-size: 12px; }
}
@keyframes job-activity-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .job-activity-icon { animation: none; } }
.task-status { min-width: 0; padding: 4px 0; }
.task-status-heading { display: flex; align-items: flex-start; gap: 13px; }
.task-status-mark { flex: 0 0 4px; align-self: stretch; min-height: 56px;
  border-radius: 4px; background: var(--ok); }
.task-status[data-state="running"] .task-status-mark,
.task-status[data-state="waiting"] .task-status-mark { background: var(--warn); }
.task-status[data-state="failed"] .task-status-mark { background: var(--danger); }
.task-status-main { min-width: 0; flex: 1; }
.task-status-kicker { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  color: var(--muted); font-size: 12px; }
.task-status-main h3 { margin: 5px 0 7px; color: var(--strong); font-size: 18px;
  line-height: 1.45; overflow-wrap: anywhere; }
.task-status-phase { margin: 0; line-height: 1.65; color: var(--muted);
  overflow-wrap: anywhere; }
.task-status-times { display: flex; flex-wrap: wrap; gap: 10px 28px;
  margin: 16px 0 0 17px; }
.task-status-times > div { display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px; }
.task-status-times dt { color: var(--muted); }
.task-status-times dd { margin: 0; color: var(--text); font-variant-numeric: tabular-nums; }
.task-status-log { margin-top: 17px; border-top: 1px solid var(--line-soft); padding-top: 12px; }
.task-status-log > summary { cursor: pointer; width: fit-content; color: var(--text);
  font-size: 12.5px; font-weight: 600; }
.task-status-log > summary .note { margin-left: 8px; font-weight: 400; }
.task-status-log .run-box { margin: 12px 0 0; max-height: 440px; min-height: 160px;
  font-size: 12px; overflow-wrap: anywhere; }
.task-status-private { margin: 14px 0 0 17px; }
.finance-ledger { margin-top: 22px; padding: 22px 24px; scroll-margin-top: 18px; }
.finance-ledger-header { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-bottom: 20px; }
.finance-ledger-kicker { margin: 0 0 5px; color: var(--muted); font-size: 12px; letter-spacing: .04em; }
.finance-ledger-header h2 { margin: 0; font-size: 20px; }
.finance-ledger-header h2::before { display: none; }
.finance-ledger-months { display: flex; flex-wrap: wrap; gap: 4px; }
.finance-ledger > .task-status { padding-bottom: 18px; margin-bottom: 20px;
  border-bottom: 1px solid var(--line); }
.finance-ledger-overview { display: flex; align-items: center; justify-content: space-between;
  gap: 28px; padding: 0 0 21px; }
.finance-ledger-verdict { min-width: 0; }
.finance-ledger-verdict > .badge { display: inline-block; margin: 0 0 9px; }
.finance-ledger-verdict > strong { display: block; font-size: 17px; color: var(--strong); }
.finance-ledger-verdict p { margin: 7px 0 0; }
.finance-ledger-totals { display: grid; grid-template-columns: repeat(4, minmax(90px, 1fr));
  gap: 18px; margin: 0; }
.finance-ledger-totals > div { padding-left: 17px; border-left: 1px solid var(--line-soft); }
.finance-ledger-totals dt { color: var(--muted); font-size: 12px; margin-bottom: 5px; }
.finance-ledger-totals dd { margin: 0; font: 600 24px/1.2 var(--sans);
  color: var(--strong); font-variant-numeric: tabular-nums; }
.finance-ledger-totals dd span { font: 12px var(--sans); color: var(--muted); }
.finance-ledger-totals small { display: block; margin-top: 7px; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.finance-ledger-categories { display: flex; flex-wrap: wrap; gap: 6px;
  border-top: 1px solid var(--line-soft); padding: 16px 0 13px; }
.finance-ledger-categories .badge { margin: 0; }
.finance-ledger-table-wrap { width: 100%; overflow-x: auto; }
.finance-ledger-table { width: 100%; table-layout: auto; margin: 0; }
.finance-ledger-table th, .finance-ledger-table td { padding: 13px 12px; white-space: normal;
  overflow-wrap: anywhere; }
.finance-ledger-table td { vertical-align: top; }
.finance-ledger-table .badge { margin: 0; white-space: normal; }
.finance-ledger-table td small { display: block; margin-top: 6px; color: var(--muted);
  font-size: 12px; line-height: 1.5; }
.finance-ledger-row-alert { background: color-mix(in srgb, var(--danger) 8%, transparent); }
.finance-ledger-detail { line-height: 1.6; }
.finance-ledger-empty { padding: 18px 0; border-top: 1px solid var(--line-soft); }
.finance-ledger-empty strong { font-weight: 500; color: var(--text); }
.finance-ledger-empty p { margin: 7px 0 0; }
.finance-ledger-warning { margin: 0 0 18px; }
.finance-ledger-permission { margin: 14px 0 0; }
.finance-ledger-footer { display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 8px 20px; margin-top: 18px;
  padding-top: 14px; border-top: 1px solid var(--line-soft); font-size: 12px; }
@media (max-width: 1100px) {
  .finance-ledger-overview { align-items: flex-start; }
  .finance-ledger-totals { grid-template-columns: repeat(2, minmax(90px, 1fr)); gap: 16px; }
}
@media (max-width: 600px) {
  .finance-ledger { padding: 17px 15px; }
  .finance-ledger-header { align-items: flex-start; flex-direction: column; gap: 12px; }
  .finance-ledger-overview { flex-direction: column; gap: 20px; }
  .finance-ledger-totals { width: 100%; }
  .finance-ledger-totals > div { padding-left: 12px; }
  .task-status-main h3 { font-size: 17px; }
  .task-status-times { gap: 8px 16px; }
  .task-status-log > summary .note { display: none; }
  .table-fill .finance-ledger-table { display: block; overflow: visible; }
  .finance-ledger-table thead { display: none; }
  .finance-ledger-table tbody { display: block; }
  .finance-ledger-table tr { display: block; padding: 10px 11px;
    border-bottom: 1px solid var(--line); }
  .table-fill .finance-ledger-table td { display: grid; width: auto; min-width: 0; max-width: none; grid-template-columns: 90px minmax(0, 1fr);
    gap: 8px; padding: 5px 0; border: 0; text-align: left; }
  .finance-ledger-table td::before { content: attr(data-label); color: var(--muted); font-size: 12px; }
  .finance-ledger-table td small { grid-column: 2; margin: 0; }
  .finance-ledger-table td > .badge { justify-self: start; }
}

/* 页面细化：统一模块间距与操作列，按内容用途分配可读宽度。 */
.distribution-chart-frame { position: relative; height: 280px; min-width: 0; }
.distribution-details { margin-top: 14px; }
.distribution-details .tbl-wrap { margin-top: 10px; }
.tier-chart-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(130px, .8fr); gap: 18px; align-items: center; }
.tier-chart-legend { list-style: none; padding: 0; margin: 0; }
.tier-chart-legend li { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; align-items: baseline; padding: 7px 0; font-size: 13px; }
.tier-chart-legend strong { color: var(--text); font-variant-numeric: tabular-nums; }
.customer-distribution > .card { padding: 18px 20px; }
.inventory-reference > .card > .note { margin: 10px 0; }
.inventory-reference > .card > .diff-banner { margin: 4px 0 12px; }
.logistics-controls { display: grid; gap: 12px; }
.logistics-controls details { margin-top: 6px; }
.table-fill .logistics-table { width: 100%; table-layout: auto; }
.logistics-table th, .logistics-table td { vertical-align: top; }
.logistics-table .col-main { min-width: 12ch; }
.logistics-table .col-wrap { min-width: 18ch; }
.logistics-table .logistics-col-status { white-space: normal; min-width: 108px; }
.logistics-col-status .badge { display: flex; width: fit-content; max-width: 100%; margin: 3px auto; padding: 2px 6px; font-size: 12px; line-height: 1.45; }
.logistics-track-number { display: block; overflow-wrap: anywhere; }
.logistics-shipments > summary .note { display: block; }
.shipment-detail { padding: 8px 0; border-top: 1px solid var(--line-soft); }
.logistics-orphans { margin-top: 16px; }
.supply-toolbar { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 1fr); gap: 20px; margin-bottom: 24px; }
.supply-toolbar-meta { min-width: 0; }
/* align-content: start：不让栅格被撑到左栏那么高、多出来的高度分给各行——那样一行的说明变长（「执行中 · … · 完成后自动
   更新结果」两行），别的行就被挤矮、按钮上下挪几像素，点按钮和任务跑完各跳一下（复审实测 /supply 4–16px） */
.supply-toolbar-actions { display: grid; gap: 8px; align-content: start; }
.supply-toolbar > :only-child { grid-column: 1 / -1; }
.supply-toolbar-actions .op-row { padding: 6px 0; }
.supply-toolbar-actions .op-summary { min-width: 0; }
.supply-arrival-matrix, .supply-arrival-summary, .supply-arrival-details, .supply-history-table, .supply-weight-table { width: 100%; }
.table-fill .supply-history-table, .table-fill .supply-weight-table { display: table; }
.supply-history-table { min-width: 1000px; }
.supply-history-value { display: block; font-weight: 650; color: var(--strong); }
.supply-history-meta { display: block; font-size: 12px; color: var(--muted); white-space: normal; }
.supply-cost-table { display: table; min-width: 1220px; width: 100%; }
table.supply-cost-table th, table.supply-cost-table td { padding: 10px 12px; }
.supply-cost-table .supply-cost-product { min-width: 150px; position: sticky; left: 0; background: var(--surface); z-index: 1; }
.supply-cost-table .supply-cost-result { min-width: 140px; position: sticky; right: 0; background: var(--surface); z-index: 1; }
.supply-cost-table thead .supply-cost-product, .supply-cost-table thead .supply-cost-result { z-index: 3; }
@container (max-width: 750px) {
  .supply-toolbar { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .supply-cost-table .supply-cost-result, .supply-cost-table .supply-cost-product { position: static; }
  .tier-chart-layout { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .tier-chart-layout .distribution-chart-frame { height: 240px; }
}
/* Suggested companion rules for supporting templates, to be integrated by CSS owner. */
.packing-scope { flex-basis: 100%; }
@container (min-width: 800px) {
  .cards.packing-cards--paired { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.payment-accounts-table { max-height: none; }
.table-fill .recall-worklist { table-layout: fixed; min-width: 1100px; }
.recall-worklist th, .recall-worklist td { vertical-align: top; }
.recall-worklist .col-main, .recall-worklist .col-wrap { min-width: 0; max-width: none; }
.recall-worklist .contact-cell { white-space: normal; overflow-wrap: anywhere; }
.recall-worklist .email-dim { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.recall-worklist td .recall-followup-form,
.recall-worklist td .recall-note-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; margin-bottom: 6px; }
.recall-followup-form select, .recall-followup-form input,
.recall-note-form input { width: 100%; max-width: 100%; min-width: 0; }
.recall-worklist details > summary { white-space: normal; overflow-wrap: anywhere; }
.relay-panel { display: grid; gap: 16px; margin-top: 16px; }
.table-fill .relay-table { table-layout: fixed; min-width: 1050px; }
.relay-table .col-main { min-width: 0; max-width: none; }
.relay-table .relay-id, .relay-table .relay-tracking { overflow-wrap: anywhere; }
.relay-table .status-tag { white-space: normal; }
.relay-table .contact-cell { white-space: normal; }
.relay-table .fix-tag { display: block; width: fit-content; margin: 3px 0 0; }
.relay-table .relay-acts { justify-content: center; }
.cards.action-cards { align-items: start; }
.action-card-heading { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.action-card-heading h3 { margin: 0; }
.action-card-body { margin-top: 10px; }
.action-card-controls { margin: 0; }
.action-card-controls form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.action-divider { grid-column: 1 / -1; padding-top: 4px; font-weight: 600; }
.table-fill .action-runs-table { table-layout: fixed; min-width: 900px; }
.action-runs-table .col-wrap { min-width: 0; max-width: none; }
.table-fill .referral-accounts-table { table-layout: fixed; min-width: 1120px; }
.table-fill .referral-detail-table { table-layout: fixed; min-width: 1050px; }
.referral-accounts-table .col-main, .referral-detail-table .col-wrap { min-width: 0; max-width: none; }
.referral-accounts-table td.mono { overflow-wrap: anywhere; }
.referral-promoter-id, .referral-usage-amount { display: block; }
.referral-monthly-card { margin-top: 16px; }
.table-fill .recall-warning-table, .table-fill .recall-woke-table { table-layout: fixed; }
.recall-warning-table .col-main, .recall-woke-table .col-main, .recall-woke-table .col-wrap { min-width: 0; max-width: none; }
.recall-warning-table th, .recall-warning-table td,
.recall-woke-table th, .recall-woke-table td { padding-inline: 10px; }
.recall-warning-table .contact-cell { white-space: normal; overflow-wrap: anywhere; }

@container (max-width: 600px) { .action-card-heading { grid-template-columns: minmax(0, 1fr); } }
