:root {
  --bg: #f6faf7;
  --card: #ffffff;
  --border: #dfe9e3;
  --text: #20302a;
  --text-sub: #66766e;
  --primary: #4fae87;
  --primary-dark: #3d8c6b;
  --danger: #d95c4a;
  --ok: #3a9e6d;
  --accent: #d9a44e;
  --sidebar-bg: #eaf5f0;
  --sidebar-hover: #d7ebe0;
  /* 🔴 V8.27（用户 2026-09-16 第 3 条）：「将页面中 4 个区块的高度在现有基础上**再增加 5 分之 1**，
     保持各区块布局对齐、比例一致。」→ 现有系数 0.85 × 1.2 ＝ **1.02**；下限 306 × 1.2 ≈ **367px**
     （各区块仍然**等高**：`.pg-cols` 的 `align-items: stretch` 一次拉齐 —— "比例一致"不用另做）。
     沿革：V8.19 起「撑满页面」、V8.21「两个信息框高度减 1.5/10」→ 系数 0.85；算式依据＝
     视口 100vh − 上方占位（main-wrap padding-top 16 ＋ 顶栏约 43 ＋ 规则提示条约 44 ＋ 工具区约 76）
     − 底部 padding 40 ≈ `100vh − 220px`。
     🔴 V8.21 还把工具区移出了右栏（改到三栏之上的一行）—— 否则右栏里的勾选表会被按钮顶下去，
     比另两个框**又低又矮**，永远做不到"同一高度"。
     ⚠️ 更早那个「固定 372px＝17 行」的高度变量**早已删除、勿加回**（生成器有反向断言：
     整个 `:root` 里都不许再出现它的名字）。 */
  --pg-fill-h: max(367px, calc((100vh - 220px) * 1.02));
  /* ⚠️ V8.27 删掉了「账号列表可见行数」那个变量（旧值 15）—— 现在列表高度**自适应填满模块**
     （见 `.pm-list`），不再按固定行数算高度。**别加回来**（生成器有反向断言，
     注释里也刻意不写它的名字 —— 写了会命中那条断言）。 */
  /* 🔴 V8.22：账号列表的行高与行距。行高 ＝ 条目上/下边框 2px ＋ line-height；
     V8.27 起它同时是**行高的下限**（`minmax(--pm-row, 1fr)`）：内容少则各行均分剩余高度填满框，
     内容多则每行回到这个值并在框内滚动。
     🔴 V8.83／V8.84（用户 2026-09-19 两轮）：「账号字体大一倍」→ 11→22px；随后「有点大了，再小一点」
     → 定为 **18px**（line-height 24）。本值必须**同步**（＝line-height ＋ 上下边框 2）：
     V8.83 是 32px，**现行 26px**。否则大字会被行高裁掉 / 行高下限与实际条目高不一致
     （少一个人就多一条滚动）。**改条目字号时这个值必须跟着改。** */
  --pm-row: 26px;
  --pm-gap: 1px;
  /* 🔴 权限页**三个模块的宽/间距**抽成共用变量（`.pg-cols` 只引用它们）：
     第 3 列是「吃剩余」（flex-grow），故不设变量。⚠️ 改列宽只改这里两行。
     V8.28 起头部那一行**不再是同一套列模板的网格**（那条口径随第 4 区块一起取消），
     所以这里的值**只服务模块行**。 */
  --pg-w1: 208px;    /* 第 1 模块：权限组（单选） */
  --pg-w2: 240px;    /* 第 2 模块：账号-勾选加入 */
  --pg-gap-x: 12px;  /* 模块之间的水平间距（头部行的元素间距也用它） */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 15px;
}

/* ==================== 登录 ==================== */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* ⚠️ V11.34：仅供下面 `.site-foot` 的**绝对定位**当参照物用 —— 不改任何现有布局行为。 */
  position: relative;
}

/* ==================== 页脚备案信息（V11.34） ====================
   🔴 法规要求：《非经营性互联网信息服务备案管理办法》（原信息产业部令第33号）第十三条 ——
     必须在**主页底部的中央位置**标明备案编号，并在其**下方**链接到工信部备案系统
     `https://beian.miit.gov.cn`。不做的，由省通信管理局责令改正 ＋ 罚 5000～10000 元。
   ⚠️ **带不带序号是分省的**：广东省标**主体备案号**（`粤ICP备…号`，**不带**序号）；
     **其他所有省份**必须标**网站备案号**（带 `-1` / `-2` 序号），不可以只标主体备案号。
     本站备案地＝**上海** ⇒ 属于「其他省份」⇒ **必须带序号** ⇒ 现用 `沪ICP备2026048002号-1`。
   ⚠️ **改备案号时三处要一起改**：① `static/style.css`（本注释）② `templates/index.html` 页脚
     ③ 云主机上的 nginx 静态页 `/var/www/yxsk/wip/index.html`（⛔ 不在仓库里，deploy 不会推它）。
   ⚠️ 只做**绝对定位**贴底部 ⇒ **不参与**登录卡片的居中计算 ⇒ 不会动登录页既有几何（也就不会碰到几何夹具）。 */
.site-foot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 16px;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.95;
  color: var(--text-sub);
  padding: 0 16px;
}
.site-foot a {
  color: var(--text-sub);
  text-decoration: none;
}
.site-foot a:hover {
  color: var(--primary-dark);
  text-decoration: underline;
}
.login-card {
  background: var(--card);
  padding: 40px 36px;
  border-radius: 14px;
  width: 360px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.08);
  text-align: center;
}
.login-card h1 { font-size: 22px; margin: 0 0 6px; font-weight: 600; line-height: 1.5; }
.login-company { display: block; font-size: 16px; font-weight: 500; color: var(--text-sub); margin-bottom: 4px; }
.login-system { display: block; }
.login-sub { color: var(--text-sub); font-size: 13px; margin-bottom: 24px; }

/* ==================== 登录页双选项卡 + 注册账号（V8.4，用户 2026-09-15） ====================
   「账号登录」原有元素与样式**一行未改**（避免影响浏览器自动填充与既有逻辑）；
   这里只加：选项卡条 + 「注册账号」表单 + 钉钉校验结果条 + 「注册已关闭」提示。 */
.login-tabs {
  display: flex; justify-content: center; gap: 28px;
  border-bottom: 1px solid var(--border); margin: 2px 0 18px;
}
.login-tab {
  border: none; background: none; padding: 8px 2px 9px; cursor: pointer;
  font-size: 14px; font-family: inherit; color: var(--text-sub);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.login-tab:hover { color: var(--primary); }
.login-tab.active { color: var(--primary); font-weight: 600; border-bottom-color: var(--primary); }
.login-pane { text-align: left; }
/* 手机号 + 「查询」同排；按钮与输入框底对齐（两者都带 14px 下边距） */
/* 🔴 V8.31（用户 2026-09-16 第 3 条）：「账号」+「手机号」两框一列，右侧「钉钉验证」按钮跨两行高度。
   旧的 `.reg-mobile-row`（手机号框与按钮**同一行**）作废、已删，**别加回来**。
   高度怎么来的：`.reg-user-row` 是 flex 行、默认 `align-items: stretch` → 按钮被拉伸到与左列等高；
   左列高 ＝ 账号框 + 14px（两框间距）＋ 手机号框（`:last-child` 把最后一个框的下边距归零，
   否则按钮会比手机号框多出 14px、底边不齐）。 */
.reg-user-row { display: flex; gap: 8px; align-items: stretch; margin-bottom: 14px; }
.reg-user-fields { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.reg-user-fields .text-input:last-child { margin-bottom: 0; }
.reg-lookup-btn {
  flex: 0 0 auto; padding: 10px 14px; font-size: 14px;
  line-height: 1.35; white-space: pre-line; margin-bottom: 0;
}
.reg-hint { font-size: 12px; color: var(--text-sub); line-height: 1.65; margin: -6px 0 12px; }
/* 🔴 V8.30：「注册账号」面板**顶部**那段注册说明（方案一：与上面 `.reg-hint` 同一套灰字样式）。
   只覆写一个上边距 —— `.reg-hint` 的 `-6px` 是为"紧跟输入框下方"设计的（抵消输入框的 14px 下边距），
   而本段是表单**第一个元素**、上面没有输入框，沿用 -6px 会顶到选项卡那条分隔线上。 */
.reg-intro { margin-top: 0; margin-bottom: 14px; }
.reg-verify { font-size: 12.5px; line-height: 1.6; border-radius: 8px; padding: 7px 10px; margin: -6px 0 10px; }
.reg-verify.ok { background: #e6f4ea; color: #137333; }
.reg-closed {
  background: #fce8e6; color: #c5221f; border: 1px solid #f0b4ae; border-radius: 8px;
  padding: 14px 12px; font-size: 13px; line-height: 1.7; text-align: center;
}
/* ⚠️ 登录页「修改密码」面板的样式**不在这里** —— 它必须写在 `.btn` / `.text-input` **之后**
   （同优先级下后来者胜，否则按钮的 padding / font-size 会被 `.btn` 覆盖、倒计时文案会溢出）。
   见文件下方「登录页「修改密码」（V8.42）」那一段。 */

/* ==================== 新人注册页（V8.4）＝ 自助注册账号的着陆页 ====================
   内置「新人注册」组只有「欢迎光临」菜单 + 本页 → 新注册的人登录后只看得到这一页。 */
.newreg-card {
  max-width: 640px; margin: 26px auto; background: var(--card);
  border: 1px solid var(--border); border-radius: 14px; padding: 30px 34px; text-align: center;
}
.newreg-title { font-size: 20px; font-weight: 600; margin-bottom: 14px; }
.newreg-hi { font-size: 15px; margin-bottom: 16px; }
.newreg-tip { font-size: 13.5px; line-height: 2.1; color: var(--text-sub); }
.newreg-tip b { color: #c5221f; }
.newreg-foot { margin-top: 22px; font-size: 12px; color: #9aa0a6; }

.login-remember {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--text-sub);
  cursor: pointer;
  user-select: none;
}
.login-remember input[type="checkbox"] {
  width: 15px;
  height: 15px;
  cursor: pointer;
  accent-color: var(--primary);
}

.text-input {
  width: 100%;
  padding: 12px 14px;
  margin-bottom: 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 15px;
  outline: none;
}
.text-input:focus { border-color: var(--primary); }

.btn {
  padding: 10px 18px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  font-size: 15px;
  color: var(--text);
}
.btn:hover { background: #f2f3f5; }
.btn-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}
.btn-primary:hover { background: var(--primary-dark); }
.btn-ghost { border-color: transparent; background: transparent; }
.btn-ghost:hover { background: rgba(0,0,0,0.06); }
/* 淡绿色按钮（V11.16 用户 2026-09-22：「修改密码，退出，这两个放一行，并填充为淡绿色」）。
   色值**沿用项目既有的绿族**（与 `.reg-verify.ok` / `.app-toast.ok` / `.lin-reg-switch.on` 同套：
   底 `#e6f4ea` / 边 `#a8d5b5` / 字 `#137333`）—— ⛔ 别自己另发明一个绿。
   结构照「淡红按钮」`.wh-btn-soft`：底/边/字三件套 ＋ hover 略深一档。 */
.btn-soft-green { background: #e6f4ea; border-color: #a8d5b5; color: #137333; }
.btn-soft-green:hover { background: #d6ecdd; }
.btn-block { width: 100%; margin-top: 4px; }

.msg { margin-top: 12px; font-size: 14px; color: var(--danger); min-height: 20px; }
.msg.ok { color: var(--ok); }

/* ==================== 登录页「修改密码」（V8.42，用户 2026-09-16） ====================
   「忘记密码也能自助改」：账号 + 钉钉验证码 + 两次新密码。
   布局沿用「注册账号」那套形态（左侧输入列 + 右侧跨两行高度的按钮）——用户已经熟悉它，
   两个面板看起来也是一家人。与注册面板的唯一差别：**按钮定宽**，否则倒计时文案从「获取验证码」
   变成「重新获取 59s」时按钮会变宽、把左边的输入框挤窄（这套布局最容易出的观感问题）。
   🔴 **本段必须写在 `.btn` / `.text-input` 之后**：`.btn` 同样声明了 padding 与 font-size，
      同优先级下**后来者胜**。实测量到过（1440 宽、headless Chromium）：本段若落在 `.btn` 之前，
      按钮实际拿到的是 `.btn` 的 `padding: 10px 18px; font-size: 15px` → 可用内容宽只剩 72px，
      而「重新获取 59s」在 15px 下实宽 88px → **文案压出按钮边界**。
      放在这里则 padding 6px / font-size 13px 生效，同样文案实宽 ≈78px，116px 宽的按钮稳稳放得下。
   ⚠️ 改这一段后请跑 `node scripts/login_geometry_check.js`（它直接量文字实宽、齐平与出界）。 */
.pwd-user-row { display: flex; gap: 8px; align-items: stretch; margin-bottom: 14px; }
.pwd-user-fields { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.pwd-user-fields .text-input:last-child { margin-bottom: 0; }
.pwd-send-btn {
  flex: 0 0 auto; width: 116px; padding: 10px 6px; font-size: 13px;
  line-height: 1.35; white-space: nowrap; margin-bottom: 0;
}
.pwd-send-btn:disabled { opacity: 0.6; cursor: not-allowed; }
/* 说明段与密码规则提示：与「注册账号」面板同一套灰字（同字号 / 同色 / 同行距），
   但**单独起一个语义化类名** —— 两个面板若共用同一个类，测试里 `querySelector` 会取到前一个面板的，
   断言就串了（这是本仓踩过多次的"取第一个元素"陷阱）。 */
.pwd-note { font-size: 12px; color: var(--text-sub); line-height: 1.65; margin: -6px 0 12px; }
.pwd-note-top { margin-top: 0; margin-bottom: 14px; }

/* ==================== 主界面 ==================== */
/* V7.31（用户 2026-09-13）：整页宽度 +1/10 → 1280px → 1408px（内容区实际可用宽度约 1148px） */
.main-wrap { max-width: 1408px; margin: 0 auto; padding: 16px 20px 40px; }

/* V11.16（用户 2026-09-22）整页骨架调整：**原来那三条顶栏规则（`.topbar` / `.topbar h1` / `.topbar-right`）
   已整条删除** —— 它里面的两样都搬进了左侧栏（品牌 → `.side-brand`；配色/当前用户/修改密码/退出 → `.side-user`，
   见下面的规则）。⛔ 别把这三条加回来（顶栏已经不在 DOM 里了，留着就是死规则）。
   ⚠️ `.cur-user` / `.theme-label` / `.theme-dot` / `.theme-select` **仍然在用**（挪到侧栏里了）⇒ 不能删。 */
.cur-user { color: var(--text-sub); font-size: 14px; margin-right: 8px; }
.theme-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 8px;
}
.theme-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--primary);
  box-shadow: 0 0 0 3px var(--sidebar-bg);
}
.theme-select {
  padding: 6px 8px;
  font-size: 13px;
  border-radius: 6px;
  cursor: pointer;
}

/* 左侧导航栏 */
.main-body { display: flex; gap: 20px; align-items: flex-start; }
/* V11.16（用户 2026-09-22）：侧栏改**三段**——顶框（品牌）/ 中间菜单 / 底框（配色·用户·改密·退出）。
   ⚠️ 宽度**不动**（仍是 `flex:0 0 200px`）—— 内容区可用宽度算式不变，几何判据（内容区 ≤1148px）不受影响。 */
.sidebar {
  flex: 0 0 160px;   /* V11.25（用户 2026-09-22）：200 → 160（左右空白各 30px，相等）。⚠️ 这条会改内容区可用宽度 ⇒ 几何基线要同步 */
  background: var(--sidebar-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* ---- 顶框：品牌（用户要求「字体缩小，一行显示」）----
   ⚠️ 可用内宽只有约 160px（侧栏 200 − 左右 padding 20 − 边框 2 − 本框 padding 16）
      ⇒ 字号必须小到装得下「仓储物流工作台 V11.15」（实测 12px 下约 131px）＋ `nowrap` 保一行；
      `text-overflow:ellipsis` 只是**兜底**（以后版本号变长时不撑破布局），正常不会触发。
   ⛔ 别把 `.topbar h1` 那条 20px 的规则照搬过来（200px 装不下会换行成两行）。 */
.side-brand {
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 10px;
  /* ⚠️ V11.25：左右内边距 8 → 6（侧栏收窄到 160 后要抠出这几像素给品牌文字） */
  padding: 6px 6px;
  background: #fff;
}
/* 🔴 V11.21（用户 2026-09-22）：「仓储物流工作台这几个字可以大一些，和版本号一起，填充整行。」
   ⇒ 字号 12px → **14px**，并改成 **flex 两端对齐**（工作台名靠左、版本号靠右）把整行占满。
   ⚠️ **14px 已经是上限附近**：那一行文字可用宽度约 **160px**，而「仓储物流工作台 V11.20」
      14px 实测约 **152px**（放得下、留约 8px 余量）、15px 约 **162px**（会溢出、版本号被截）。
      ⇒ 想再大**必须先加宽侧栏**（`flex:0 0 200px`），而那会改内容区可用宽度、
        所有几何档位的判据都要重标 —— ⛔ 别只改这个字号。
   ⚠️ `white-space:nowrap` 必须留着：两端对齐靠的是 flex，**一旦换行"两端"就不成立**了。
   ⚠️ 这里**去掉了 `text-overflow:ellipsis`**（它在 `display:flex` 的容器上**不生效** ——
      `text-overflow` 只作用于块容器的行内内容）。换成 `overflow:hidden` 作兜底裁剪。 */
.side-brand h1 {
  margin: 0; font-size: 11.5px; font-weight: 600; line-height: 1.5;
  display: flex; justify-content: space-between; align-items: baseline; gap: 4px;
  white-space: nowrap; overflow: hidden;
}
/* 🔴 V11.25（用户 2026-09-22 拍板）：侧栏收窄到 **160px** 后，那行文字只剩约 **124px** 可用，
   而 14px 字号的「仓储物流工作台 V11.24」实测要 **141.8px** ⇒ **装不下、版本号会被裁掉**。
   要在 160px 里同时保住「14px ＋ 一行」数学上不可能（至少需 182px 侧栏）；
   用户在三个方案里选了「**缩小字号**」⇒ **14px → 11.5px**（缩到 11.5px 后整块约 117px，装得下）。
   ⛔ 别再把字号调回 14px（那会让版本号被裁掉）；要放大就**先加宽侧栏到 182px 以上**。
   ⚠️ 版本号跟着一起缩到 11.5px（否则它比正文字还大、且总宽超限）。
   ⚠️ **余量是 0**（文字 124 ＝ 可用 124，实测）⇒ 版本号一旦变成 **7 个字符**（如 `V11.100`）
      就会超出几个像素被裁（`overflow:hidden` 只裁不破版）—— 那时**加宽侧栏或再缩字号**。 */
.side-brand .version-tag { font-size: 11.5px; margin-left: 0; flex: 0 0 auto; }
/* ---- 中间：一级菜单。**唯一可滚动的一段** ----
   🔴 滚动条必须在这一段里（⛔ 别留在 `.sidebar` 上）：菜单一长时只滚菜单，
      上面的品牌与下面的「退出」永远可见。
   ⚠️ `min-height:0` 是这个写法的**必需项** —— flex 子项默认 `min-height:auto`，
      少了它内层不会收缩、滚动条根本不出现，整条侧栏会被撑高。 */
.side-nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* ---- 底框：配色 / 当前用户 / 修改密码 / 退出（**竖排**：侧栏只有 200px，横排放不下）----
   ⚠️ 这几样原来在顶栏是横排且带 `margin-right`，搬进来必须清零（见下面两条覆盖）。 */
.side-user {
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px;
  background: #fff;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}
/* 覆盖顶栏时代的横向间距（`.cur-user{margin-right:8px}` / `.theme-label{margin-right:8px}`）——
   `.side-user .x`（两个类）特异性高于 `.x`（一个类），不用 `!important`。 */
/* V11.21（用户 2026-09-22）：「登陆账号和权限组显示在行中间」⇒ 水平居中。
   ⚠️ 只是**文字居中**，那一行本身仍是整宽的（`.side-user` 是 `align-items:stretch`）。
   ⛔ 别加 `white-space:nowrap` —— 名字+组名长了要能自然换行（V11.16 定的口径）。 */
.side-user .cur-user { margin-right: 0; font-size: 13px; line-height: 1.4; text-align: center; }
.side-user .theme-label { margin-right: 0; }
/* 🔴 V11.24（用户 2026-09-22）：「界面中的颜色选择框，减少宽度，能显示即可，不要有空白区」。
   ⇒ 由 `width:100%`（铺满整行 ⇒ 选中项文字右边一大片空白）改成 **按内容自适应**。
   ⚠️ `<select>` 的 `width:auto` ＝「按**最宽的那个选项**」定宽（本项目主题名最长 5 个汉字 ≈ 65px）
      ⇒ 换主题名（更长/更短）会自动跟着变，不用改这里。⛔ 别写死像素。
   ⚠️ 仍留 `max-width:100%` 兜底（万一以后加了很长的主题名，不会把盒子顶破）。 */
.side-user .theme-select { width: auto; max-width: 100%; padding: 6px 8px; font-size: 13px; }
/* 「修改密码 / 退出」**同一行**、各占一半（V11.16 用户 2026-09-22）。
   ⚠️ 这里**不是** `.side-user .btn` 而是 `.side-user-row .btn` —— `.side-user` 是竖排容器，
      按钮直接挂它下面就会各占一行；包一层 `.side-user-row`（flex 行）才是一行。
      ⛔ 别把 `width:100%` 写回来（两个 100% 会把行撑破、又变回两行）。 */
.side-user-row { display: flex; gap: 6px; }
/* 🔴 V11.24（用户 2026-09-22）：「修改密码、退出，这两个按钮减少宽度，能显示文字即可」
   ⇒ 由 `flex:1 1 0`（各占一半 ⇒ 按钮被拉得很宽、字右边一片空白）改成 **按文字宽自适应**
     （`flex:0 0 auto`）⇒ 按钮宽度＝文字＋内边距（实测「修改密码」约 66px、「退出」约 40px）。
   ⛔ 别写死像素：`flex:0 0 auto` ＋ `white-space:nowrap` 下宽度本来就跟着文字走。
   ⚠️ 与 V11.17 的口径**相反**（那时用户要「各占一半」）⇒ 以本版为准；
      `shell_wiring_test` 第 9 节与几何 ㉒ 的对应断言都已同步改判。 */
.side-user-row .btn { flex: 0 0 auto; min-width: 0; width: auto; padding: 7px 8px;
  font-size: 13px; white-space: nowrap; }
/* 🔴 V11.25（用户 2026-09-22）：「一级菜单栏宽减少到合适的宽度，**右边空白区和左边一样**」。
   ⇒ 内容（图标＋文字）**居中**。⚠️ 这条不是装饰、是**必需项**：侧栏收窄后菜单项仍有富余宽度，
      而 flex 默认 `flex-start` 会把富余**全堆在右边**（实测 160px 时左 16 / 右 32，还是不等）；
      居中后富余左右均分 ⇒ 两边相等（实测左 24 / 右 24）。
   ⚠️ 也因为居中，以后菜单名长短变化时**仍然自动两边相等**，不用再调侧栏宽度。 */
.nav-parent {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 16px;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  color: var(--text);
  border-radius: 22px;
  margin-bottom: 6px;
  transition: background 0.15s, color 0.15s;
}
.nav-parent:hover { background: var(--sidebar-hover); }
.nav-parent.active {
  background: var(--primary);
  color: #fff;
  font-weight: 600;
}
.nav-icon { width: 20px; height: 20px; flex: 0 0 20px; }

/* 右侧选项卡 */
.tabs { display: flex; gap: 4px; border-bottom: 2px solid var(--border); margin-bottom: 20px; flex-wrap: wrap; }
.tab {
  /* V6.81：左右内边距 24px→16px——「劳务管理」9 个选项卡原总宽 1049px 超出内容区 1020px 会被迫换行，
     收紧后总宽约 905px，常规窗口均可单行显示（上下内边距不变，故选项卡吸顶高度保持 48px）。 */
  padding: 12px 16px;
  border: none;
  background: transparent;
  font-size: 15px;
  cursor: pointer;
  color: var(--text-sub);
  border-bottom: 3px solid transparent;
  margin-bottom: -2px;
  white-space: nowrap;
}
.tab:hover { color: var(--text); }
.tab.active {
  color: var(--primary);
  border-bottom-color: var(--primary);
  font-weight: 600;
}
.content-area { flex: 1; min-width: 0; }
.empty-page {
  padding: 60px 20px;
  text-align: center;
  color: var(--text-sub);
  font-size: 16px;
}

/* ==================== 扫码提示条 ==================== */
.scan-tip {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 16px;
  margin-bottom: 14px;
  background: #eaf6ef;
  border: 1px solid #c8e6d5;
  border-radius: 8px;
  font-size: 14px;
  color: #2f6b4f;
  line-height: 1.6;
}
.scan-tip::before {
  content: "";
  flex: 0 0 10px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #4fae87;
  box-shadow: 0 0 0 3px #d6eee0;
}

/* ==================== 扫码行 ==================== */
.scan-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}
.scan-label {
  font-size: 18px;
  font-weight: 600;
  white-space: nowrap;
}
.scan-input {
  flex: 0 0 220px;
  padding: 12px 14px;
  font-size: 20px;
  border: 2px solid var(--border);
  border-radius: 8px;
  text-align: center;
  outline: none;
}
.scan-input:focus { border-color: var(--primary); }
.result-input {
  flex: 1;
  padding: 12px 14px;
  font-size: 18px;
  border: 2px solid var(--border);
  border-radius: 8px;
  text-align: center;
  background: #fafbfc;
  color: var(--text);
}

/* ==================== 单据字段 ==================== */
.order-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 18px;
}
.field {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
}
.field label {
  display: block;
  font-size: 13px;
  color: var(--text-sub);
  margin-bottom: 6px;
}
.value {
  font-size: 18px;
  /* V7.34（用户 2026-09-13）：原来写死 height:48px —— 单据编号/数量只有一行时会留一截空白。
     改为自适应：最低一行（24px）、最多两行（48px，超出裁掉，与原口径一致）。 */
  min-height: 24px;
  max-height: 48px;
  line-height: 24px;
  white-space: pre-wrap;
  word-break: break-all;
  overflow: hidden;
}
.value-wrap { font-size: 16px; }

/* V7.34 建 / V7.35 改宽 / V7.38（用户 2026-09-13）：「扫码结果」的显示框**与标签同一行**（label 在左、输入框占剩余宽度）。
   ⚠️ 别再改成标签独占一行（V7.34~V7.37 是那样，用户已改回）。 */
.scan-result-block {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}
.scan-result-block .scan-label { white-space: nowrap; }
.scan-result-block .result-input { flex: 1; width: auto; box-sizing: border-box; }

/* ==================== 信息框 ==================== */
.info-box {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 18px;
  margin-bottom: 16px;
}
.info-box h3 {
  margin: 0 0 10px;
  font-size: 15px;
  color: var(--text-sub);
  font-weight: 600;
}
.info-pre {
  margin: 0;
  font-family: inherit;
  font-size: 17px;
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-all;
}

/* 入库计件：货品信息 / 单据信息 左右并排，动态等高 */
.rk-info-row {
  display: flex;
  gap: 16px;
  align-items: stretch;
}
.rk-info-row .info-box {
  flex: 1;
  margin-bottom: 0;
}

/* V7.35（用户 2026-09-13）：入库计件「单据信息」放在「货品信息」**上面**（货品信息下移）
   → 由「左右并排」改为**纵向堆叠**，两框各自独立高度（不再被拉伸等高）→ 内容变短框就真的变矮。
   ⚠️ 别为了"省空间"改回 row（用户要"上面/下移"）。
   ⚠️ 只作用于入库计件页；**卸货计件页仍是原来的左右并排**（用户未要求改）。 */
#page-ruku .rk-info-row { flex-direction: column; }
/* 栏宽沿革：V7.36 定为「并排时各自的半宽」`calc(50% - 8px)` → V7.38 再 +1/4 `calc(62.5% - 10px)`
   → **V7.40（用户 2026-09-13）改为占满左列 `width: 100%`**（两框仍上下堆叠、各自独立高度）。
   ⚠️ 别删这个 width —— 删了在 flex 纵向容器里靠 `align-items: stretch` 也是满宽，但显式写出更稳、也便于日后回退。
   ⚠️ 只作用于入库计件页；**卸货计件页仍是原来的左右并排**（用户未要求改）。 */
#page-ruku .rk-info-row .info-box { flex: 0 0 auto; width: 100%; }

/* ==================== 入库计件：左（显示区）/ 中（计件操作员）/ 右（读取未登记）（V7.38） ====================
   用户 2026-09-13 指定：**计件操作员在中间，读取未登记在最右侧**（原来是右侧上下两张卡）。 */
.rk-flex { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.rk-left { flex: 1 1 620px; min-width: 480px; }
.rk-mid { flex: 0 0 156px; }
.rk-right { flex: 0 0 300px; }
.rk-card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
.rk-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.rk-card-title { font-size: 15px; font-weight: 600; color: var(--text); white-space: nowrap; }
.rk-card-note { font-size: 12px; color: var(--text-sub); }
.rk-card-flex { flex: 1; }
.rk-btn-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* V7.38（用户 2026-09-13）：日期下拉「加大一些、与按钮同高」→ 本行内按钮与下拉统一 40px 高、字号 14px */
.rk-btn-row .btn { padding: 0 14px; height: 40px; }
.rk-range { padding: 0 10px; height: 40px; font-size: 14px; border: 1px solid var(--border); border-radius: 8px;
  background: #fff; color: var(--text); outline: none; }
.rk-mini { padding: 2px 9px; font-size: 12px; }
.rk-progress { margin-top: 10px; font-size: 13px; color: var(--text); }
.rk-counts { margin-top: 4px; font-size: 12px; color: var(--text-sub); line-height: 1.6; }
/* 勾选框：一行一人、不滚动（用户 2026-09-13 明确「不要滚动，全部显示」） */
.rk-ops-wrap { font-size: 14px; line-height: 2.0; }
.rk-ops-wrap label { display: block; white-space: nowrap; }
.rk-ops-wrap input[type="checkbox"] { margin-right: 6px; }
/* V7.53（用户 2026-09-14）：**勾选中的计件操作员，姓名加粗 + 蓝色**
   三页（入库 / 包装 / 卸货）的勾选框容器**共用** `.rk-ops-wrap` ⇒ 一条规则覆盖三页。
   ⚠️ 姓名必须包在 `.ops-name`（`app.js` 三处渲染里已包）——裸文本节点用 `:checked +` 选不中。
   ⚠️ 蓝色沿用页面现有蓝 `#0b57d0`（与「货品清单」按钮、范围按钮选中态同色，别另起一种蓝）。 */
.rk-ops-wrap input[type="checkbox"]:checked + .ops-name { color: #0b57d0; font-weight: 700; }

/* ==================== 入库计件：单据编号 / 单据数量（V7.41 建 → V7.42 改，用户 2026-09-13） ====================
   现行口径（V7.42，用户 19:09）：
     ① 「文字标签继续放在显示框里面」→ 标签回到 `.field` 卡片**内部**（框内上方），
        **撤回 V7.41 的「标签移到框外」方案**（`.rk-field-box` 那一层已删）。
     ② 「栏宽增加，将左侧填满」→ 栅格 4 列 → **2 列**：每项 **50%**（＋12px 列距 = 正好占满整行/整个左列）。
   ⚠️ 全部挂在 `.rk-order-grid` 上，**包装计件 / 卸货计件的 .order-grid 不受影响**（仍是 4 列、标签在卡片内）。
   ⚠️ V7.41 的「与扫码输入等高（`--rk-ctrl-h`）」**已随标签回到框内而作废**：框里多了一行标签，
      自然高度必然高于扫码输入框；要压矮只能把标签改成同行内联，需用户另行确认。 */
.rk-order-grid { grid-template-columns: repeat(2, 1fr); }
.rk-order-grid .value {
  font-size: 18px;
  line-height: 24px;
  min-height: 0;
  max-height: 24px;        /* 单行：两个框高度一致、不留空白高（与 V7.34 口径一致） */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==================== 包装计件：2×2 字段 + 右侧「计件操作员」（V7.47，用户 2026-09-14） ====================
   用户 2026-09-14 要求（计件登记 · 包装计件）：
     ① 「货品名称和单据数量，分别放在生产单号和货品编码的下面」
        → 字段栅格 4 列 → **2 列 × 2 行**（DOM 顺序 生产单号 → 货品编码 → 货品名称 → 单据数量，
          第 3/4 项自然落到第二行第 1/2 列）。货品名称保留 value-wrap（可折行），
          未套用 .rk-order-grid 的单行省略口径（那是**单据编号/数量**的要求）。
     ② 「增加一个和入库计件一样的计件操作员模块，模块放在右侧」
        → 右侧新增一张卡，**复用**入库计件的 .rk-card / .rk-ops-wrap / .rk-mini 样式，
          名单也同一份（权限组「计件操作组」）。
     ③ 「生产信息展示框的宽度，调整到和货品名称、单据数量两个模块加总的宽度」
        → 那两项就是栅格第二行的两列，加总宽度＝栅格整行宽度；本页 .bz-left 内
          「扫码行 / 字段栅格 / 生产信息框」三者均满宽 → 三者左右边界对齐、宽度完全相等。
   ⚠️ 只挂在 `.bz-*` 上：卸货计件的 `.order-grid`（4 列）与页面骨架不受影响。 */
.bz-flex { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.bz-left { flex: 1 1 620px; min-width: 480px; }
.bz-ops-col { flex: 0 0 190px; }
.bz-order-grid { grid-template-columns: repeat(2, 1fr); }
/* ③ 显式写出满宽（与栅格同宽）——即便 flex 纵向容器下默认也是满宽，写出来便于日后回退与自检 */
#page-baozhuang .info-box { width: 100%; box-sizing: border-box; }

/* ==================== 卸货计件：右侧「计件操作员」+ 单据编号同行 + 信息框堆叠（V7.49，用户 2026-09-14） ====================
   用户 2026-09-14 要求（计件登记 · 卸货计件）：
     ① 「增加计件操作员模块，放在最右侧，行单据编号同一行的位置」
        → 扫码行下面起左右分栏：左列第一行就是「单据编号」，右列（.xh-ops-col）同顶
          （容器 align-items: flex-start）→ 两卡**顶端对齐** = "与单据编号同一行"。
     ② 「托盘数量展示框减少一半宽度」→ `#xh-tray-input` 140px → **70px**（见上面 .xh-tray-input）。
     ③ 「单据编号，将文字标签放在外面，展示框和扫码输入一样的高度，标签和展示框放一行」
        → 不再是 .field 卡片（标签在框内），改为一行 flex：`[标签][展示框]`；
          展示框与 `.scan-input` **逐项同构造**（padding 12px 14px / font-size 20px / line-height 24px /
          border 2px / 全局 border-box）→ 高度按构造必然相等，不依赖估出来的像素值。
          ⚠️ `.value` 全局有 `max-height: 48px`（比同构造的 52px 小）→ 必须显式 `max-height: none` 解除，
             否则会被夹到 48px 而"差 4px 说不清"。同时把该页扫码输入的 line-height 也钉成 24px，
             两块引用的是同一组数值（而不是各自依赖字体 normal 行高）。
     ④「单据信息放在货品信息框上面」→ `#page-xiehuo .rk-info-row { flex-direction: column; }`（DOM 已是单据信息在前）。
   ⚠️ 只挂 `#page-xiehuo` / `.xh-*`：包装计件的 .bz-*、入库计件的 .rk-* 不受影响。 */
.xh-flex { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.xh-left { flex: 1 1 620px; min-width: 480px; }
.xh-ops-col { flex: 0 0 190px; }
.xh-order-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}
.xh-order-label {
  font-size: 18px;
  font-weight: 600;
  white-space: nowrap;
}
/* V7.52（用户 2026-09-14「单据编号框高度，固定下来」）：
   不再依赖"同构造推出来的高度"，改成**两处引用同一个高度变量**——
   `--xh-ctrl-h` 定义在 `#page-xiehuo`（只作用于卸货页，包装/入库的扫码框不受影响），
   「单据编号」展示框与「扫码输入」都吃这个值 ⇒ 高度**写死且必然相等**；
   空值不会再塌、也不会因字体/浏览器的行高差异而变（真机嫌高矮只改这一个数）。
   52px ＝ padding 12×2 + 行高 24 + 边框 2×2（全局 border-box，别改 padding/边框却忘改它）。 */
#page-xiehuo { --xh-ctrl-h: 52px; }

/* 展示框：与 .scan-input 同构造（同 padding/字号/行高/边框）+ 高度由变量写死 */
.xh-order-value {
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 14px;
  font-size: 20px;
  line-height: 24px;
  height: var(--xh-ctrl-h);          /* V7.52：高度固定（三处都钉住 → 内容多少都不变） */
  min-height: var(--xh-ctrl-h);
  max-height: var(--xh-ctrl-h);
  /* ⚠️ 高度沿革（别改回去）：
     V7.49 `min-height: 0`（想解除 .value 的 24px 约束）→ **空值时 flex 容器没有子项、内容高度为 0**
       → 框只剩 padding+border ＝ 28px，扫出单号又蹦回 52px，用户当场看出"不一样高"；
     V7.51 改 `min-height: 24px`（＝行高）兜住，靠"同构造"推出 52px（仍依赖浏览器行高行为）；
     V7.52 用户要求「固定下来」→ 与 `.scan-input` **共用 `--xh-ctrl-h`**，高度写死。
     三处 height/min/max 都用变量：少写一条，内容超长或空值都可能把框撑高/塌矮。 */
  border: 2px solid var(--border);
  border-radius: 8px;
  background: #fafbfc;
  color: var(--text);
  display: flex;
  align-items: center;      /* 单行居中，与 input 的文字基线观感一致 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 该页扫码输入：行高钉死 + 高度同样吃 `--xh-ctrl-h` → 与上面的展示框**同一个值、同一个高度**（V7.52） */
#page-xiehuo .scan-input { line-height: 24px; height: var(--xh-ctrl-h); }
#page-xiehuo .rk-info-row { flex-direction: column; }
#page-xiehuo .rk-info-row .info-box { flex: 0 0 auto; width: 100%; }

/* 卸货计件：托盘数量 + 卸货类型 补录区 */
.xh-edit-box {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 18px;
  margin-bottom: 16px;
}
.xh-edit-label {
  font-size: 14px;
  color: var(--text-sub);
  font-weight: 600;
  white-space: nowrap;
}
.xh-tray-btns {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.xh-tray-btn {
  min-width: 40px;
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--text);
  border-radius: 6px;
  cursor: pointer;
  font-size: 15px;
}
.xh-tray-btn:hover { border-color: var(--primary); color: var(--primary); }
.xh-tray-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
  font-weight: 600;
}
.xh-tray-input-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  flex-wrap: wrap;
}
.xh-tray-input {
  /* V7.49（用户 2026-09-14）：「托盘数量展示框减少一半宽度」→ 140px → 70px */
  width: 70px;
  margin-bottom: 0;
}
.xh-hint {
  font-size: 13px;
  color: var(--text-sub);
}
.xh-type-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.xh-type-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}
.xh-save-tip {
  display: none;
  font-size: 15px;
  color: var(--ok);
  font-weight: 600;
  padding: 8px 12px;
  background: #eefaf1;
  border: 1px solid #bfe6c8;
  border-radius: 6px;
  width: 100%;
}

/* 卸货计件保存：即时校验错误提示 */
.xh-err {
  display: none;
  font-size: 14px;
  color: var(--danger);
  font-weight: 600;
  padding: 8px 12px;
  background: #fdecea;
  border: 1px solid #f5c6c0;
  border-radius: 6px;
  width: 100%;
}
.xh-tray-btns.invalid .xh-tray-btn,
.xh-tray-input.invalid,
.xh-type-btn.invalid,
.xh-edit-label.invalid {
  border-color: var(--danger) !important;
}
.xh-type-btn.invalid { color: var(--danger); }
.xh-edit-label.invalid { color: var(--danger); }

/* ==================== 弹窗 ==================== */
.modal-mask {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.modal-card {
  background: #fff;
  padding: 26px 28px;
  border-radius: 12px;
  width: 380px;
}
.modal-card h2 { margin: 0 0 18px; font-size: 18px; font-weight: 600; }
.modal-wide { width: 560px; }
.modal-btns { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }

/* ===== 重置密码弹窗（V7.02） ===== */
.rp-row { display: flex; gap: 8px; align-items: flex-start; }
.rp-row .text-input { flex: 1 1 auto; margin-bottom: 8px; }
.rp-row .btn { flex: 0 0 auto; padding: 11px 12px; font-size: 13px; }
.rp-confirm { display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; margin-bottom: 8px; }
.rp-confirm input { width: 15px; height: 15px; cursor: pointer; }
.modal-tip { font-size: 14px; font-weight: 600; margin-bottom: 10px; color: var(--text); }
.modal-warn { font-size: 12px; line-height: 1.6; color: #a15c00; background: #fff8e6;
  border: 1px solid #ffe1a8; border-radius: 8px; padding: 8px 10px; margin-bottom: 4px; }
/* ===== 保存结算确认弹窗（V8.32，费用待确认页） =====
   🔴 两个扣时输入框并排，**必须把 `.text-input` 的 14px 下边距归零** —— 那是给"竖排表单"留的，
   这里是左右并排、下面紧跟预览卡，留着会把底下的卡顶偏（与 V8.30/V8.31 同族的坑）。 */
.lsc-cuts { display: flex; gap: 14px; margin: 12px 0 8px; }
.lsc-cuts label { flex: 1 1 0; min-width: 0; font-size: 13px; color: var(--text-sub); }
.lsc-cuts .text-input { width: 100%; margin: 6px 0 0; text-align: center; font-size: 16px; font-weight: 600; }
#lsc-preview { margin: 10px 0 0; }
/* V8.81（用户 2026-09-19）：弹窗里的「统一备注」输入区 —— 单行块级（与上面并排的扣时框区分开）。
   ⚠️ 同样要把 `.text-input` 自带的 14px 下边距归零/改小：那是给"竖排表单"留的，
      留着会把下面的按钮行顶偏（同 `.lsc-cuts` 那条注释里记的坑）。 */
.lsc-note-wrap { margin: 12px 0 0; }
.lsc-note-wrap label { display: block; font-size: 13px; color: var(--text-sub); margin-bottom: 6px; }
.lsc-note-wrap .text-input { width: 100%; margin: 0; box-sizing: border-box; font-size: 13px; }
/* 账号管理·手机号列（V7.02） */
.phone-input { width: 122px; padding: 5px 8px; margin-bottom: 0; font-size: 13px; }
/* 账号管理·密码列（V7.02）：仅一个「重置密码」按钮 */
.pwd-cell .link-btn { margin-left: 0; }

.select {
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 15px;
  background: #fff;
}

.user-add-row { display: flex; gap: 10px; margin-bottom: 16px; }
.user-add-row .text-input { flex: 1; margin-bottom: 0; }

.user-table { width: 100%; border-collapse: collapse; }
.user-table th, .user-table td {
  text-align: left;
  padding: 10px 8px;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.user-table th { color: var(--text-sub); font-weight: 600; }
/* V6.91：表头「人事确认」旁的「全部选择」按钮（把当前列表的上班记录一次标为已到岗） */
.cf-all-btn {
  margin-left: 6px; padding: 1px 8px; font-size: 12px; font-weight: 500;
  color: var(--primary); background: #fff; border: 1px solid var(--border);
  border-radius: 4px; cursor: pointer; white-space: nowrap; vertical-align: middle;
}
.cf-all-btn:hover { background: #f1faf5; border-color: var(--primary); }
.cf-all-btn:disabled { color: #999; border-color: #ddd; background: #f7f7f7; cursor: default; }
.del-btn { color: var(--danger); cursor: pointer; background: none; border: none; font-size: 14px; }
.del-btn:hover { text-decoration: underline; }

/* ==================== 版本号 ==================== */
.version-tag {
  font-size: 14px;
  color: var(--text-sub);
  font-weight: 400;
  margin-left: 6px;
}

/* ==================== 版本记录 ==================== */
.changelog-wrap {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 20px;
}
.changelog-item {
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.changelog-item:last-child { border-bottom: none; }
.changelog-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 8px;
}
.changelog-ver {
  font-size: 17px;
  font-weight: 600;
  color: var(--primary);
}
.changelog-date { font-size: 13px; color: var(--text-sub); }
.changelog-item ul { margin: 0; padding-left: 20px; }
.changelog-item li { font-size: 15px; line-height: 1.8; }

/* ==================== 登录记录 ==================== */
.log-ok { color: var(--ok); font-weight: 600; }
.log-fail { color: var(--danger); }

/* ==================== 审核人为空提醒 ==================== */
.reviewer-warn { color: var(--danger); font-weight: 600; }

/* ==================== 账号状态（停用/启用） ==================== */
.status-group { display: inline-flex; gap: 10px; align-items: center; }
.status-label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
  cursor: pointer;
  color: var(--text);
}
.status-label input[type="radio"] { cursor: pointer; }
.status-disabled { color: var(--danger); font-weight: 600; }

/* ==================== 分页控件 ==================== */
.pagination {
  display: flex;
  gap: 6px;
  align-items: center;
  justify-content: center;
  margin-top: 16px;
  flex-wrap: wrap;
}
.page-btn {
  min-width: 32px;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
}
.page-btn:hover:not(:disabled) { border-color: var(--primary); color: var(--primary); }
.page-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}
.page-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ==================== 权限说明 ==================== */
.permission-wrap { display: flex; flex-direction: column; gap: 14px; }
.perm-item {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 20px;
}
.perm-head {
  font-size: 17px;
  font-weight: 600;
  margin-bottom: 12px;
  color: var(--primary);
}
.perm-me {
  font-size: 12px;
  color: var(--ok);
  font-weight: 600;
  margin-left: 6px;
}
.perm-row {
  display: flex;
  gap: 12px;
  padding: 6px 0;
  font-size: 14px;
  line-height: 1.7;
}
.perm-key {
  flex: 0 0 130px;
  color: var(--text-sub);
  font-weight: 600;
}
.perm-val { flex: 1; color: var(--text); }

/* ==================== 升级中遮罩 ==================== */
.upgrade-mask {
  position: fixed;
  inset: 0;
  background: rgba(246, 250, 247, 0.97);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 300;
}
.upgrade-card {
  text-align: center;
  padding: 40px 48px;
}
.upgrade-spinner {
  width: 40px;
  height: 40px;
  margin: 0 auto 20px;
  border: 4px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: upgrade-spin 1s linear infinite;
}
@keyframes upgrade-spin {
  to { transform: rotate(360deg); }
}
.upgrade-card h2 {
  margin: 0 0 10px;
  font-size: 22px;
  font-weight: 600;
  color: var(--text);
}
.upgrade-card p {
  margin: 0 0 4px;
  font-size: 15px;
  color: var(--text-sub);
}
.upgrade-sub {
  font-size: 13px;
  color: var(--text-sub);
}

/* ==================== 版本变更横幅（V8.77，用户 2026-09-18） ==================== */
/* 用途：部署新版本后，**已打开的旧标签页**（SPA 只在首次加载取 HTML）要能自己发现并提示刷新。
   完整背景与不变量见 static/app.js::checkUpgrade 注释。
   ⛔ 非阻塞：固定在视口最顶部的一条**细带**（不遮盖内容、不打断正在作业的人），刷新**由用户点**。
   ⛔ 不要动上面的 .upgrade-mask（那是"服务不可达"的全屏遮罩，两回事，语义不同）。
   z-index：本仓现有最高是 .app-toast 的 1200（另有 .kd-float 1100 / 弹窗 1000 / 遮罩 300 /
   吸顶 .tabs 30）⇒ 横幅取 1300，保证压在**所有**现有浮层之上、不被吸顶表头/选项卡盖住。 */
.ver-banner {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: 1300; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 6px 12px;
  background: #fef3c7;            /* 浅底深字的"提示色"（与仓库既有提示风格一致） */
  color: #92400e;                 /* ⛔ 不用红色 —— 这不是错误，只是提醒 */
  font-size: 13px; line-height: 1.5;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);   /* 一条浅分隔，与下方内容区分 */
}
.ver-banner-text { flex: 0 1 auto; min-width: 0; }

/* ==================== 计件审核 ==================== */
.review-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
  flex-wrap: wrap;   /* V7.16：包装审核工具栏元素变多，窄屏允许折行 */
}
.review-count {
  font-size: 13px;
  color: var(--text-sub);
}
/* V8.6（用户 2026-09-15）：系统管理 · **账号明细** 的说明文字 —— 位置＝「开放注册 / 关闭注册」
   按钮**之后**，固定折成**两行**：给足 flex 基准宽度占满按钮右侧剩余宽度（窄屏才继续往下折），
   再限一个最大宽度，免得超宽屏被拉成一长条、反而失去"两行"的观感。
   ⚠️ 别的页面（包装审核 / 临时工账号 …）也共用 `.review-toolbar`，所以这里必须用 `#page-users` 限定。 */
#page-users .review-toolbar .user-reg-tip {
  flex: 1 1 560px;
  min-width: 0;
  max-width: 900px;
  line-height: 1.75;
  white-space: normal;
}
/* ==================== V8.51：账号明细「钉钉账号校验」智能按钮（用户 2026-09-17）====================
   位置＝**紧随「注册开关」按钮之后**（用户点名），形态与旁边那个同类（一个按钮多态、点一下动一次）。
   ⛔ 两条别改：
     ① **单独一套类名 `.acc-ding-check`**，不与旁边 `.lin-reg-switch` 共用 —— 共用的话测试里
        `querySelector` 会取到前一个按钮（本仓踩过多次的"取第一个元素"陷阱）；
     ② **"正常"用中性色，别用绿** —— 绿在这套系统里表示「已开放/已通过」，与旁边那个注册开关
        （绿＝开放注册）撞车；这里"正常"＝中性蓝边、「有需处理」＝淡红、「运行中」＝淡蓝。
   文案是**动态的**（倒计时 / n/N / 需处理数），所以必须 `white-space: nowrap` 免得换行把工具栏撑高。 */
.acc-ding-check { white-space: nowrap; }
.acc-ding-check.idle { background: #fff; color: #0b57d0; border-color: #cfd8e3; }
.acc-ding-check.idle:hover { background: #f4f8ff; }
.acc-ding-check.warn { background: #fce8e6; color: #c5221f; border-color: #f0b4ae; }
.acc-ding-check.warn:hover { background: #f9dcd9; }
.acc-ding-check.busy { background: #eef4ff; color: #1d4ed8; border-color: #b6d4fe; cursor: progress; }
.acc-ding-check:disabled { opacity: .85; }
/* 校验结果一行：只在本轮有需处理账号时出现（默认 display:none 由 DOM 控制） */
.acc-ding-msg {
  font-size: 13px; line-height: 1.7; color: #b3261e;
  background: #fdf3f2; border: 1px solid #f0b4ae; border-radius: 8px;
  padding: 8px 12px; margin: 0 0 10px;
}
.acc-ding-msg b { font-weight: 600; }
.review-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.review-item {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-left: 4px solid var(--primary);
  border-radius: 10px;
  padding: 12px 16px;
}
.review-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.review-l1,
.review-l2,
.review-l3 {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 22px;
  row-gap: 4px;
}
.review-l2,
.review-l3 {
  column-gap: 18px;
}
.rf {
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
  line-height: 1.5;
}
.rf em {
  font-style: normal;
  font-size: 12px;
  color: var(--text-sub);
  margin-right: 5px;
}
.rf.no {
  font-size: 16px;
  font-weight: 600;
  color: var(--primary-dark);
}
.rf.num {
  font-weight: 600;
}
.rf.wrap {
  white-space: normal;
  word-break: break-all;
}
/* V6.84：入库计件审核的「货品」字段——内容超长时单行省略号，不再折行。
   （flex 子项必须 min-width:0 才能收缩；flex:1 1 auto 让它吃掉单号之后的剩余宽度） */
.review-item .rf.ellip {
  flex: 1 1 0%;      /* 关键：basis 用 0（不能用 auto）——用 auto 时基础宽度＝内容宽度，会把 flex 行撑开导致不收缩、不出省略号 */
  min-width: 0;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rf.bad em {
  color: var(--danger);
}
.rf-bad {
  color: var(--danger);
  font-weight: 600;
}
.rf-empty {
  color: var(--text-sub);
  font-style: normal;
}
.review-item .review-do {
  flex: 0 0 auto;
  font-size: 14px;
  padding: 8px 22px;
}
.review-item .review-do.review-done {
  background: var(--ok);
  border-color: var(--ok);
  color: #fff;
  cursor: default;
}
.review-item .review-do.review-done:hover {
  background: var(--ok);
  color: #fff;
}
/* V6.82：待审核状态的「审 核」按钮改为淡黄（用户 2026-09-10 要求），
   与已审核的绿色（--ok，保持不变）形成明显区分。
   只作用于主审核按钮 .btn-primary：同区域的「整理费用」（.review-tidy，白底）与已审核态（.review-done）不受影响。
   四个审核页（包装/入库/卸货/纸箱回收）共用此样式，保持统一。 */
.review-item .review-do.btn-primary:not(.review-done) {
  background: #fff6cf;
  border-color: #ecd27a;
  color: #7a5a00;
}
.review-item .review-do.btn-primary:not(.review-done):hover {
  background: #fdeeb0;
  border-color: #e0c451;
  color: #6b4d00;
}
.review-item .review-do.btn-primary:not(.review-done):disabled {
  background: #fbf3d8;      /* 审核中…（禁用态）：淡黄再淡一档，避免看起来仍可点 */
  border-color: #ecd27a;
  color: #8a7a4a;
  cursor: default;
}
.review-empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--text-sub);
  font-size: 15px;
  background: var(--card);
  border: 1px dashed var(--border);
  border-radius: 10px;
}

/* ==================== 权限组（V4.0） ==================== */
.link-btn {
  border: none;
  background: none;
  color: var(--primary);
  cursor: pointer;
  font-size: 12px;
  margin-left: 8px;
  padding: 0;
}
.link-btn:hover { text-decoration: underline; }
.user-add-row { align-items: center; gap: 10px; flex-wrap: wrap; }
.user-new-tip { font-size: 12px; color: var(--text-sub); margin: 4px 0 10px; line-height: 1.6; }
/* 🔴 V8.29（用户 2026-09-16）：「账号明细」行内改权限移除后，原来给「权限组复选框」用的
   那几条规则**已无任何使用者**（原用于「新建账号」表单 + 行内复选框，两处都已删除）
   → **整块删掉，别加回来**。⚠️ `.grp-cell`（权限组列的对齐）保留 —— 列还在、只是不可改。 */
.grp-cell { vertical-align: top; }
.grp-toolbar { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; flex-wrap: wrap; }
.grp-tip { font-size: 12px; color: var(--text-sub); line-height: 1.6; }
.grp-list { display: flex; flex-direction: column; gap: 10px; }
.grp-card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; }
.grp-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.grp-name { font-size: 15px; font-weight: 600; color: var(--text); }
.grp-badge { font-size: 11px; padding: 1px 8px; border-radius: 999px; }
.grp-badge.builtin { background: #eef6f2; color: var(--ok); border: 1px solid var(--ok); }
.grp-badge.custom { background: var(--bg); color: var(--text-sub); border: 1px solid var(--border); }
.grp-members { font-size: 12px; color: var(--text-sub); margin-left: auto; }
.grp-perms { display: flex; flex-wrap: wrap; gap: 6px; }
.perm-pill { font-size: 12px; color: var(--text-sub); background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 2px 8px; }
.grp-ops { display: flex; gap: 8px; margin-top: 10px; }
.grp-ops .btn { font-size: 13px; padding: 4px 12px; }
.grp-editor { background: var(--bg); border: 1px solid var(--primary); border-radius: 10px; padding: 12px 16px; margin-bottom: 12px; }
.grp-ed-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.grp-ed-head .text-input { width: 220px; }
.grp-ed-head .btn { font-size: 13px; padding: 6px 14px; }
.grp-ed-points { display: flex; flex-direction: column; gap: 8px; }
.pd { display: flex; align-items: baseline; gap: 12px; }
.pd-name { flex: 0 0 76px; font-size: 12px; color: var(--text-sub); font-weight: 600; }
.pd-opts { display: flex; flex-wrap: wrap; gap: 6px 16px; }

/* ==================== 包装计件单价 & 整理费用（V4.3） ==================== */
.review-tidy {
  background: #fde8e8;
  border: 1px solid #f2b3b3;
  color: #b3261e;
  font-weight: 600;
}
.review-tidy:hover { background: #fbd8d8; }
.pkg-add-row { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; flex-wrap: wrap; }
#pkg-new-name { width: 220px; }
.pkg-price-input { width: 120px; }
.pkg-edit { font-size: 13px; padding: 4px 12px; }
.pkg-del { margin-left: 6px; }
.tidy-mask {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0, 0, 0, 0.35);
  display: flex; align-items: center; justify-content: center; padding: 18px;
}
.tidy-box {
  background: var(--card); border-radius: 12px;
  width: min(1120px, 96vw); max-height: 92vh;
  display: flex; flex-direction: column; overflow: hidden;
}
.tidy-head { display: flex; align-items: center; gap: 12px; padding: 12px 18px; border-bottom: 1px solid var(--border); }
.tidy-title { font-size: 16px; font-weight: 600; }
.tidy-sub { font-size: 13px; color: var(--text-sub); flex: 1; }
.tidy-prev-row { padding: 10px 18px 2px; }
.tidy-preview {
  min-height: 34px; margin-top: 4px; padding: 6px 10px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  font-size: 14px; color: var(--text); word-break: break-all; line-height: 1.6;
}
.tidy-body {
  display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 12px;
  padding: 8px 18px 12px; overflow: auto; flex: 1;
}
.tidy-left { overflow: auto; min-height: 0; }
.tidy-right { overflow-y: auto; min-height: 0; }
.tidy-bom-table th, .tidy-bom-table td { white-space: nowrap; padding: 8px 10px; }
.tidy-bom-table td { font-size: 13px; }
.tidy-chips { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding-top: 6px; }
.tidy-item {
  border: 1px solid var(--border); border-radius: 8px; padding: 7px 4px;
  display: flex; flex-direction: column; gap: 4px; align-items: center; background: var(--card);
}
.tidy-p { font-size: 16px; font-weight: 600; color: var(--primary-dark); }
.tidy-n { font-size: 12px; color: var(--text-sub); white-space: nowrap; }
.tidy-qty { width: 94%; height: 28px; font-size: 12px; text-align: center; padding: 2px 4px; }
.tidy-foot { padding: 10px 18px; border-top: 1px solid var(--border); display: flex; gap: 10px; justify-content: flex-end; }
.tidy-foot .tidy-save { margin-left: auto; }

/* ==================== V4.5 调整 ==================== */
.pkg-table { table-layout: fixed; width: 100%; }
.pkg-table th, .pkg-table td { width: 25%; text-align: center; }
.pkg-table td:first-child { text-align: center; }
.pkg-table .text-input { width: 100%; box-sizing: border-box; text-align: center; }
#pkg-add-row { display: flex; align-items: center; gap: 10px; }
#pkg-add-row .text-input { flex: 1; box-sizing: border-box; text-align: center; }
/* 整理费用弹窗：每行货品后横排单价列 */
.tidy-scroll { overflow: auto; flex: 1; min-height: 0; }
.tidy-grid { border-collapse: collapse; font-size: 13px; min-width: max-content; }
.tidy-grid th, .tidy-grid td { border: 1px solid var(--border); padding: 3px 5px; text-align: center; white-space: nowrap; vertical-align: middle; }
.tidy-grid thead th { background: var(--bg); font-weight: 600; }
.tidy-grid .tidy-info { position: sticky; left: 0; background: var(--card); }
.tidy-grid .tidy-info strong { font-size: 13px; }
.tgh { padding: 3px 5px !important; line-height: 1.25; }
.tgp { font-size: 15px; font-weight: 600; color: var(--primary-dark); }
.tgn { font-size: 11px; color: var(--text-sub); }
.tg-qty { width: 100%; text-align: center; font-size: 13px; padding: 1px 1px; height: 26px; box-sizing: border-box; }
.tg-rt { color: var(--primary-dark); font-weight: 600; }
.tg-rm { color: var(--text-sub); font-size: 12px; font-weight: 400; }
.tidy-info-wrap { background: var(--card); }
.tidy-sub-note { font-size: 13px; color: var(--text-sub); padding: 4px 18px; }
.tidy-info-wrap { background: var(--card); white-space: normal; word-break: break-all; line-height: 1.25; min-width: 0; }
/* V4.12：货品名称内容左对齐、垂直居中；行数按内容自适应——一行放得下显示一行，放不下自动换行为 2 行（上限 2 行，超出截断），不强制短名称留空 2 行 */
.tidy-grid td.tidy-info-wrap {
  text-align: left;
  vertical-align: middle;
  white-space: normal;
  word-break: break-all;
}
.tidy-grid td.tidy-info-wrap strong {
  display: block;
  line-height: 1.35;
  max-height: 2.7em;
  overflow: hidden;
}

/* ==================== V4.6 调整 ==================== */
/* V11.16（用户 2026-09-22）：这一段原先是「整条侧栏 sticky ＋ `max-height` ＋ `overflow-y:auto`」。
   侧栏改三段后，🔴 **滚动条下移到中间菜单容器 `.side-nav`**（见文件上方那段）——
   ⛔ 别把 `overflow-y:auto` 加回来：那会变成"整条侧栏滚动"，菜单一长就把上面的品牌
      和下面的「退出 / 修改密码」一起滚出视野（这是本轮特意改掉的）。
   本段只保留「吸顶 ＋ 不超出视口」两件事；`overflow:hidden` 是为了让内层滚动生效
   （父级一旦是 `visible`，内层的 `overflow-y:auto` 就没有可滚动的边界）。 */
.sidebar {
  position: sticky;
  top: 8px;
  align-self: flex-start;
  max-height: calc(100vh - 32px);
  overflow: hidden;
}
.tg-qty {
  width: 100%;
  appearance: textfield;
  -moz-appearance: textfield;
}
.tg-qty::-webkit-inner-spin-button,
.tg-qty::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.tidy-grid { table-layout: fixed; min-width: 0; }
.tidy-grid { table-layout: fixed; min-width: 0; }

/* ==================== V4.14 调整 ==================== */
/* 一键审核完成：按钮旁的就地提示条（不弹窗） */
.review-inline-msg {
  display: inline-block;
  margin-left: 12px;
  padding: 4px 12px;
  border-radius: 6px;
  font-size: 13px;
  background: #e8f5e9;
  color: #188038;
  border: 1px solid #a5d6a7;
  vertical-align: middle;
  max-width: 60%;
}
.review-inline-msg.review-inline-err {
  background: #fdecea;
  color: #c5221f;
  border-color: #f5c6cb;
}

/* ==================== 劳务管理 V5.x ==================== */
.range-btn { margin: 0 2px; }
.range-btn.active { background: #0b57d0; color: #fff; border-color: #0b57d0; }

.sim-phone {
  width: 330px;
  min-height: 560px;
  margin: 0 auto;
  background: #f2f5f8;
  border: 10px solid #22262b;
  border-radius: 36px;
  padding: 14px 14px 24px;
  box-sizing: border-box;
  font-size: 14px;
  color: #222;
}
.sim-btn {
  width: 100%; padding: 16px; font-size: 18px; color: #fff; border: 0; border-radius: 12px; margin-top: 10px;
}
.sim-msg { margin-top: 12px; padding: 10px; border-radius: 8px; text-align: center; font-size: 14px; line-height: 1.5; }
.sim-msg.ok { background: #e6f4ea; color: #137333; }
.sim-msg.err { background: #fce8e6; color: #c5221f; }

/* V5.28/30/6.1：审核明细·货品清单展开 */
.review-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; flex: 0 0 auto; align-self: flex-start; }
.review-do-group { display: flex; flex-direction: row; align-items: center; gap: 10px; flex: 0 0 auto; flex-wrap: wrap; align-self: flex-start; }
/* 🔴 V8.99（用户 2026-09-21 报「锁定按钮没在整理费用右边、而是在下面」）：
   「整理费用」与「锁定/解锁」必须**同一行**（锁定在整理费用**右边**）。
   ⛔ 别把这两个按钮**直接挂在 `.review-actions` 下** —— 那是 `flex-direction: column`，
      每个直接子元素各占一行，结果就成了「整理费用」第 2 行、「锁定」第 3 行（V8.97 踩到）。
   必须包进本行容器，让整行只作为 `.review-actions` 的**一个**子元素。 */
.review-do-row { display: flex; flex-direction: row; align-items: center; gap: 10px; flex: 0 0 auto; flex-wrap: wrap; align-self: flex-start; }
/* V9.0：待审核明细卡片上的「已锁定」标记（按单号卡走状态条，不用它） */
.review-lock-tag { display: inline-block; margin: 0 0 6px; padding: 3px 10px; border-radius: 8px;
  font-size: 12.5px; font-weight: 600; color: #a32d2d; background: #fdecec; border: 1px solid #f2b0b0; }
.review-glist-toggle { font-size: 15px; font-weight: 600; padding: 10px 22px; color: #0b57d0; background: #eef4ff; border: 1px solid #b9d0f5; }
.review-glist-toggle:hover { background: #dde9ff; }
.review-glist-toggle.open { background: #0b57d0; color: #fff; border-color: #0b57d0; }
/* V6.2/6.4：整理费用按钮 —— 统一淡红背景红字 */
.review-tidy {
  background: #fde8e8;
  border: 1px solid #f2b3b3;
  color: #b3261e;
  font-weight: 600;
  font-size: 14px;
  padding: 8px 18px;
}
.review-glist { width: 100%; } /* V6.3：高度自适应内容，不再限定高度滚动 */
.review-glist pre { margin: 0; padding: 10px 14px; background: #f6f8fa; border: 1px solid #e1e6ec; border-radius: 8px; font-size: 13.5px; line-height: 1.9; white-space: pre-wrap; word-break: break-all; color: #333; }

/* V6.18：费用结算扣除分钟输入框：隐藏上下调节按钮 */
.lin-cut-input::-webkit-inner-spin-button,
.lin-cut-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.lin-cut-input { -moz-appearance: textfield; appearance: textfield; }

/* ==================== 已结算费用明细表（V6.38）：固定布局、字段/内容自动换行、不出现横向滚动条 ==================== */
.settle-tbl { width: 100%; table-layout: fixed; border-collapse: collapse; }
.settle-tbl th, .settle-tbl td {
  white-space: normal; overflow-wrap: anywhere; word-break: break-word;
  text-align: center; vertical-align: middle;
  padding: 6px 4px; border-bottom: 1px solid var(--border);
  font-size: 13px; line-height: 1.5;
}
.settle-tbl th { color: var(--text-sub); font-weight: 600; }
.settle-tbl td.tl, .settle-tbl th.tl { text-align: left; }
.settle-tbl td.tc { text-align: center; }
.settle-tbl td.tt { text-align: left; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* V6.46：费用待结算明细表——姓名加宽单行显示（超长省略，悬停可见全名）、身份证号窄列自动断行换行 */
.settle-tbl th.c-name, .settle-tbl td.c-name {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 0; text-align: center;
}
.settle-tbl td.c-idno, .settle-tbl th.c-idno { word-break: break-all; }

/* ==================== V6.52：选项卡吸顶 + 已结算费用页固定工具条与冻结表头 ==================== */
.tabs { position: sticky; top: 0; z-index: 30; background: var(--bg); }

/* 已结算费用页：范围按钮+刷新+合计 同一排，页面滚动时吸附在选项卡下方 */
.lin-settled-bar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 2px;
  position: sticky; top: var(--tabs-h, 54px); z-index: 20;
  background: var(--bg);
}
.settled-gap { width: 46px; }   /* 范围按钮与刷新按钮之间空一个按钮位 */
.settled-total {
  margin-left: auto; padding: 5px 14px; font-size: 14px; font-weight: 600;
  background: var(--card); border: 1px solid var(--border); border-radius: 8px;
  color: #b3261e; white-space: nowrap;
}

/* 已结算明细：独立滚动区，行数多时表头冻结不随滚动 */
.settled-scroll {
  overflow: auto;
  max-height: calc(100vh - 235px);
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--card);
}
.settled-scroll table { margin: 0; }
.settled-scroll table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg);
  box-shadow: 0 1px 0 0 var(--border);
}

/* 单元格状态底色（V7.58 更正：用户明确「颜色不是字体颜色，字体默认黑色，颜色变化是单元格的颜色变化」）
   —— 图例色块与表格格子**共用这四个变量**，改色只需要改这一处。 */
#page-duty_summary {
  --duty-c-confirmed: #dbe9fd;   /* 已确认（未请款） */
  --duty-c-paid: #d9f0e2;        /* 已请款 */
  --duty-c-pend: #fff6cf;        /* 仅打卡未确认 */
  --duty-c-bad: #fbe0dd;         /* 时长异常 */
}

/* ==================== V7.56：到岗汇总（人×日 矩阵） ====================
   用户 2026-09-14 的要求：「表格字段不随页面滚动」→ 表头吸容器顶 + 姓名列左右固定。
   ⚠️ sticky 的两条坑（都踩过）：
     ① 表格在**自身滚动容器**内时，表头要吸容器顶，规则必须带 `section.page` 前缀提高特异性，
        否则被全局 `section.page table thead th { top: var(--tabs-h) }` 压住 → 吸到页面顶端；
     ② 同时吸顶又吸左的**角格**（表头首列、表尾首列）z-index 必须**高于同排其它固定格**，
        否则后出现的兄弟格会盖住它（用 `section.page .duty-tbl …` 再提一级特异性）。 */
.duty-bar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 2px;
  position: sticky; top: var(--tabs-h, 54px); z-index: 20;
  background: var(--bg);
}
/* 顶部「说明 + 颜色图例」：色块＝格子的填充色、描边＝同一格文字的颜色，和表格里保持一套色。
   V7.64（用户 2026-09-15）：用户明确要求**6 项横排一行**（不要竖排）→ 列距由 16px 收到 12px。
   6 项按 13px 字号量算约 700px ＋ 列距 60 ≈ 760px，在内容区（约 1132px）内富余；
   `flex-wrap: wrap` 保留 —— 只在窗口很窄时才折行，不会溢出。 */
.duty-legend {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 8px 12px; margin-bottom: 6px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--card); font-size: 13px; color: var(--text-sub);
}
.duty-lg { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.duty-lg .sw {
  width: 12px; height: 12px; border-radius: 3px; display: inline-block; flex: 0 0 auto;
}
/* 色块填充＝该状态的**单元格底色**（同一变量，改色两处一起变），描边＝同色系更深一档便于辨认 */
.sw-blue { background: var(--duty-c-confirmed); border: 1px solid #7fa8dd; }
.sw-green { background: var(--duty-c-paid); border: 1px solid #7fbf95; }
.sw-yellow { background: var(--duty-c-pend); border: 1px solid #e6d08a; }
.sw-red { background: var(--duty-c-bad); border: 1px solid #e2a49c; }
.duty-note { font-size: 12px; color: var(--text-sub); margin-left: 8px; }
.duty-total {
  margin-left: auto; padding: 5px 14px; font-size: 14px; font-weight: 600;
  background: var(--card); border: 1px solid var(--border); border-radius: 8px;
  color: #b3261e; white-space: nowrap;
}
.duty-scroll {
  overflow: auto;
  max-height: calc(100vh - 235px);
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--card);
}
/* 列多（本月 30 列 / 上月 31 列）→ 表格按内容撑开、由容器横向滚动 */
.duty-scroll table {
  margin: 0; border-collapse: separate; border-spacing: 0;
  width: max-content; min-width: 100%;
}
section.page .duty-scroll table thead th {
  position: sticky; top: 0; z-index: 3; background: #f4f6f9;
  box-shadow: inset 0 -1px 0 0 var(--border), 0 3px 6px -4px rgba(0, 0, 0, .22);
}
.duty-tbl th, .duty-tbl td {
  padding: 6px 8px; font-size: 13px; text-align: center;
  border-bottom: 1px solid var(--border);
}
.duty-tbl th {
  min-width: 68px; color: var(--text-sub); font-weight: 600;
  line-height: 1.35; white-space: nowrap;
}
.duty-tbl th .d-w { display: block; font-size: 11px; font-weight: 500; color: #98a2b3; }
.duty-tbl th.wk { background: #eef1f6; }
.duty-tbl th.wk .d-w { color: #b3261e; }
.duty-tbl td.dcell { min-width: 68px; line-height: 1.45; }
.duty-tbl td.dcell .h { font-weight: 600; }
/* 一律第二行显示金额（单元格 / 个人合计 / 每日合计三处同一样式）。
   ⚠️ 不设 color —— V7.58 起**字体一律默认黑色**，金额不再标红（状态只靠单元格底色表达）。 */
.duty-tbl td.dcell .m, .duty-tbl td.c-sum .m, .duty-tbl tr.dfoot td .m {
  display: block; font-size: 12px;
}
.duty-tbl td.dcell.none { color: #b9c0c9; }          /* 当天两表都没有 → 灰色「—」占位（不是状态色，只是占位） */
.duty-tbl td.dcell.wk { background: #fafbfd; }       /* 周末列淡底 */
/* V7.57（用户 2026-09-14）：只有「上班」/「下班」（打了卡但还没确认结算）→ **标黄**
   底色/字色沿用项目既有的黄色系（#fff6cf 底 / #6b4d00 字，与审核页的「待处理」同色，不新增色值）。
   ⚠️ 必须排在上面的 .wk 之后：两者特异性相同，靠先后决定胜负 —— 周末的打卡格也应当是黄的。 */
.duty-tbl td.dcell.pend { background: var(--duty-c-pend); }
.duty-tbl td.dcell.pend .p {
  display: block; font-size: 12px; font-weight: 600; line-height: 1.5;   /* 字色默认黑 */
}
/* V7.58：状态一律用**单元格底色**表达（字体保持黑）：
   fee(蓝底，已确认未请款) → miss(红底，时长异常) → paid(绿底，已请款)，**后面的压前面的**；
   且四者都写在 `td.dcell.wk`（周末淡底）之后 —— 顺序即优先级，务必保持此序。 */
.duty-tbl td.dcell.fee { background: var(--duty-c-confirmed); }
.duty-tbl td.dcell.miss { background: var(--duty-c-bad); }
/* 前 3 列（项次 / 姓名 / 合计）**全部固定不滚动**（V7.60，用户 2026-09-14：
   「项次这个列，也要和姓名合计栏位一样，固定不滚动」）。
   ⚠️ 左偏移是一条**链**，不能各写各的：项次(0) → 姓名(项次实宽) → 合计(项次实宽+姓名实宽)；
      宽度由 JS **实测**后写进 CSS 变量（写死像素会在真机因字体/缩放错位），量不到时回落到这里的默认值。 */
.duty-tbl th.c-idx, .duty-tbl td.c-idx {
  position: sticky; left: 0; z-index: 2;
  min-width: 46px; width: 46px; text-align: center;
  color: var(--text-sub); font-weight: 600;
  background: var(--card);                  /* 固定列必须不透明，否则滑动内容会透出来 */
  border-right: 1px solid var(--border);
}
.duty-tbl tr.dfoot td.c-idx { background: #f4f6f9; }

/* 姓名列紧接项次；「合计」列再紧接姓名 —— 两列的左偏移都是变量算出来的 */
.duty-tbl th.c-name, .duty-tbl td.c-name {
  position: sticky; left: var(--duty-idx-w, 46px); z-index: 2;
  min-width: 84px; background: var(--card);
  border-right: 1px solid var(--border); text-align: center; font-weight: 600;
}
.duty-tbl th.c-sum, .duty-tbl td.c-sum {
  position: sticky; left: calc(var(--duty-idx-w, 46px) + var(--duty-name-w, 84px)); z-index: 2;
  min-width: 86px; background: #f7f9fc;
  border-right: 1px solid var(--border); font-weight: 600;
}
/* 表尾「每日合计」行：纵向滚到底时吸住 */
.duty-tbl tr.dfoot td {
  position: sticky; bottom: 0; z-index: 2;
  background: #f4f6f9; font-weight: 600;
  box-shadow: inset 0 1px 0 0 var(--border);
}
/* V7.58：**已请款**（费用表「薪资发放」已勾选）→ 绿底。
   排在 .miss 之后 → 「已请款 + 异常」时以「已请款」为准。 */
.duty-tbl td.dcell.paid { background: var(--duty-c-paid); }

section.page .duty-tbl thead th.c-idx,
section.page .duty-tbl thead th.c-name,
section.page .duty-tbl thead th.c-sum { z-index: 4; background: #f4f6f9; }
section.page .duty-tbl tr.dfoot td.c-idx,
section.page .duty-tbl tr.dfoot td.c-name,
section.page .duty-tbl tr.dfoot td.c-sum { z-index: 4; background: #f4f6f9; }

/* V6.53：付款申请明细——结算单号窄列自动断行（2 行内显示完整单号） */
.settle-tbl td.c-no, .settle-tbl th.c-no { word-break: break-all; }

/* ==================== V6.67：费用待确认页「口径说明」折叠面板 ==================== */
.lin-help {
  margin: 8px 0 10px;
  padding: 10px 14px;
  border: 1px solid #cfd8e3;
  border-radius: 8px;
  background: #f7fafd;
  font-size: 13px;
  line-height: 1.75;
  color: #555;
}
.lin-help-t { margin-bottom: 6px; }
.lin-help-t:last-child { margin-bottom: 0; }

/* ==================== V6.73：物流管理·快递物流报价 二级选项卡 ====================
   与一级选项卡同一规则：固定位置（sticky），不随页面滚动。
   一级 .tabs 吸顶 top:0（高约 54px），二级 .subtabs 紧随其下吸顶 top:54px。 */
.subtabs {
  display: flex; gap: 4px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin: 0 0 16px;
  position: sticky; top: var(--tabs-h, 54px); z-index: 29;
  background: var(--bg);
}
.subtab {
  padding: 9px 18px;
  border: none;
  background: transparent;
  font-size: 14px;
  cursor: pointer;
  color: var(--text-sub);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
}
.subtab:hover { color: var(--text); }
.subtab.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 600; }

/* ==================== V6.77：全站明细表表头统一吸顶 ==================== */
/* 规则：凡在页面流中展示数据的明细表，表头一律吸附在一级选项卡下方（不随页面滚动消失）。
   · 吸顶基准用 --tabs-h（由 app.js 的 syncTabsHeight() 实时写入一级选项卡实际高度），不要写死像素；
   · .settle-tbl 等表格用 border-collapse:collapse，sticky 表头的 border 会随滚动丢失，故用 inset box-shadow 补分隔线；
   · 弹窗面板（如模拟请款内容）内的表格同样适用。 */
section.page table thead th {
  position: sticky;
  top: var(--tabs-h, 54px);
  z-index: 8;
  background: var(--bg);
  box-shadow: inset 0 -1px 0 0 var(--border), 0 3px 6px -4px rgba(0, 0, 0, .22);
}

/* 例外：自身就是滚动容器的区域（表格在容器内部滚动，如已确认费用页、整理费用弹窗），
   表头应吸附在「容器」顶部而不是页面顶部。
   注意：必须用 `section.page .xxx` 这样的前缀提高特异性——`section.page table thead th` 是
   元素+类组合，特异性 (0,1,4) 高于 (0,1,3)，不加前缀覆盖不掉。 */
section.page .settled-scroll table thead th,
section.page .tidy-scroll table thead th,
.tidy-scroll table thead th {
  position: sticky;
  top: 0;
  z-index: 8;
  background: var(--bg);
  box-shadow: inset 0 -1px 0 0 var(--border), 0 3px 6px -4px rgba(0, 0, 0, .22);
}

/* 补贴输入框：去掉数字微调按钮（上下箭头）、宽度减半（66px → 33px） */
.lin-sub-input { width: 33px; padding: 3px 4px; }
.lin-sub-input::-webkit-inner-spin-button,
.lin-sub-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.lin-sub-input { -moz-appearance: textfield; appearance: textfield; }

/* ==================== 权限组（V8.7 三栏：左＝组单选＋组信息，中＝账号勾选，右＝菜单/选项卡/权限设置） ==================== */
/* —— 整体布局（用户 2026-09-15 第 3 条：权限勾选区**移到右侧**，中间让给账号区）——
   左列固定 208（权限组 + 组信息）、中列固定 240（账号勾选）、右列吃掉剩余宽度（三层勾选表）。
   🔴 V8.19（用户 2026-09-15）：「权限勾选信息框的位置高度与**权限组、账号列表持平**」→
   `align-items` 由 `flex-start` 改 **`stretch`**（三栏等高）＋ 容器 `min-height: var(--pg-fill-h)`
   （铺满页面）。⚠️ 只改这一处还不够：三栏内部还要各自 flex 撑开（见下面 `.pm-list` / `.pg-table-wrap`）。
   🔴 V8.28（用户 2026-09-16）：**第 4 个区块已取消**（那两个按钮搬到头部行）→ 本行只剩**三块**，
   勾选表因此自动**吃掉原第 4 块空出来的宽度**（176px ＋ 一个间距 ≈ 188px）——不需要额外加宽规则。
   窄屏放不下时右列自动折到下一行铺满（flex-wrap），不会把表格挤坏。 ·*/
.pg-cols {
  display: flex; gap: var(--pg-gap-x); align-items: stretch; flex-wrap: wrap;
  min-height: var(--pg-fill-h);
}
.pg-groups {
  width: var(--pg-w1); flex: 0 0 var(--pg-w1); border: 1px solid var(--border); border-radius: 10px;
  background: var(--card); padding: 10px 8px;
  /* V8.19：左列也撑满（与另两栏等高）→ 组列表吃掉剩余高度、组信息贴底 */
  display: flex; flex-direction: column;
}
.pg-groups-title { font-size: 12px; font-weight: 600; color: var(--text-sub); padding: 0 6px 8px; }
/* V8.19：组列表撑满左列剩余高度（组多了自己滚动），组信息因此贴到框的底部 */
.pg-group-list { display: flex; flex-direction: column; gap: 3px; flex: 1 1 auto; min-height: 0; overflow: auto; }
.pg-grp {
  display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text);
  padding: 7px 8px; border: 1px solid transparent; border-radius: 8px; cursor: pointer;
}
.pg-grp:hover { background: var(--bg); }
/* 选中的组：字体加粗 + 淡蓝填充（用户 2026-09-15 第 1 条） */
.pg-grp.on { background: #eef4ff; border-color: #b6d4fe; font-weight: 700; color: #12336b; }
.pg-grp-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pg-grp-cnt, .pg-grp-mem { font-size: 11px; color: var(--text-sub); white-space: nowrap; }
.pg-grp.on .pg-grp-cnt, .pg-grp.on .pg-grp-mem { color: #1d4ed8; }
/* V8.19（用户 2026-09-15）：右栏改**纵向 flex**；🔴 V8.21 工具区已移出右栏 →
   现在右栏里**只有勾选表**这一个子项，`flex: 1 1 auto` 让它吃满整栏高度
   → 与左「权限组」、中「账号」两个框**顶边齐、底边齐、高度相等**。 */
.pg-main { flex: 1 1 460px; min-width: 0; display: flex; flex-direction: column; }
/* ⚠️ V8.7 删掉了左列底部那块「权限组为系统内置，不支持新增 / 删除 / 改名…」的样式 ——
   用户 2026-09-15 第 2 条要求去掉；该信息改由下面的 `.pg-meta` 徽标表达。**勿加回来。** */

/* ⚠️ V8.10 删掉了右栏那条独立工具条（`.pg-bar`）—— 用户 2026-09-15 要求把「仅看已勾选」「清空」
   移进页首那一行的状态提示里（后来整体收敛成 `#pg-dirty`）。**别再给它加样式或加回来。**
   组信息（V8.7 第 2 条：从工具条**移到左列组列表下方**）—— 左列只有 208px，故纵向堆叠。 */
.pg-meta {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  font-size: 12px; color: var(--text-sub); flex-wrap: wrap;
  margin-top: 8px; padding: 9px 6px 0; border-top: 1px solid var(--border);
}
.pg-meta-item { white-space: nowrap; }
.pg-badge { font-size: 11px; padding: 1px 8px; border-radius: 999px; }
.pg-badge.builtin { background: #eef6f2; color: var(--ok); border: 1px solid var(--ok); }
.pg-badge.admin { background: #eef4ff; color: #1d4ed8; border: 1px solid #b6d4fe; }
.pg-warn { color: #b54708; }
.pg-gap { flex: 1 1 auto; }
/* V8.11（用户 2026-09-15）：两个按钮**加大加粗 + 淡蓝填充**；
   🔴 V8.28（用户 2026-09-16 第 4 条）：位置定稿＝**页首那一行、两个按钮横向并排、靠右**。
   「仅看已勾选」仍是**智能按钮**（两态）：未过滤＝「仅看已勾选」；已过滤＝文案「查看所有」
   ＋ **深一档淡蓝**（靠深浅区分"筛选生效中"，不用红/绿）。 */
.pg-rule-tools .btn {
  font-size: 14px; font-weight: 700; padding: 7px 18px; min-width: 118px; border-radius: 8px;
}
.pg-only-btn { background: #eef4ff; border: 1px solid #b6d4fe; color: #1d4ed8; }
.pg-only-btn:hover { background: #e2ecff; border-color: #93bdf7; color: #1a44b8; }
.pg-only-btn.on { background: #d7e5ff; border-color: #7ea9f5; color: #16357f; font-weight: 700; }
.pg-only-btn.on:hover { background: #cbdcff; }
.pg-none-btn { background: #eef4ff; border: 1px solid #b6d4fe; color: #1d4ed8; }
.pg-none-btn:hover { background: #e2ecff; border-color: #93bdf7; color: #1a44b8; }
.pg-none-btn:disabled { opacity: .5; cursor: not-allowed; }
/* ⚠️ V8.14（用户第 3 条「再检查」的根因）：它是**独立元素**，排在「规则文字」与「按钮组」之间，
   **不在 `.pg-rule-tools` 里** —— 上一版把它塞进按钮组占了第三行，提示框因此被撑到与按钮组同高，
   于是「顶对齐」看不出任何差别（框高 ＝ 按钮组高时，居中就是贴顶）。**别再塞回去。** */
.pg-only-note { font-size: 11px; color: var(--text-sub); white-space: nowrap; align-self: center; }
.pg-chk { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--text); cursor: pointer; }
.pg-item input, .pg-chk input { cursor: pointer; }
/* —— 🔴 V8.28（用户 2026-09-16）：本页最上面一行＝**单行工具条** ——
   左＝状态块（载入于 / 保存中 / 已保存 / 失败），右＝两个按钮 ＋（过滤生效时的）计数说明。
   用户第 2/4 条：状态块**移到原来提示框的位置（最左）**、两个按钮**在它之后、同一行、靠右**，
   且**按钮之间不换行**。做法＝一行 `flex`：
   · `.pg-dirty` 不再有固定宽度（用户选 B：**按内容自适应**）、也不再有列定位；
   · `.pg-rule-tools` 用 `margin-left: auto` 把「两个按钮 ＋ 计数说明」整体推到**最右**
     （右沿与下面的勾选表右沿对齐）；
   · 计数说明在按钮**之后**（用户选 A），与按钮之间用 `margin-left` 拉开 10px。
   ⚠️ 旧的「头部与模块行共用四列模板的网格」口径（V8.25/V8.26）**随第 4 区块一起取消**，
   连它配的那条窄屏媒体查询回退也一并删除 —— 现在这行**本身就是 flex**，窄屏不会挤出横向滚动。
   （注释里刻意不写那条媒体查询的字面 —— 生成器有「它已删除」的反向断言，写了会命中注释。） */
.pg-head {
  display: flex;
  align-items: center;
  gap: var(--pg-gap-x);
  flex-wrap: wrap;                       /* 极端窄屏才折行；两个按钮之间不折（见 .pg-rule-tools） */
  margin-bottom: 10px;
}
/* 🔴 V8.22（用户第 2 条）：这块**只显示「载入于 hh:mm:ss」**（空闲态）；写库中/成功/失败仍显示结果。
   尺寸与大小沿用当时与它同排的那个提示块（12px / line-height 1.7 / padding 6×10 / 圆角 8 / 1px 描边），
   只把**颜色**换成淡红（沿用本项目既有的淡红三件套）。`nowrap` 以免时间折行。 */
.pg-dirty {
  /* 🔴 V8.28（用户选 B）：**宽度按内容自适应**（原来被锁成 176px 是为了与第 4 区块同宽，
     该口径已随第 4 区块取消）。`max-width: 100%` 兜住极端长文案：真变长也只在本行内裁，
     不会把页面撑宽（配合下面的 `overflow: hidden`）。 */
  flex: 0 0 auto; max-width: 100%;
  white-space: nowrap;
  /* 🔴 V8.26：**溢出兜底**（当时实测出来的真 bug）——那块宽 176px、内容区只有 154px，
     而 V8.22 的写库成功文案「已保存 hh:mm:ss · 组名 · 影响 N 个账号」实测需 **256px**：
     原来没有兜底，文字直接溢出到盒子外，**把整页 scrollWidth 从 1440 撑到 1490**
     （页面右边多出一条横向滚动条）。现在 ① 文案改短（见 app.js，附 title 悬停看全文）
     ② 这里保留 `overflow: hidden` 兜底：即使将来文案又变长，也只在本块内裁掉。
     ⚠️ 别删这两条 —— 让 `scrollWidth` 仍然如实反映内容宽度，
     几何实测里「状态块不溢出」那条断言才抓得住回归。 */
  overflow: hidden; text-overflow: ellipsis;
  font-size: 12px; line-height: 1.7;
  padding: 6px 10px; border-radius: 8px;
  background: #fce8e6; border: 1px solid #f0b4ae; color: #c5221f;
}
.pg-dirty.on { color: #b54708; font-weight: 500; }
.pg-dirty.done { background: #e6f4ea; border-color: #a8d5b5; color: var(--ok); }
.pg-dirty.err { background: #fce8e6; border-color: #f0b4ae; color: var(--danger); font-weight: 500; }
/* ⚠️ V8.18 起这里**只放两个按钮**；计数说明在它外面（见 `.pg-only-note`）。
   🔴 V8.28（用户第 4 条）：由**纵向两行**改**横向并排**，并整体**靠右**（`margin-left: auto`）
   → 与下面的勾选表右沿对齐。`flex-wrap: nowrap` **保证两个按钮永不换行**（用户明确要求）。 */
.pg-rule-tools {
  flex: 0 0 auto; margin-left: auto;
  display: flex; flex-direction: row; align-items: center;
  gap: var(--pg-gap-x); white-space: nowrap;
}

/* 🔴 V8.28（用户第 3 条）：**原来的第 4 个区块已取消** —— 它里面的两个按钮搬到了头部行
   （见 `.pg-head` / `.pg-rule-tools`）。因此这里**不再有任何"按钮模块"的样式**：
   它空出来的宽度由第 3 个模块（勾选表）自动吃掉（`.pg-main` 是 `flex: 1 1 460px`）。
   ⚠️ 别再把那一块加回来 —— 加了就会把勾选表挤窄，与"加宽权限勾选区块填补空白"直接冲突。

/* —— 中间列：账号勾选（V8.7 用户第 3 条；🔴 V8.11 减半到 122px；🔴 V8.13 改**双列**后回到 240px）——
   双列每列约 110px（去掉 padding 与列间距）—— 122px 塞不下两列，故本列必须加宽。
   🔴 V8.19（用户 2026-09-15）：列表改为**撑满本列剩余高度**（靠 `.pg-cols` 的 stretch 与容器
   `min-height` 拿到"页面剩余高度"）。
   🔴 V8.27（用户 2026-09-16 第 2 条）：「账号单元格高度需**自适应**，确保**刚好填充满整个容器框**，
   不出现留白或溢出」→ 本框是 flex 列：**头部固定高、列表 `flex: 1 1 auto` 吃掉剩下的全部高度**
   （再靠行高 `1fr` 均分 → 不留白；超出则框内滚动 → 不溢出）。详见 `.pm-list` 的注释。
   ⚠️ 本框高度**不要**写死/写 `height`：它由 `.pg-cols` 的 stretch 与 `--pg-fill-h` 决定，
   写死就做不到"各区块等高"。 */
.pg-members {
  width: var(--pg-w2); flex: 0 0 var(--pg-w2); border: 1px solid var(--border); border-radius: 10px;
  background: var(--card); padding: 9px 7px; display: flex; flex-direction: column;
}
/* 🔴 V8.24（用户 2026-09-16 第 1 条）：「将搜索框放在【账号-勾选加入】**后面**，**自适应宽度**。」
   → 标题与搜索框改为**同一行**（`.pg-members-head` 用 flex）：标题 `flex: 0 0 auto` 只占文字宽，
   搜索框 `flex: 1 1 auto` **吃掉剩下的宽度**（窄列里也不会撑破，`min-width: 0` 兜底）。 */
.pg-members-head { display: flex; align-items: center; gap: 6px; padding: 0 3px 8px; }
.pg-members-title { flex: 0 0 auto; font-size: 12px; font-weight: 600; color: var(--text-sub); white-space: nowrap; }
/* ⚠️ `margin: 0` 必须有：这个 input 同时带 `.text-input`，而全局 `.text-input` 有 `margin-bottom: 14px`
   —— 不归零的话，flex 居中会把输入框整体**顶高 7px**（行高也被撑到 46px），肉眼看着就不在同一行了。
   （2026-09-16 真实渲染量出来的：标题 y=282 / 搜索框 y=271，中心差正好＝14/2。） */
.pm-search { flex: 1 1 auto; min-width: 0; margin: 0; font-size: 12px; padding: 4px 6px; box-sizing: border-box; }
/* 🔴 V8.27（用户 2026-09-16 第 2 条）：「账号单元格高度需**自适应**，确保**刚好填充满整个容器框**
   的高度，不出现留白或溢出，并在列表内容数量变化时仍保持该效果。」
   → 本节**取代** V8.22/V8.23 的「固定每列 15 人 + `align-content: start`」口径（那两条是为"不超出模块"
     立的，但代价是**框里留白**：框高 578px 而列表只占 15×17 ≈ 269px，剩下 ~280px 空白）。
   · 高度：`flex: 1 1 auto` ＋ `min-height: 0` → 列表**吃掉账号模块的剩余高度**（跟着 `.pg-cols` 的
     等高一起变高变矮，`--pg-fill-h` 改了它自动跟）；
   · 行高：由 JS 设 `repeat(行数, minmax(var(--pm-row), 1fr))` —— `1fr` 把剩余高度**均分给各行**
     → **正好填满、无留白**；内容超出一屏时每行回到 17px 下限、在框内滚动（`overflow: auto`）
     → **不撑破模块**。行数＝`ceil(n/2)`（列优先的需要；**不再有 15 行的下限**）；
   · `grid-auto-rows` 同款兜底：万一某条路径没调 `pmSyncRows()`，隐式行也按 1fr 填满而不是留白。 */
.pm-list {
  flex: 1 1 auto; min-height: 0; overflow: auto;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  /* 🔴 V8.24（用户 2026-09-16 第 1 条）：「账号列表，**先将左侧显示满了再显示在右侧**。」
     grid 默认是**行优先**（1 2 / 3 4…，读起来跳着走）→ 改 **列优先**（`column`）：
     先把左列排到显式行数，还有剩余才去右列。
     ⚠️ 列优先**必须同时给出显式行数**（`grid-template-rows`），否则 grid 会一路往右排成 n 列；
     行数由 JS 的 `pmSyncRows()` 按 `ceil(n/2)` 给出 → 两列各占一半、**永不出现第 3 列**。 */
  grid-auto-flow: column;
  grid-auto-rows: minmax(var(--pm-row), 1fr); gap: var(--pm-gap) 4px; padding-right: 2px;
}
/* V8.14：内边距 4→2px、字号 12→11px；V8.19：上下内边距去掉、缩到 14px
   🔴 V8.22（第 3 条）：**不再换行** —— 一个账号＝一行（`flex-wrap: nowrap`），名字用省略号收在列内、
   徽标（默认管理员 / 已停用）保持行内 → 横向不溢出、纵向不浪费行数。
   🔴 V8.27：行高**不再固定** —— 条目自然高 15px ＋ 上下边框 2 ＝ **17px（＝ `--pm-row`，行高下限）**，
   框里有余量时由 `1fr` 把它和同列其它行一起**均分撑高**（见 `.pm-list`）；条目自身仍是内容高，
   `align-items: center` 让姓名/徽标在撑高的行里**垂直居中**。 */
/* 🔴 V8.83／V8.84（用户 2026-09-19 两轮）：先是「账号-勾选加入里显示的账号，字体比当前**大一倍**」
   ⇒ 11 → 22px；**随后用户反馈「字体有点大了，再稍微小一点」⇒ 现行 18px**（line-height 24，
   `--pm-row` 26，见 :root）。两轮的来由都留在这里，⛔ 别再按"大一倍"把 22px 改回来。
   ✅ **18px 的额外好处（实测）**：每格给名字约 **80px** ⇒ **4 个汉字（72px）也放得下了**
   （22px 时 4 汉字 ＝ 88px > 80px，会被省略号截断）⇒ 原"≥4 汉字会截断"的边界**已消除**；
   现行边界变成 **≥5 汉字**。⚠️ 真出现 5 字以上账号名，仍需**加宽本列（`--pg-w2` 240px）
   或双列改单列**，⛔ 别靠继续缩小字号解决。
   ⚠️ 顺带说明：**复选框 13px 与徽标 11px 两轮都未动**（用户只要求账号字样）。 */
.pm-item {
  display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; font-size: 18px;
  line-height: 24px; color: var(--text); min-width: 0;
  padding: 0 5px; border: 1px solid transparent; border-radius: 6px; cursor: pointer;
}
.pm-item:hover { background: var(--bg); }
/* 🔴 V8.23（真实渲染实测抓出来的第二个真 bug）：账号条目里的复选框**必须把 UA 默认 margin 归零**。
   Chrome 给 `input[type=checkbox]` 的默认样式是 `margin: 3px 3px 3px 4px` —— 上下各 3px 会让复选框的
   外框高变成 13+6＝19px，成为**行高的决定者** → 条目从 17px 涨到 **21px**。
   ⚠️ V8.27 起行高会按 `1fr` 撑高，这个 margin 不再"少显示几行"，但它仍会**抬高行高的下限**
   （`--pm-row` 与实际条目高不一致 → 少一个人就多一条滚动），所以照旧**必须归零**。
   ⚠️ 只查 CSS 字符串永远发现不了（jsdom 不做布局），必须真实渲染量行高。**勿删。** */
.pm-box {
  flex: 0 0 auto; margin: 0; width: 13px; height: 13px;
  accent-color: var(--primary); cursor: pointer;
}
/* 已在本组：**加粗 + 淡蓝填充**（用户 2026-09-15 第 3 条），与左列选中组同一套色 */
.pm-item.on { background: #eef4ff; border-color: #b6d4fe; font-weight: 700; color: #12336b; }
.pm-item.on .pm-tag { color: #1d4ed8; border-color: #b6d4fe; }
.pm-item.is-lock { cursor: not-allowed; opacity: .6; }
.pm-item.is-stopped .pm-name { text-decoration: line-through; }
.pm-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-tag {
  flex: 0 0 auto; font-size: 11px; font-weight: 400; color: var(--text-sub); white-space: nowrap;
  border: 1px solid var(--border); border-radius: 999px; padding: 0 6px;
}
.pm-sum {
  margin-top: auto; padding: 8px 3px 0; border-top: 1px solid var(--border);
  font-size: 11px; color: var(--text-sub); line-height: 1.6;
}

/* —— 右侧：三层勾选表（V8.7 起独占右栏）——
   🔴 V8.21（用户 2026-09-15）：「权限选择信息框要和权限组、账号两框**在同一高度**」→
   工具区/按钮已在 V8.21-V8.22 全部移出右栏（V8.28 起落在页首那一行），所以本框**独占右栏整个高度**：
   `flex: 1 1 auto; min-height: 0`（右栏高度由 `.pg-cols` 的 stretch + `min-height` 决定）
   → 顶边与另两个框齐、高度也相等。
   `max-height: var(--pg-fill-h)` 只是**窄屏折行时的兜底**（那时右栏不再被 stretch 拉高，
   否则表格会按 35 行内容撑到一千多像素）。
   🔴 V8.24（用户 2026-09-16 第 2 条）：「权限设置栏位，**增加宽度，填充第 3 和第 4 区之间的空隙**。」
   → 表格宽度改 **100%**（＝铺满本模块）。前两列仍是固定像素（115 / 106px，贴内容无空余），
   第 3 列「权限设置」吃掉**模块剩余的全部宽度**。
   ⚠️ **这推翻了 V8.17 的「权限设置列＝容器 43.5%」**（那条会故意留白、把表格做窄）；
   现在第 3 列会随窗口宽窄变化（窗口越宽、这一列越宽），不再有固定比例。 */
.pg-table-wrap {
  width: 100%;
  flex: 1 1 auto; min-height: 0; max-height: var(--pg-fill-h); overflow: auto;
  border: 1px solid var(--border); border-radius: 10px; background: var(--card);
}
/* ⚠️ 必须 `fixed` —— 前两列现在是**像素宽**（115 / 106px），只有 fixed 能把它们钉住、
   并让第 3 列稳定地吃掉「模块宽 − 221px」。`auto` 会按内容重新分配，列宽就飘了。 */
.pg-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 13px; }
.pg-table th, .pg-table td {
  padding: 7px 10px; border-bottom: 1px solid var(--border);
  text-align: left; vertical-align: top; overflow-wrap: anywhere;
}
.pg-table thead th { font-size: 12px; font-weight: 600; color: var(--text-sub); background: #f4f6f9; white-space: nowrap; }
/* 表格在自身滚动容器内 → 表头吸附「容器顶部」；必须加 section.page 前缀提高特异性，
   否则被全局 `section.page table thead th { top: var(--tabs-h) }` 压住（会吸到页面顶部）。 */
section.page .pg-table-wrap table thead th {
  position: sticky; top: 0; z-index: 8; background: #f4f6f9;
  box-shadow: inset 0 -1px 0 0 var(--border), 0 3px 6px -4px rgba(0, 0, 0, .22);
}

/* —— 第 1 列：一级菜单（rowspan 合并；浅底 + 右分隔线）—— */
.pg-td-menu { background: #fafbfc; border-right: 1px solid var(--border); }
/* ⚠️ nowrap：V8.14 起这两列宽度＝内容宽（`table-layout: auto`），不加 nowrap 会被压到折行 */
.pg-menu-lbl { display: inline-flex; align-items: center; padding: 2px 6px; border-radius: 6px; border: 1px solid transparent; white-space: nowrap; }
.pg-menu-name { font-size: 14px; font-weight: 600; color: var(--text); }

/* —— 第 2 列：选项卡 —— */
.pg-page-lbl { display: inline-flex; align-items: center; padding: 2px 6px; border-radius: 6px; border: 1px solid transparent; white-space: nowrap; }
.pg-page-name { font-size: 13px; font-weight: 600; color: var(--text); }
.pg-scan { font-size: 11px; padding: 0 6px; border-radius: 999px; background: #f1f3f5; color: var(--text-sub); border: 1px solid var(--border); }

/* —— 勾选态：菜单 / 选项卡 / 权限设置**一律填充淡蓝**（用户 2026-09-15 第 3 条）；未勾选不填色 —— */
.pg-menu-lbl.on, .pg-page-lbl.on { background: #eef4ff; border-color: #b6d4fe; }
/* —— 置灰：上层没勾 → 下层禁用（用户第 2-1/2-2 条）—— */
.pg-menu-lbl.is-off, .pg-page-lbl.is-off, .pg-item.is-off { opacity: .45; }
.pg-menu-lbl.is-off, .pg-page-lbl.is-off { cursor: not-allowed; }

/* —— 第 3 列：权限设置（chip；勾选态有底色） —— */
.pg-items { display: flex; flex-wrap: wrap; gap: 6px 8px; }
.pg-item {
  display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--text);
  padding: 3px 7px 3px 6px; border: 1px solid var(--border); border-radius: 8px;
  background: #fff; cursor: pointer; line-height: 1.5;
}
.pg-item:hover { border-color: var(--primary); }
.pg-item.on { background: #eef4ff; border-color: #b6d4fe; }
.pg-item.on .pg-item-name { font-weight: 500; }
/* V8.9（用户 2026-09-15）：问号图标改为**淡红填充**（原来是无色底 + 灰字）。
   用的是项目里既有的红系三档（浅红底 / 中红描边 / 深红字，与 `.app-toast.err`、
   注册开关的「已关闭」态同一套色），**不新增色值**；未勾/置灰的项仍靠整块 opacity 变淡。 */
.pg-info {
  width: 17px; height: 17px; flex: 0 0 auto; padding: 0;
  border: 1px solid #f0b4ae; background: #fce8e6; color: #c5221f;
  border-radius: 50%; font-size: 11px; line-height: 1; cursor: pointer;
}
.pg-info:hover { background: #f9dcd9; border-color: #e8a29a; color: #a81f1c; }
.pg-none-item { font-size: 12px; color: var(--text-sub); padding: 4px 0; }
/* ⚠️ V8.8 删掉了搜索框 → 它的输入框样式与「搜索命中高亮」样式一并删除，**勿加回来**。 */
.pg-ro-tip {
  font-size: 12px; color: #7a5a00; background: #fff9e6; border-bottom: 1px solid #f0e0a8;
  padding: 6px 12px;
}

/* —— 「?」说明气泡（V8.7 用户第 1 条：点问号弹**气泡**、不是弹窗）——
   原来是右侧一块常驻说明面板（sticky），现改为贴着问号图标浮出的小气泡：
   `position: fixed` + JS 用 `getBoundingClientRect()` 实时定位（页面在滚动容器里，用 absolute 会算错）。
   z-index 取 1100：要压过表格吸顶表头（8）与弹窗（100），但要**低于**顶部浮层（1200）。 */
.pg-bubble {
  position: fixed; z-index: 1100; box-sizing: border-box;
  width: 252px; padding: 11px 13px 10px;
  border: 1px solid var(--border-secondary, #d7dce3); border-radius: 10px;
  background: var(--card); color: var(--text); font-size: 13px;
  box-shadow: 0 6px 18px -6px rgba(0, 0, 0, .28);
}
.pg-bubble-x {
  position: absolute; top: 5px; right: 7px; border: 0; background: transparent; cursor: pointer;
  color: var(--text-sub); font-size: 15px; line-height: 1; padding: 1px 3px;
}
.pg-bubble-x:hover { color: var(--text); }
.pg-d-title { font-size: 14px; font-weight: 600; margin-bottom: 2px; padding-right: 16px; }
.pg-d-code { font-size: 12px; color: var(--text-sub); margin-bottom: 9px; word-break: break-all; }
.pg-d-row { display: flex; gap: 8px; font-size: 12px; color: var(--text); margin-bottom: 6px; line-height: 1.6; }
.pg-d-row > span:first-child { flex: 0 0 58px; color: var(--text-sub); }
.pg-d-hint, .pg-d-tip { font-size: 12px; color: var(--text-sub); line-height: 1.7; margin-top: 8px; }

/* —— 底部保存条（🔴 V8.22：整块已并入页面最上面的「载入于」淡红块，这里不再有样式）——
   ⚠️ 旧的 `.pg-save` 与 `#pg-saved-at` 都已删除：前者会把勾选表压矮 42px（V8.21 的坑），
   后者已并入 `#pg-dirty`（用户要求"仅显示载入于"、作为一个独立元素块）。**都别加回来。** */

/* 顶部浮层提示（V8.1，通用组件，非权限页专属）：给"做完动作但界面变化不明显"的操作一个强反馈。
   固定**视口顶部居中**、不随滚动；2.5 秒自动淡出（JS 摘 .show），也可点 × 立即关掉。
   z-index 取 1200：要压过弹窗（100）与整理费用浮层（1000）。 */
.app-toast {
  position: fixed; top: 16px; left: 50%; transform: translate(-50%, -12px);
  z-index: 1200; box-sizing: border-box;
  display: flex; align-items: center; gap: 9px;
  max-width: min(780px, 92vw);
  padding: 9px 12px 9px 14px; border-radius: 10px; border: 1px solid transparent;
  font-size: 13px; font-weight: 500; line-height: 1.5;
  opacity: 0; pointer-events: none;
  transition: opacity .22s ease, transform .22s ease;
}
.app-toast.show { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.app-toast.ok { background: #e6f4ea; border-color: #a8d5b5; color: #137333; }
.app-toast.err { background: #fce8e6; border-color: #f0b4ae; color: #c5221f; }
.app-toast-ico {
  width: 16px; height: 16px; flex: 0 0 16px; border-radius: 50%;
  color: #fff; font-size: 11px; line-height: 16px; text-align: center; font-weight: 500;
}
.app-toast.ok .app-toast-ico { background: #137333; }
.app-toast.err .app-toast-ico { background: #c5221f; }
.app-toast.ok .app-toast-ico::before { content: '✓'; }
.app-toast.err .app-toast-ico::before { content: '!'; }
.app-toast-txt { flex: 1 1 auto; min-width: 0; }
.app-toast-x {
  flex: 0 0 auto; border: 0; background: transparent; cursor: pointer;
  color: inherit; font-family: inherit; font-size: 16px; line-height: 1;
  padding: 0 2px; opacity: .7;
}
.app-toast-x:hover { opacity: 1; }

/* 货品清单·原料汇总行（V7.15） */
.review-glist-sum {
  font-size: 13px; color: var(--text); background: #f6f8fa;
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 10px; margin-bottom: 6px;
}

/* 包装计件审核 · 日期范围按钮（V7.16） */
.review-range-label { font-size: 13px; color: var(--text-sub); }
.review-rng { padding: 7px 14px; font-size: 14px; }
.review-rng.on { background: var(--primary); border-color: var(--primary); color: #fff; }
.review-rng.on:hover { background: var(--primary-dark); }

/* 临时工账号分页（V7.20：每页 10 条） */
#lin-people-count { display: block; text-align: center; margin-top: 12px; }

/* 临时工「注册开关」（V7.66，用户 2026-09-15）：一个按钮两种状态。
   绿＝开放中（点一下即关闭）、红＝已关闭（点一下即开放）。色值沿用项目既有成功/告警色。 */
.lin-reg-switch.on { background: #e6f4ea; color: #137333; border-color: #a8d5b5; }
.lin-reg-switch.on:hover { background: #d7ecdd; }
.lin-reg-switch.off { background: #fce8e6; color: #c5221f; border-color: #f0b4ae; }
.lin-reg-switch.off:hover { background: #f9dcd9; }

/* ==================== V8.43（用户 2026-09-16）：三个二维码页的「开放/关闭」开关 ====================
   三张二维码卡片各宽 **180px**（`#page-lin_qr` 里的内联宽度）→ 工具栏那套 `.lin-reg-switch` 尺寸
   （字号 15px、内边距 10px 18px）会把「注册已开放 · 点此关闭」撑出卡片，所以单独压一档：
   定宽 100% ＋ 12.5px 字号 ＋ 6px 内边距 → 11 个汉字 ≈ 138px，一行放得下且左右留白。
   ⚠️ 与 `.btn` 同为类选择器、**同优先级，后来者胜** → 本段必须写在 `.btn` 之后（它在文件末尾，安全）。
      改这一段后跑 `node scripts/lin_qr_geometry_check.js` 会直接量出按钮是否溢出卡片。 */
.qr-switch {
  width: 100%; box-sizing: border-box; margin: 10px 0 0; padding: 7px 6px;
  font-size: 12.5px; line-height: 1.5; white-space: nowrap;
}

/* ==================== V8.43：付款申请页的「日期范围 / 人名筛选」 ====================
   日期范围复用全局 `.range-btn`（选中＝蓝底白字）；人名筛选按钮沿用「智能按钮」口径：
   生效时用**淡蓝高亮**（不是红绿 —— 红绿会让人以为"出错了"，本仓在「仅看已勾选」那处已定过口径）。 */
.pay-name-btn.on { background: #eef4ff; color: #1d4ed8; border-color: #b6d4fe; }
.pay-name-btn.on:hover { background: #e2ecff; }
.pay-filter {
  border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px;
  margin: 0 0 12px; background: #f7fafd;
}
.pay-filter-hd { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pay-filter-gap { flex: 1 1 auto; }
.pay-filter-box {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin-top: 10px; max-height: 168px; overflow: auto;
}
.pay-name-item {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13.5px; line-height: 1.6; cursor: pointer; white-space: nowrap;
}
.pay-name-item input { width: 16px; height: 16px; cursor: pointer; }

/* ==================== V8.53：付款申请明细表的**人员分组分隔线**（用户 2026-09-17）====================
   用户原话：「不同人员之间有一个彩色的加粗横线，让人能很容易看清区分即可。」
   做法：同一个人只在**第一行**上面画一条 —— 那一行带 `.pay-grp-sep`（由 app.js 的 `linPayNewGroup()` 判定；
   排序保证同一个人是连续的，见 `linPaySortRows()`）。
   🔴 三条口径：
     · **3px 实线 + 主题绿 `var(--primary)`**（沿用既有色值，不新增颜色）：表格既有边框是 `--border`
       #dfe9e3（很浅的灰绿），3px 主题绿压在上面一眼可辨；刻意**不用红/黄** —— 那两色在本系统表示"出错/告警"，
       会被误读。
     · 用 **`inset box-shadow` 而不是 `border-top`**：本表是 `border-collapse: collapse`，给它加 3px 上边框会
       让**相邻行各长高约 1px**（几何实测量到 52→53/54px；同一张表的吸顶表头当年也是为这个改用 inset
       box-shadow 的，见 V6.77 那段注释）。box-shadow **完全不参与布局** → 列宽、行高、表宽一律不动
       （`payapply_group_check.js` 会摘掉这个 class 再量一遍逐项比对）。
     · 每个格子各画自己那一段 → 15 段首尾相接＝**横贯整行**的一条线（几何实测逐格验证）。
       ⛔ 别改成 `border-top` / `padding-top` / 额外空行（会改行高）。 */
.settle-tbl tbody tr.pay-grp-sep > td { box-shadow: inset 0 3px 0 0 var(--primary); }

/* ==================== V8.49：模拟请款内容面板的字段区块（付款申请 → 点「模拟请款内容…」按钮后） ======
   用户 2026-09-17 要求：字段**不再用四列表格**（# / 字段 / 控件类型 / 模拟填写值），
   改成「**分组区块 + 每行「表头｜内容」**」：组内按行、组间各自成框；
   「收款账户」「备注」各单独一行（组里只有一行）。
   🔴 两条必须守住：
     ① 表头列是**定宽** `flex: 0 0 104px`，**别改成 `flex: 1` / `min-width`** ——
        定宽才能让 6 个区块的「表头｜内容」左边界**严格对齐**（这是"按行显示表头与内容"的全部意义）；
        6 个字的「发票提交日期」在 13px 下约 78px，104px 留出右侧间隙。
     ② 内容列 `min-width: 0` + `word-break: break-word` —— 否则长值（收款账户那段）会把
        flex 子项撑破容器（flex 子项默认 min-width:auto 不收缩）。
   ⛔ 别把 .pay-block 的 padding 改大：区块一多会显得很松散（用户刚要求去掉标题、压缩观感）。 */
.pay-block {
  border: 2px solid #cfd8e3; border-radius: 8px; background: #fff;
  padding: 6px 12px; margin-bottom: 8px;
}
.pay-block-hd { font-size: 12px; color: #8a8f98; margin: 4px 0 2px; }
.pay-row { display: flex; align-items: flex-start; gap: 10px; padding: 5px 0; }
.pay-row + .pay-row { border-top: 1px dashed #eef1f5; }
.pay-lb {
  flex: 0 0 104px; font-size: 13px; color: #5f6368;
  line-height: 1.7; white-space: nowrap;
}
.pay-vl {
  flex: 1 1 auto; min-width: 0; font-size: 13px; color: #202124;
  font-weight: 600; line-height: 1.7; word-break: break-word;
}
.pay-hint { display: block; font-size: 12px; color: #8a8f98; font-weight: 400; margin-top: 4px; }

/* ==================== V8.56：模拟请款面板——字段区改「表格样式」＋ 全区域框线加粗 ====================
   用户 2026-09-17 原话：「钉钉请款模拟结果区域，这个区域的框线，加粗。这几个字段的格式，用类似费用明细
   表格的样式展示：申请日期、付款类型、供应商类型；另外几个显示区块也一样，除了收款账户和备注是一行的不变。」
   🔴 三条口径：
     ① **框线加粗**：面板外框（app.js 内联）与 `.pay-block` 一律 **2px**（颜色沿用面板既有的 `#cfd8e3`，
        只加粗不改色）。
     ② **≥2 个字段的组改表格**（`.pay-tbl`）：**表头行＝字段名、数据行＝值**，模仿「费用明细」那张表的观感
        （表头灰字 + 单元格下边框）。⛔ 不再用「表头｜内容」两列的行式（那是 V8.49 的形态，现只留给单字段组）。
        ⚠️ **不画竖线**：参照的「费用明细」表本来就只有横线，画竖线会让两块并排时显得乱。
     ③ **单字段组保持一行**（`.pay-block` + `.pay-row`）：收款账户、备注（备注里是可编辑 textarea）。
   🔴 别给 `.pay-tbl` 加 `border-radius`：`border-collapse: collapse` 下圆角会被方角边框切掉（表格就方角，
      与「费用明细」一致）。 */
.pay-tbl {
  width: 100%; table-layout: fixed; border-collapse: collapse;
  border: 2px solid #cfd8e3; background: #fff; margin-bottom: 8px;
}
.pay-tbl th, .pay-tbl td {
  padding: 7px 10px; font-size: 13px; line-height: 1.55; text-align: left; vertical-align: top;
  word-break: break-word; overflow-wrap: anywhere;
}
.pay-tbl th {
  color: var(--text-sub); font-weight: 600; background: #f7fafd; white-space: nowrap;
  border-bottom: 2px solid #cfd8e3;              /* 表头下沿＝加粗线（与面板外框同粗） */
}
.pay-tbl td { border-bottom: 1px solid #eef1f5; }
.pay-tbl tbody tr:last-child td { border-bottom: 0; }   /* 末行不留重复底边（外框已有） */
.pay-tbl-hd { font-size: 12px; color: #8a8f98; margin: 6px 0 4px; }

/* 权限组表格：第 3 列容器（V7.22） */
.pg-td-items { min-width: 0; }
.pg-td-page .pg-chk { display: inline-flex; }

/* ==================== V7.61：待办事项（欢迎光临 → 待办事项；**只开放给管理员**） ====================
   布局＝**左右两列**（V7.62）：左＝全部待办 + 留痕流水；右＝今日必做 + 其下方的新增/编辑表单；
  卡片式（与欢迎页既有卡片观感一致），但**用 class 而非内联样式**（欢迎页那套内联是历史写法）。
   配色沿用项目既有色值，不新增：红 #c5221f（高/逾期）、黄 #a15c00+#fff6cf（中）、灰 #5f5e5a（低）、
   蓝 #0b57d0（周期标签/进行中）、绿 #137333（成功提示）。 */
#page-todo { padding: 6px 8px 22px; }

/* V7.62（用户 2026-09-14）：**左右两列** —— 左＝全部待办（含留痕流水），右＝今日必做（含其下方的新增/编辑表单）。
   左列自适应占满（V7.63 起「全部待办」已由 9 列表格改为卡片行，对宽度不再敏感）；右列固定 460px（够放
   「优先级+标题+标签+到期+3 个按钮」不至于频繁换行）。
   ⚠️ 列内 `.todo-box` 的 margin-bottom 置 0，间距交给列的 `gap`，否则会变成"双份间距"。 */
.todo-cols { display: flex; align-items: flex-start; gap: 12px; }
.todo-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.todo-col-left { flex: 1 1 auto; }
.todo-col-right { flex: 0 0 460px; max-width: 460px; }
.todo-col .todo-box { margin-bottom: 0; }

.todo-box {
  background: #fff; border: 1px solid #d5e0ec; border-radius: 12px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .05); margin-bottom: 12px; overflow: hidden;
}
.todo-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 14px; border-bottom: 1px solid #eef2f7; background: #fafcff;
}
.todo-h-title { font-size: 15px; font-weight: 700; color: #1a355e; }
.todo-h-sub { font-size: 12px; color: #8a94a6; }
.todo-flex { flex: 1 1 auto; }
.todo-mini { padding: 4px 12px; font-size: 12.5px; }
.todo-sel, .todo-kw {
  font-size: 12.5px; padding: 4px 8px; border: 1px solid var(--border);
  border-radius: 6px; background: #fff; color: var(--text);
}
.todo-kw { width: 190px; }
.todo-body { padding: 8px 14px 12px; }

/* 待办条目（今日必做 + 全部待办**共用**同一套卡片样式；V7.63 起「全部待办」也是卡片行）
   左侧色条＝优先级/状态：逾期红；其余灰（V7.63 去掉了「进行中」态 → 不再有蓝条） */
.todo-item {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 6px;
  border: 1px solid #e6ecf3; border-left: 4px solid #b9c0c9; border-radius: 8px;
  background: #fdfefe;
}
.todo-item.overdue { border-left-color: #c5221f; background: #fdf3f2; }
/* 已完成（只在「全部待办」里出现）→ 左条转绿、标题灰＋删除线、整条淡绿底 */
.todo-item.done { border-left-color: #137333; background: #f6fbf7; }
.todo-item.done .todo-item-title { color: #9aa0a6; text-decoration: line-through; }
.todo-item.done .todo-due { color: #9aa0a6; }
.todo-item-title { font-size: 14px; font-weight: 600; }
.todo-tag {
  font-size: 12px; color: #5f6368; background: #f1f3f4;
  padding: 1px 7px; border-radius: 10px; white-space: nowrap;
}
.todo-tag.repeat { color: #0b57d0; background: #dbe9fd; }
.todo-tag.done { color: #137333; background: #e6f4ea; }
.todo-due { font-size: 12.5px; color: #5f6368; white-space: nowrap; }
.todo-item.overdue .todo-due { color: #c5221f; font-weight: 600; }
.todo-empty-today { padding: 16px 0; text-align: center; color: #8a94a6; font-size: 13px; }

/* 优先级标签 */
.todo-pri {
  display: inline-block; min-width: 28px; text-align: center;
  font-size: 12px; font-weight: 700; padding: 1px 6px; border-radius: 4px; white-space: nowrap;
}
.pri-high { color: #c5221f; background: #fdecea; }
.pri-mid { color: #a15c00; background: #fff6cf; }
.pri-low { color: #5f5e5a; background: #f1f3f4; }

/* 表格样式：V7.63 起只剩「留痕流水」一张表在用 .todo-tbl（「全部待办」已改卡片行）。
   原来给 9 列待办表用的 .todo-td-title / .todo-td-ops / .todo-row-done / .todo-overdue-text 已随表格一并删除。 */
.todo-tbl td, .todo-tbl th { font-size: 13px; padding: 6px 8px; }
.todo-td-small { font-size: 12px; color: #5f6368; white-space: nowrap; }
.todo-del { color: #c5221f; }

/* 新增 / 编辑表单（就地展开） */
/* V7.62：表单移入 **右列（460px）** → 网格由 4 列改 **2 列**（4 列会挤成一团）；窄屏再降 1 列。 */
.todo-form-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px 12px;
}
.todo-f { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; color: #5f6368; }
.todo-f-wide { grid-column: 1 / -1; }
.todo-f input, .todo-f select { font-size: 13px; padding: 5px 8px; }
.todo-form-tip { color: #8a94a6; font-size: 12px; line-height: 1.6; }
.todo-msg { font-size: 12.5px; min-height: 16px; }

/* 「提醒」：选项卡上的今日必做计数；有逾期时整块标红 */
.tab.todo-tab-alert { color: #c5221f; font-weight: 700; }

/* 窄屏堆叠：窗口不够宽时两列变一列，且**「今日必做」提到最前**（它是提醒区，先看到更要紧）。 */
@media (max-width: 1100px) {
  .todo-cols { flex-direction: column; }
  .todo-col-right { flex: 1 1 auto; max-width: none; width: 100%; order: -1; }
  .todo-col-left { width: 100%; }
}
@media (max-width: 900px) {
  .todo-form-grid { grid-template-columns: 1fr; }
  .todo-kw { width: 130px; }
}

/* ==================== 日志管理（V8.34：全站操作日志） ====================
   复用既有变量与 .review-toolbar / .user-table / .page-btn；
   本页**没有删除或编辑入口** —— 不写任何"操作列"样式是有意的。 */
.audit-note {
  font-size: 13px;
  line-height: 1.7;
  color: var(--text-sub);
  background: #fafcfb;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 12px;
}
.audit-note b { color: var(--text); font-weight: 600; }
.audit-note code {
  font-size: 12px;
  background: var(--sidebar-bg);
  padding: 1px 5px;
  border-radius: 4px;
}
.audit-toolbar { gap: 8px; }
.audit-lab { font-size: 13px; color: var(--text-sub); }
.audit-sel,
.audit-date { width: auto; margin-bottom: 0; padding: 7px 10px; font-size: 14px; }
.audit-kw { flex: 1 1 190px; width: auto; margin-bottom: 0; padding: 7px 10px; font-size: 14px; }
.audit-export-btn { margin-left: auto; }
.audit-tabs { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.audit-cat {
  padding: 6px 12px;
  font-size: 13px;
  cursor: pointer;
  border: 1px solid var(--border);
  background: #fff;
  border-radius: 999px;
  color: var(--text-sub);
}
.audit-cat.active {
  background: var(--sidebar-bg);
  border-color: var(--primary);
  color: var(--primary-dark);
  font-weight: 600;
}
.audit-count { margin-left: auto; font-size: 13px; color: var(--text-sub); }
.audit-table { table-layout: fixed; }
.audit-table td { font-size: 13px; }
.audit-time { white-space: nowrap; color: var(--text-sub); }
.audit-act { word-break: break-all; }
.audit-dim { color: var(--text-sub); }
.audit-res-ok { color: var(--ok); font-weight: 600; }
.audit-res-fail { color: var(--danger); }
.audit-res-denied { color: #b54708; font-weight: 600; }
.audit-res-error { color: var(--danger); font-weight: 600; }
/* V8.35：详情按钮**加宽并强制单行**（原来「详情」两字在窄列里会被挤成两行） */
.audit-detail-btn {
  font-size: 12px;
  padding: 2px 12px;
  min-width: 52px;
  white-space: nowrap;
}
.audit-detail-row > td { background: #fafcfb; padding: 8px 12px; }
/* V8.35：详情里的短字段走「四格一行」—— 第 1 行＝账号 / 权限组 / 动作 / 接口（用户要求），
   第 2 行＝对象 / 功能点 / 耗时 / 浏览器；只有长文本才各占一行 → 详情框整体更矮。 */
.audit-kv-row {
  display: grid;
  grid-template-columns: 0.85fr 0.85fr 1.3fr 1.6fr;
  gap: 2px 14px;
  font-size: 13px;
  line-height: 1.5;
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px dashed var(--border);
}
.audit-kv-row2 {
  grid-template-columns: 0.85fr 0.85fr 0.85fr 1.6fr;
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
  color: var(--text-sub);
}
.audit-kv-cell { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.audit-kv-cell b { flex: 0 0 auto; color: var(--text-sub); font-weight: 500; }
.audit-kv-cell span { min-width: 0; word-break: break-all; }
.audit-kv {
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr);
  gap: 3px 10px;
  font-size: 13px;
  line-height: 1.5;
  margin-top: 4px;
}
.audit-kv b { color: var(--text-sub); font-weight: 500; }
.audit-mono {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px;
  word-break: break-all;
  white-space: pre-wrap;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 6px;
}
/* 窄屏：详情四格折成两列 */
@media (max-width: 1100px) {
  .audit-kv-row,
  .audit-kv-row2 { grid-template-columns: 1fr 1fr; }
}


/* ==================== 伙伴云 Token 管理（V8.41，用户 2026-09-16） ====================
   布局（由我设计）：说明条 → 状态行（来源 + 掩码值）→ 编辑行（输入框 + 4 个按钮）→ 结果行
   → 就地展开的确认框（管理员密码 + 取消/确认）。
   ⚠️ 输入框默认 `type=password`（掩码）——「显示」按钮切换明文（防肩窥，用户没指定，按安全默认做）。 */
.hbt-note {
  font-size: 13px;
  line-height: 1.75;
  color: var(--text-sub);
  background: #fffdf6;
  border: 1px solid #f0e3c4;
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 12px;
}
.hbt-note b { color: var(--text); }
.hbt-status {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--text-sub);
  margin-bottom: 10px;
}
.hbt-lab { color: var(--text-sub); font-size: 13px; white-space: nowrap; }
.hbt-src {
  font-size: 12px;
  color: var(--primary-dark);
  background: #eef7f1;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 8px;
}
.hbt-mask {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--text);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 8px;
}
.hbt-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;          /* 窄屏自动折行，不做横向滚动 */
  margin-bottom: 10px;
}
.hbt-input {
  flex: 1 1 360px;
  min-width: 240px;
  margin-bottom: 0;         /* ⚠️ 复用 .text-input 时会自带下边距，行内要清掉 */
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
}
.hbt-btn { white-space: nowrap; }
.hbt-btn:disabled { opacity: .5; cursor: not-allowed; }
.hbt-msg {
  font-size: 13px;
  line-height: 1.7;
  min-height: 22px;
  color: var(--text-sub);
}
.hbt-msg.ok { color: var(--ok); }
.hbt-msg.err { color: var(--danger); }
.hbt-msg.warn { color: #8a6a1f; }
.hbt-confirm {
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fafcfb;
  padding: 12px 14px;
  max-width: 640px;
}
.hbt-cf-title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 10px; }
.hbt-cf-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.hbt-pwd { width: 300px; max-width: 100%; margin-bottom: 0; }
.hbt-cf-tip { font-size: 12px; line-height: 1.7; color: var(--text-sub); margin: 10px 0; }
.hbt-cf-btns { display: flex; gap: 8px; }

/* ==================== 揽收检查（V8.66，用户 2026-09-18；V8.72 改版 / V8.74 去标签 / V8.76 去按钮） =========
   V8.72（用户 2026-09-18）版式改版：
   · **左边那个输入框（`#kd-nums` / `.kd-col` / `.kd-box` / label「快递单号」）整个删除** ——
     表格成为**唯一载体**：单号写进表格行，检查结果也写回这些行。
   · 表格新增**第 4 列「异常原因」**（列序：快递单号 ｜ 快递公司 ｜ 揽收时间 ｜ 异常原因），
     查询失败/未揽收的原因直接写在行里（例如「识别不出快递公司…」），⛔ 不再逐单拼进顶部消息。
   · 按钮**竖排、吸顶（磁吸）在表格右列**。
   V8.74（用户 2026-09-18）：「表头标签「已揽收」已删除」⇒ `.kd-lab` 规则一并删除（全站已无引用），
   且按钮列的 `padding-top` 由 24px 改为 0（原来那 24px 是为对齐"标签下方"的表格）。
   V8.76（用户 2026-09-18）：**删掉读系统剪贴板的那个按钮**（`#kd-paste`）—— 它靠
   `navigator.clipboard.readText()`，只在安全上下文（HTTPS / localhost）存在，内网 http 下没有该 API、
   浏览器会拦 ⇒ 唯一入口＝**点击表格空白处后按 Ctrl+V**（`paste` 事件不需要安全上下文）。
   本段随之新增：`.kd-tbl-wrap:focus`（聚焦反馈）、`.kd-hint`（表格内常驻提示）、
   `.kd-rate-warn`（右侧快递100 请求限制提示，粗体红字）。
   表格高＝**整个页面**、宽＝占满剩余空间；高由 `app.js::kdFitSizes()` 量算后写到
   `#kd-picked-wrap` 的 style，宽交给 flex（这里只给外观）。
   V8.78（用户 2026-09-19）：表头四列扩到**五列** —— 在「揽收时间」与「异常原因」之间新增
   **第 4 列「称重发货结果」**（列序：快递单号 ｜ 快递公司 ｜ 揽收时间 ｜ 称重发货结果 ｜ 异常原因）。
   本节随之新增 `.kd-shipres` / `-ok` / `-skip` / `-bad`（见下）。 */
.kd-wrap { display: flex; align-items: flex-start; gap: 16px; }

/* V8.72：表格列（沿用 V8.70 的 `.kd-picked-col`，只改名）：`flex: 1 1 0` 占满剩余宽度。
   `min-width: 0` 是 flex 子项**真能收缩**的前提；漏了它窄屏下会撑破整页（横向滚动）。 */
.kd-table-col { display: flex; flex-direction: column; flex: 1 1 0; min-width: 0; }

/* V8.72：按钮列 —— 按钮竖排、**磁吸（sticky）在表格右侧**（用户原话「3 个按钮按上、中、下排列，
   磁吸固定在表格展示区域的右侧」；V8.76 起只剩 2 个；**V8.79 起恢复 3 个**：
   第 1 步：识别快递公司 / 第 2 步：检查是否揽收 / 第 3 步：批量发货出库）。
   · `position: sticky; top: calc(var(--tabs-h) + 12px)`：吸在一级选项卡下方 12px 处。
     `--tabs-h` 由 `app.js::syncTabsHeight()` 实时写入（⛔ 别写死像素，同本文件
     `.lin-settled-bar` / `.pg-table-wrap` 的吸顶口径）。
   · 🔴 `align-self: flex-start` **必须写**：flex 子项默认 `stretch`，会被拉成与 `.kd-wrap` 一样高
     ⇒ 没有可移动空间，`position: sticky` 等于没吸（实测：删掉它，「没被拉成一整列高」那条断言会红）。
   · `flex: 0 0 220px`：宽度固定，⛔ 不被内容压缩（压了按钮文案会换行/挤成两行）。
   · V8.74：`padding-top` 由 24px 改为 **0** —— 那 24px 原本是为对齐"标签（已揽收）下方"的表格，
     标签删除后表格顶边上移，再留 24px 会让按钮比表格低一截（用户要的是"磁吸在表格展示区域的右侧"）。 */
.kd-btn-col {
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  flex: 0 0 220px; box-sizing: border-box; padding-top: 0;
  position: sticky; top: calc(var(--tabs-h, 54px) + 12px); align-self: flex-start; z-index: 3;
}
.kd-btn-col .btn { white-space: nowrap; }
.kd-btn-col .msg { max-width: 100%; text-align: left; }
/* 🔴 V8.82：「统一查询电话」输入区 —— 放在三个按钮**上面**（先填参数、再走 1→2→3 步）。
   ⚠️ 必须把 `.text-input` 自带的 14px 下边距归零/改小：那是给"竖排表单"留的，
      留着会把下面的按钮往下顶、整列变高（同 `.lsc-cuts` / `.lsc-note-wrap` 那两处记过的坑）。 */
.kd-phone-wrap { margin: 0 0 2px; }
/* 🔴 V8.82 修（真 bug）：类名必须与 `index.html` 里 `class="kd-phone-lab"` **一字不差**。
   首次写完时 CSS 写的是 `.kd-lab-sm`、HTML 写的是 `kd-phone-lab` ⇒ **这条规则整条不生效**
   （label 不会变块级、字号/颜色/间距全没上），而**测试当时全绿** —— 因为探针只读 label 的**文本**、
   不读**计算样式**。已补几何断言（量 label 的 display/fontSize）把这类「HTML/CSS 类名不一致」钉死。
   ⛔ 另外**不许**把类名起成 `kd-lab-sm`：它**含 `kd-lab` 子串**，会撞上 `make_kd_probe.py` 里
   「表头标签 `.kd-lab` 不许回来」那条**源码级反向断言**（本版编辑时正是先撞上它才改的名）。 */
.kd-phone-wrap .kd-phone-lab { display: block; font-size: 12px; color: var(--text-sub); margin-bottom: 4px; }
.kd-phone-wrap .text-input { width: 100%; box-sizing: border-box; margin: 0; font-size: 13px; }

/* V8.70 结果区＝**真表格**（V8.71：滚动容器＝外层 `.kd-tbl-wrap`，⛔ 不是表格自己）。
   根因（用户报「明细结果和表头重叠、表头没在最上面」）：把 `display:block; overflow:auto`
   写在 `.kd-tbl` 上 —— `<table>` 一旦变成块盒，thead/tbody 就不再按真实表格布局参与排版
   （浏览器会插入匿名表格包装）⇒ 列对不上、表头与明细互相压；而 `overflow` 挂在表格自身，
   `position: sticky` 又**没有可吸附的滚动容器**。
   ⇒ 表格恢复真表格，边框/圆角/背景/overflow 全移到 `.kd-tbl-wrap`，JS 的高也写在这一层。 */
.kd-tbl-wrap {
  box-sizing: border-box; overflow: auto;
  border: 1px solid #cfd8e3; border-radius: 8px; background: #fff;
}
/* V8.76：**聚焦反馈** —— 用户点一下表格空白处时，`tabindex="-1"` 让它拿到焦点
   （这也是「再按 Ctrl+V 粘单号」的前提）。给一个轻微反馈（边框变主题色）让"点一下"看得见，
   否则用户不知道点没点到、也不知道该在哪里按 Ctrl+V。⛔ 只改边框色，不动尺寸/内边距
   （改了会挪列位置、四列对齐那条断言会红）。 */
.kd-tbl-wrap:focus { border-color: var(--primary); outline: none; }
/* V8.72：⛔ 去掉了 `min-width: max-content` —— 第 4 列「异常原因」是长中文句
   （如「识别不出快递公司（可在单号后加空格与快递公司编码）」），`max-content` 会把**整句**
   算成表格最小宽度 ⇒ 表格永远比视口宽、原因列永远不换行。
   超长单号的横向溢出改由 `.kd-tbl tbody td { white-space: nowrap }` 的 min-content 撑出（同效）。 */
.kd-tbl {
  box-sizing: border-box; width: 100%;
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px;
  border-collapse: collapse;
}
.kd-tbl thead th {
  color: var(--text-sub);
  font-size: 12px; font-weight: 600; text-align: left; white-space: nowrap;
  padding: 8px 12px;
}
/* V8.71：表头吸顶（吸附在 `.kd-tbl-wrap` 这个滚动容器的顶部）。
   · 必须加 `section.page` 前缀提高特异性，否则被全局 `section.page table thead th
     { top: var(--tabs-h) }` 压住（会吸到**页面**顶部，不是容器顶部）—— 同 `.pg-table-wrap` 的口径。
   · 🔴 背景必须**不透明**（这里用 #f7f9fc，与 `.pg-table` 表头同款思路）：透明的话明细会从表头
     下面透出来，就是用户说的"重叠"。
   · ⛔ 分隔线用 `inset box-shadow`，不用 `border-bottom`：本表是 `border-collapse: collapse`，
     sticky 表头的 border **会随滚动丢失**（全站统一口径，见本文件「全站明细表表头统一吸顶」注释）。 */
section.page .kd-tbl-wrap table thead th {
  position: sticky; top: 0; z-index: 2; background: #f7f9fc;
  box-shadow: inset 0 -1px 0 0 #e6ecf3;
}
.kd-tbl tbody td { padding: 6px 12px; white-space: nowrap; border-bottom: 1px solid #f1f4f8; }
/* V8.80（用户 2026-09-19）：**列宽按内容自适应** —— 前 4 列贴合内容、剩余宽度**全部**给第 5 列「异常原因」。
   用户原话：「快递单号、快递公司、揽收时间、称重发货结果，这几栏自适应内容宽度，剩余宽度全部留给异常原因」。
   · **前 4 列＝`width: 1%`**：本表 `tbody td` 有 `white-space: nowrap` ⇒ 列的**最小宽就是内容宽**；
     给一个极小的 `1%`（`table-layout: auto`）⇒ 浏览器把它们**收缩到内容宽**，⛔ 不留多余空白。
     ⛔ **不许写固定像素宽**：列内容会变长（22 位单号、长公司名），写死必然要么截断、要么留白。
   · **第 5 列＝`width: 100%`** ⇒ 吃掉全部剩余宽度（它是全表**唯一折行**的列，长中文句在这里换行）。
   ⚠️ 这两条**只动 `width`**：`padding` / 字号 / `white-space` 一个字都没改 ⇒ 五列对齐那条判据的基准不变。 */
.kd-tbl tbody td.kd-num,
.kd-tbl tbody td.kd-com,
.kd-tbl tbody td.kd-time,
.kd-tbl tbody td.kd-shipres { width: 1%; }
.kd-tbl .kd-time { color: var(--text-sub); }   /* 时间列比正文浅一档（只改颜色，不改字号） */
/* V8.72：第 4 列「异常原因」—— 全表**唯一允许折行**的列（长中文句），
   查询失败（`ok === false`，如「识别不出快递公司…」）额外标红，与「尚未揽收」（灰字）区分开。
   🔴 V8.80：`min-width: 240px` **实测后决定留着** —— 试过去掉，结果这一列被四个 nowrap 列
     **压到 72px**（＝表头「异常原因」四个字的 min-content）⇒ 那句 24 字的原因被挤成
     175px 高的竖条（行高翻 5 倍，连表头吸顶那条判据都被带红）。留着 240px：
     表格本来就被 200 位超长单号撑出横向滚动（容器滚，⛔ 整页不滚），这 240px 不会额外
     顶出**整页**滚动；而"吃剩余"由 `width: 100%` 负责（有多余宽度时全给它，实测见几何脚本）。 */
.kd-tbl tbody td.kd-reason { width: 100%; min-width: 240px; white-space: normal; color: var(--text-sub); }
.kd-tbl tbody td.kd-reason.kd-reason-bad { color: #c0392b; }
.kd-tbl tr.kd-bad td { color: #c0392b; }       /* 发货**失败**的行：红字色，一眼看出哪几单没发出去 */
/* V8.70：发货**被跳过**的行（后端护栏：同一单号 30 分钟内已成功发货过 ⇒ 本次没发请求）——
   弱化字色、⛔ 不标红：它**没出错**，只是没重复发；与 `.kd-bad` 必须一眼能分开。 */
.kd-tbl tr.kd-skip td { color: var(--text-sub); }
/* V8.78：第 4 列「称重发货结果」—— 允许折行（失败原因可能是长中文句），最小宽比原因列小。
   ⚠️ `.kd-bad` / `.kd-skip` 是 `tr td` 级颜色（整行变色），与本节 `tbody td.kd-shipres-*` **同特异性**
      （都＝2 个类 + 2 个类型选择器）⇒ 靠**源码顺序**取胜：本段**排在 `.kd-bad` / `.kd-skip` 之后**，
      所以本列颜色不会被行级红/灰抢走（几何脚本会量 computed color 证明真的生效）。
   · 成功＝项目现成的 `--ok`（绿）；跳过＝弱化 `--text-sub`（⛔ 不标红，它没出错）；
     失败＝`#c0392b`（与 `.kd-bad` / `.kd-reason-bad` 同色，⛔ 不引入新色系）。
   · ⛔ 别改 `.kd-tbl tbody td` 的 padding/字号（改了列位置会挪、五列对齐那条会红）。 */
/* 🔴 V8.80（用户 2026-09-19）：这一列改成**按内容自适应宽度** ⇒ **删掉了原来的
   `white-space: normal; min-width: 120px`**（让 it 继承 `.kd-tbl tbody td` 的 `nowrap`）。
   为什么必须删：`normal`（可折行）会让列宽**小于**内容宽 —— 实测夹具里列宽 **120px**、内容需 **232px**
   （「本次跳过（此前已发货）」那行被挤成多行），与用户要的「自适应内容宽度」**直接矛盾**。
   ⚠️ 代价：失败原因是长句时这一列会变宽 ⇒ 表格更宽；但横向滚动由 `.kd-tbl-wrap` 兜住（**整页仍不滚**）。
   ⚠️ 折行让位：现在全表**只有第 5 列「异常原因」**可折行（它也因此成为唯一"吃剩余"的列）。 */
.kd-tbl tbody td.kd-shipres-ok { color: var(--ok); }        /* 成功＝项目现成的 --ok */
.kd-tbl tbody td.kd-shipres-skip { color: var(--text-sub); }/* 跳过＝弱化，⛔ 不标红（它没出错） */
.kd-tbl tbody td.kd-shipres-bad { color: #c0392b; }         /* 失败＝红（与 .kd-bad/.kd-reason-bad 同色） */
.kd-tbl .kd-empty { color: var(--text-sub); white-space: normal; }
/* V8.76：表格里的**常驻提示**（`<tfoot>` 里那一行，文案＝「点击表格空白处，按 Ctrl+V粘贴快递单号」）。
   · 弱化字色（同 `.kd-time` / `.kd-empty` 的 `var(--text-sub)`）、左对齐、`white-space: normal`（可折行）。
   · `padding` 与 `td`（6px 12px）一致 ⇒ 与数据行左右对齐。
   · 🔴 **别改字号**：字号一改，列宽口径就变（四列对齐/表格宽度那几条断言的基准会跟着动）。 */
.kd-tbl tfoot td.kd-hint {
  color: var(--text-sub); white-space: normal; text-align: left;
  padding: 6px 12px;
}
/* V8.76：右侧按钮下方「快递100 请求限制」提示（用户要求：**粗体 + 红字**，给普通作业员看）。
   · `font-weight: 700`（粗）—— 「一眼就是警告」。
   · 红色沿用本页既有的 `#c0392b`（与 `.kd-bad` / `.kd-reason-bad` 同色系），⛔ 别引入新色值体系。
   · `white-space: normal` + `line-height`：三句话要能换行、读得舒服。 */
.kd-rate-warn {
  font-weight: 700; color: #c0392b; font-size: 12px; line-height: 1.6;
  margin-top: 10px; white-space: normal;
}


/* ==================== 杂项处理 · 外仓入库（V10.0，用户 2026-09-21）====================
   页面＝`#page-wcwh`。类名统一 `wh-` 前缀，⛔ 别改到别的页面上（这一页全是新类，不共享）。 */
.wh-wrap { font-family: system-ui, -apple-system, "PingFang SC", sans-serif; }
/* 🔴 V11.26（用户 2026-09-22）：外仓入库页**纵向留白压缩**（用户「压缩多个区块之间的垂直高度间隙，并减少各区块内部的空白高度…使整体布局更紧凑，在单屏内显示更多内容」；力度＝中等，省约 19%）。
   ⚠️ **只动纵向**（padding 的第二/第一个值只改上下、左右一个字不动）—— 横向留白是 `whAlignLoc()` 那批**精确对齐判据**的依据，动横向会成片打红。
   ⛔ **不动的**：按钮/输入框的 40px 高度（扫码枪与触摸可用性 + 大批判据依赖）、表格的行高与表头高、字号。
   ⚠️ 这几条改完总高 700 → 约 566px；若要回退，把上下内边距/外边距改回注释里记的原值即可。 */
.wh-sec { background:#fff; border:1px solid #dfe9e3; border-radius:12px; padding:8px 16px; margin-bottom:10px; }
/* ⚠️ 这里原有两条规则（卡片标题行 `h3` 与它下面那行「通知人员」小标题）**已删**：
   用户 2026-09-21 晚要求取消三张卡的全部说明文案，三张卡现在都没有 `<h3>` 了 ⇒ 留着就是死规则
   （别以为是"标题漏渲染了"）。⛔ 注释里也别把那两个已经删掉的类名/选择器原样写出来 ——
   探针夹具会把整份 `style.css` 内联进去，字面串会污染全仓 grep（这一步刚踩过）。 */
.wh-row { display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.wh-row .lab { font-size:13px; color:#20302a; font-weight:600; min-width:64px; }
/* 工具栏一行（用户 2026-09-21 晚：「元素置于同一行，统一并优化高度，使其对齐一致」）。
   定式：输入框与按钮写**同一个显式高度**（40px）＋ `box-sizing:border-box`，并清掉 `.text-input` 自带的
   `margin-bottom:14px` —— 那个 margin 会把输入框与按钮的中心线错开，是"看着没对齐"的真凶。
   只写 `align-items:center` 是不够的：高度不同的两个控件中心线对齐、上下沿照样错开。 */
.wh-tool { gap:10px; }
.wh-tool .lab { min-width:auto; white-space:nowrap; }
.wh-tool .text-input { flex:1 1 200px; width:auto; min-width:160px; height:40px; margin:0;
  padding:0 12px; font-size:14px; box-sizing:border-box; }
/* ⚠️ V11.1：「选择货位」那一行也放了一个**同规格**按钮（「直接完成」，用户 2026-09-22 从工具栏挪过来的）
   ⇒ 选择器扩成两组，两处按钮**永远同一套尺寸**（⛔ 别各写一份，迟早漂）。
   ⚠️ V11.15：「识别单号」那一行也并进本组（用户要求它与输入框「填充一整行」⇒ 尺寸必须与工具栏一致）。 */
.wh-tool .btn, .wh-locrow .btn, .wh-detect .btn { flex:0 0 auto; height:40px; padding:0 16px; font-size:14px; line-height:1;
  box-sizing:border-box; white-space:nowrap;
  /* 🔴 V11.0（用户 2026-09-22 第 3 条）：两个工具栏的按钮**统一宽度 104px**（＝原来最宽的
     「采购单查询」的实测宽：5 个汉字 70px ＋ 左右 padding 32px ＋ 边框 2px）。
     ⇒ 采购区的三个与订单区的三个**同位置一样大**，两行右端也齐。
     ⚠️ 用 `min-width` 而不是 `width`：以后哪个按钮文字变长时**让它自己长**，别被截断/溢出。 */
  min-width:104px; }
/* 淡红色按钮（用户 2026-09-21 晚第 5 条：「复制采购数量」颜色改淡红、样式与另两个按钮一致）。
   ⚠️ V10.8 起按钮**文案例名**为「复制数量」（用户 2026-09-22），`id` 仍是 `#wh-pcopy`、行为不变。 */
.wh-btn-soft { background:#fdecec; border-color:#f2b0b0; color:#a32d2d; }
.wh-btn-soft:hover { background:#fadada; }
/* 淡黄色按钮（用户 2026-09-22：「直接完成」按钮的颜色换为淡黄色）。
   ⚠️ 色值**沿用项目既有那套黄**（`#a15c00` on `#fff6cf` / 边框 `#f0dda0`，与 `.wh-msg .warn`、
   `.pri-mid`、登录页提示同族），⛔ 别自己调一个新黄。 */
.wh-btn-yellow { background:#fff6cf; border-color:#f0dda0; color:#a15c00; }
.wh-btn-yellow:hover { background:#fdf0bf; }
/* item_id（写入伙伴云后返回）—— 用户 2026-09-21 晚：挪到「采购入库」按钮**后面**、**加粗**显示 */
/* ============ item_id 信息展示框（V11.2，用户 2026-09-22）============
   位置＝「选择货位」那一行里、**正下方于「采购单查询 / 复制数量」两个按钮**（宽度由 `whAlignLoc()`
   量出来写成 inline；⛔ 别写死像素）。
   🔴 没有 item_id 时用 **`visibility:hidden`**（＝**保留占位**）：这一行的宽度不跳、「直接完成」的位置也不动；
   ⛔ 别改成 `display:none`（那就把占位抽走了，整行会重排）。
   ⚠️ 内容放不下时**省略号截断**，全值挂在 `title` 上（鼠标悬停看全）—— 与「货品名称」列同一套做法。 */
.wh-itemid { box-sizing:border-box; height:40px; display:inline-flex; align-items:center;
  border:1px solid #dfe9e3; border-radius:8px; background:#f8fafc; padding:0 9px;
  font-size:12.5px; color:#20302a; flex:0 0 auto; visibility:hidden; overflow:hidden; white-space:nowrap; }
.wh-itemid.on { visibility:visible; }
.wh-itemid b { font-weight:700; font-family:ui-monospace, Menlo, monospace; color:#0b57d0;
  overflow:hidden; text-overflow:ellipsis; }
/* 状态文字（原本是内联样式）—— 允许收缩，别把整行撑出横向滚动 */
.wh-st { font-size:13px; color:#66766e; flex:0 1 auto; min-width:0; }
/* 通知人员（框内：标题 ＋ 勾选项 ＋ 已选行，三者在**同一条流**里）与指示灯**同一行**
   （用户 2026-09-21 晚两轮调整：第 1 轮「同一行 ＋ 缩短通知人员」，第 2 轮「灯太宽，空间留给通知人员」） */
/* 🔴 两个框**等高**（用户 2026-09-22：「指示灯元素框高度和通知人员相同高度」）：
   `align-items:stretch` ⇒ 一行里两个框都取该行的交叉尺寸 ＝ 等高。
   ⛔ 别写回 `flex-start`（那是"顶对齐、各高各的"，实测 1440 档框 80px / 灯 116px，灯在决定行高）。 */
.wh-top { display:flex; align-items:stretch; gap:14px; flex-wrap:wrap; }
/* 🔴 通知人员框＝吃掉**指示灯之外的全部剩余宽度**（用户 2026-09-21 晚第 1 条）。
   ⚠️ 必须是 `flex:1 1 **0**`（basis 为 0）而不是 `1 1 auto`：`auto` 的基准是**内容宽**，
      17 个名字全 nowrap 时基准极大 ⇒ 会先把灯挤到**下一行**（`.wh-top` 是 wrap）。
   ⛔ 也别写回固定宽度（V10.1 曾把这一栏钉成 560px、并给了 620px 的上限 —— 那条已被用户否掉：
      他说「空间留给通知人员」）。⚠️ 连注释里也别把那两个旧数值原样写出来：**它们会让「旧口径清干净了吗」
      这类 grep 复核永远非 0**（刚踩过一次）。 */
.wh-ops { flex:1 1 0; min-width:0; display:flex; flex-wrap:wrap; align-items:center;
  gap:6px 18px; padding:6px 12px; background:#f7fbf9; border:1px solid #e0ebe5; border-radius:10px; }
/* 🔴 「通知人员」四个字与「已选 N 人 / 清空勾选」要跟人员名字在**同一条流**里：
   前者在第一个名字前、后者在最后一个名字后（用户 2026-09-21 晚第 3/4 条）。
   机制＝`display:contents`：这个容器**自己不生成盒子**，里面的 label 直接成为 `.wh-ops` 的子项，
   于是「标题 / 名字 / 已选行 / 清空按钮」全在同一条 wrap 流里。
   ⛔ 别改成 `display:flex`（那样三部分各占一行 —— 正是这轮要消掉的样子）。
   ⚠️ `rk-ops-wrap` 仍在它身上（勾选高亮的既有规则照旧生效，见下条）。 */
.wh-ops-list { display:contents; }
.wh-ops-title { font-size:13px; font-weight:600; color:#20302a; white-space:nowrap; }
.wh-ops-title .wh-req { color:#b3261e; }
.wh-ops-list label { display:inline-flex; align-items:center; gap:6px; font-size:13px; cursor:pointer; white-space:nowrap; }
/* 🔴 V11.9（用户 2026-09-22）：① 「复选框与人名应靠近显示」—— 这条页面上原本被两处间距叠加拉开：
      `.rk-ops-wrap input[type=checkbox]{margin-right:6px}` ＋ label 的 `gap:6px` ＝ 12px ⇒ 这里把 margin 归零，只留 gap。
   ② 「勾选后对应人名填充为淡绿色」—— 与灯的 ok 底色同族（`#eaf5ec`）。
      ⚠️ 既有那条「勾选加粗变蓝」是**四个页面共用**的（`.rk-ops-wrap …`），⛔ 别动它；
         这里只加一条**页面作用域**（`#wh-notify`）的底色规则，用 `box-shadow` 填充 ⇒ **零布局影响**（不推字、不换行）。 */
#wh-notify input[type="checkbox"] { margin-right:0; }
#wh-notify input[type="checkbox"]:checked + .ops-name { background:#eaf5ec; border-radius:4px;
  box-shadow:0 0 0 3px #eaf5ec; }
/* ============ V11.9（用户 2026-09-22）通知人员与指示灯之间的「人名方框」 ============
   用户原话：「在通知人员区与指示灯区之间新增一个方框，内含人名输入框和一个按钮；当输入内容时，
   和通知人员区中的人名对比 …… 当输入框为空时，按钮失效。」
   ⚠️ 它夹在 `.wh-ops`（`flex:1 1 0`，吃掉剩余宽度）与 `#wh-lamp`（`flex:0 0 auto`）之间 ⇒ 自身 `flex:0 0 auto`，
      ⛔ 别给它 `flex:1`（会把通知人员区挤扁、还可能把灯挤到下一行）。
   ⚠️ 行是 `align-items:stretch`（两个框等高）⇒ 本框也会被拉成同样高；内容用**竖排居中**，看着才不空。
   🔴 注释必须写成 CSS 块注释 —— 我第一版用了「#」开头（那是 Python 的写法）⇒ 浏览器把注释当选择器、
      整条 `.wh-namebox` 规则被吞掉（表框"没有边框"就是这么来的，几何判据当场抓到）。 */
/* 🔴 V11.10（用户 2026-09-22）改成**上下两行**（输入框在上、按钮在下）—— 用户明确要求。
   ⚠️ 高度必须**控制在这一行原有高度以内**：整行是 `align-items:stretch`，方框一变高就把行撑高，
      而圆点是「吃高度」的 ⇒ 会连带变大（V11.9 那版竖排就是这么把圆点拉成长方形的）。
      算式（1440 档原行高 80）：8(上内边距) + 28(输入框) + 4(间距) + 28(按钮) + 8(下内边距) + 2(边框) ＝ **78 ≤ 80** ✓
      ⇒ 行高不变、圆点尺寸不变。⛔ 别把内层控件加高（一加就顶破 80）。 */
.wh-namebox { flex:0 0 auto; display:inline-flex; flex-direction:column; gap:4px;
  border:1px solid #dfe9e3; border-radius:10px; padding:6px 10px; background:#fff; }
.wh-namebox .text-input { width:120px; height:28px; margin:0; padding:0 8px; font-size:13px;
  box-sizing:border-box; }
.wh-namebox .btn { width:120px; height:28px; min-width:0; padding:0 8px; font-size:13px; line-height:1;
  box-sizing:border-box; white-space:nowrap; }
.wh-namebox .btn:disabled { opacity:.5; cursor:not-allowed; }
/* V11.11（用户 2026-09-22）：「采购入库」发现没勾通知人员时，把这个框**高亮**一下（配合滚动）。
⚠️ 用 `box-shadow` 描边（不改尺寸、不挤布局）；⛔ 别写成 `border` —— 加边框会让这一行高度变 2px，
   连带把灯/圆点尺寸也带跑（几何判据对那一行很敏感）。 */
.wh-ops.flash { box-shadow:0 0 0 3px #f2b0b0; border-radius:10px; }
/* ------------------------------------------------------------------
   V11.15（用户 2026-09-22）：「识别单号」一行 —— 位置在「通知人员」那一行的**下面**。
   用户原话：「通知人员下面增加一个输入框，一个按钮：识别单号，两个控件填充一整行。」
   ⇒ 输入框 `flex:1 1 auto` 吃掉整行剩余宽度，按钮沿用工具栏那套规格
     （见上面 `.wh-tool .btn, .wh-locrow .btn` 那组选择器 —— 本行也**并进同一组**，⛔ 别在这里另写一份尺寸，
      两份迟早漂；「两个控件铺满一整行」就是靠「输入框吃满剩余 ＋ 按钮固定宽」实现的）。
   ⚠️ 高度/`margin` 与 `.wh-tool .text-input` **同一套写法**：`.text-input` 自带的 `margin-bottom:14px`
      会把输入框与按钮的中心线错开（这是「看着没对齐」的真凶，V10.0 踩过）。
   ⚠️ `min-width:160px` 与工具栏同口径：窗口很窄时输入框不再缩、行内换行，⛔ 别去掉（去掉会挤成一条线）。
   ⛔ 别给输入框写死宽度（换个窗口宽就露白或溢出）。
   ------------------------------------------------------------------ */
.wh-detect { gap:10px; margin-top:6px; }
.wh-detect .text-input { flex:1 1 auto; width:auto; min-width:160px; height:40px; margin:0;
  padding:0 12px; font-size:14px; box-sizing:border-box; }
/* 🔴 V11.28（用户 2026-09-22）：「识别单号对应的输入框，改为淡蓝色填充」。
   色值**沿用项目既有的淡蓝家族**（与权限组页那两个按钮 `.pg-only-btn`/`.pg-none-btn` 同一套：
   底 `#eef4ff` / 边 `#b6d4fe` / 字 `#1d4ed8`）—— ⛔ 别自己另发明一个蓝。
   ⚠️ 用 `#wh-detect-in`（ID 选择器）覆盖 `.wh-detect .text-input`（两个类）—— 特异性天然够，不用 !important。
   ⚠️ **只改「底 + 边框」**：输入框里是要**粘贴一坨文本**进去的，正文保持默认深色最易读
      （⛔ 别把 `color` 也设成 `#1d4ed8`，蓝字压蓝底会难认）。
   ⚠️ 高度/内边距/宽度一个字都没动（V11.15 定的「两个控件填充一整行」与 40px 规格保持不变）。 */
#wh-detect-in { background: #eef4ff; border-color: #b6d4fe; }
.wh-ops-foot { font-size:12.5px; color:#66766e; white-space:nowrap; }
.wh-ops-foot .wh-copy { margin-left:6px; }
/* ⛔ 勾选姓名「变蓝加粗」的着色规则**全站只有一条**（`.rk-ops-wrap` 那条），别在这里再写第二条 ——
   容器本身带 `rk-ops-wrap`，既有规则已经覆盖本页；再写一条就是**第二条**同名选择器，
   谁生效只看书写顺序与特异性（`make_rk_pending_probe.py` 有守卫盯着「只此一条」，多了生成器直接失败）。
   ⚠️ 连**注释里也别把那条选择器原样写出来**：守卫是拿整份 CSS 数出现次数的，注释照样会被数进去。 */
/* 🔴 数量比对指示灯：**竖排**（圆点在上、提示文字在**下方**）＋ 收窄，把宽度让给「通知人员」
   （用户 2026-09-21 晚：「指示灯区域太宽了，减少宽度，空间留给通知人员」＋「提示文字放在指示灯下方」）。
   ⛔ 别改回横排胶囊 —— 横排时文字把灯撑到 240px 上下，正是用户说的"太宽"。 */
.wh-lamp { display:inline-flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
  flex:0 0 auto; padding:6px 12px; border-radius:14px;
  border:1px solid #dfe9e3; background:#fff; color:#66766e;
  font-size:13px; font-weight:600; text-align:center; }
/* 🔴 圆点＝**自动填满灯的可用高度**（用户 2026-09-22 选「方案 A」：框高与「通知人员」等高，圆点依此缩小）：
   `aspect-ratio:1` 定形状、`flex:1 1 auto` 吃高度、宽度由比例跟着高度走
   ⇒ 每档视口都"刚好不撑高、且尽量大"（实测 1440 档 ≈38px、1100 档 ≈70px）。
   `min-*` 是下限（别让它退化成小圆点）、`max-width` 是上限（防止框很高时圆点把灯撑宽）。
   ⛔ 别再写回固定 `width/height`（那样又会出现"灯比框高、由灯决定行高"）。 */
.wh-lamp-dot { aspect-ratio:1; flex:1 1 auto; width:auto; height:auto;
  min-width:32px; min-height:32px; max-width:72px; max-height:72px;
  /* ⚠️ `max-height` 与 `max-width` **要成对写**：只写 max-width 时，这一行被别的东西撑高，
     圆点会"高度照长、宽度被卡" ⇒ **变成长方形**（V11.9 加人名方框时真踩到）。 */
  border-radius:50%; background:#c9d6cf;
  /* 🔴 V11.9（用户 2026-09-22）：「减小指示灯光晕的大小和扩散范围，确保绿色状态下光晕不超出指示灯边框范围」
     ⇒ 光晕半径必须 ≤ 灯的**内边距**（`.wh-lamp` 是 `padding:8px 12px`）⇒ 默认 3px、ok/bad 态 **6px**（原来 14px 会溢出）。
     ⛔ 别再加回去：判据在 `wcwh_layout_check.js`（量「圆点顶边 − 光晕半径 ≥ 灯边框外沿」）。 */
  box-shadow:0 0 0 3px rgba(0,0,0,.05); }
.wh-lamp.ok  { color:#137333; border-color:#9fd3ae; background:#eaf5ec; }
.wh-lamp.ok .wh-lamp-dot  { background:#1a9e4b; box-shadow:0 0 0 6px rgba(26,158,75,.22); }
.wh-lamp.bad { color:#a32d2d; border-color:#f2b0b0; background:#fdecec; }
.wh-lamp.bad .wh-lamp-dot { background:#d93025; box-shadow:0 0 0 6px rgba(217,48,37,.22); }
.wh-diff-btn { font-size:12px; padding:3px 10px; border:1px solid #f2b0b0; background:#fff;
  color:#a32d2d; border-radius:6px; cursor:pointer; }
/* 🔴 窄屏（实测 1100px）下明细表 min-content 比容器宽 ⇒ 会把整页撑出横向滚动条。
   定式（照「任务总览」页的 `.kd-tbl-wrap`）：**滚动容器只能是外面这层**，⛔ 别在 table 上写 overflow。 */
.wh-tbl-wrap { overflow-x:auto; }
/* 🔴🔴 表头**必须**把吸顶基准钉在「容器顶部」，否则会跑到第一行数据上面去（V10.5 修的真 bug，用户 2026-09-22 报
   「表头不是显示在最上方，显示在最下方」）：
   来由 —— 全站那条 V6.77 规则 `section.page table thead th { position:sticky; top:var(--tabs-h,54px) }`
   特异性是 (0,1,4)，**也命中这两张新表**；而本页表格在 `.wh-tbl-wrap`（自身滚动容器，
   `overflow-x:auto` 让 `overflow-y` 也计算成 auto）里，`top:54px` 在"容器比表格矮"时会把表头**往下推** ——
   实测表头行盒 285–312、它的 `<th>` 却画在 **318–345**（比自己的行低 33px，压在第一行数据上）。
   写法照「任务总览」页 `.kd-tbl-wrap` 的既有先例：**带 `section.page` 前缀提特异性** ＋ `top:0`。
   ⚠️ 顺带必须把 `background` 显式写回表头色：那条全局规则的 `background: var(--bg)` 同样靠特异性
      压掉了 `.wh-tbl th` 的 `#f4f9f6`（不写回去的话表头底色会是页面底色）。
   ⛔ 别只写 `.wh-tbl thead th { top:0 }` —— 特异性不够，覆盖不掉那条全局规则。 */
section.page .wh-tbl-wrap table thead th {
  position: sticky; top: 0; z-index: 8;
  background: #f4f9f6;
  box-shadow: inset 0 -1px 0 0 var(--border);
}
.wh-tbl { width:100%; border-collapse:collapse; margin-top:6px; font-size:13px; }
.wh-tbl th, .wh-tbl td { border:1px solid #e3ebe6; padding:7px 9px; text-align:left; vertical-align:middle; }
.wh-tbl th { background:#f4f9f6; color:#20302a; font-weight:600; white-space:nowrap; }
/* 🔴 表头行降高（用户 2026-09-21 晚第 4/7 条）：padding 7 → **5px**、行高压到 1.25、字号 12.5px。
   来由：以前「复制采购数量」按钮长在「入库数量」表头里、「审核后自动取回」是**独立一行**小字，
   两者都把表头撑高；现在前者挪进工具栏、后者改成同行小字 ⇒ 表头自然回落（几何实测 < 34px）。 */
.wh-tbl thead th { padding:5px 9px; font-size:12.5px; line-height:1.25; }
.wh-tbl thead th small, .wh-tbl thead th .wh-th-hint { font-size:11px; font-weight:400; color:#66766e; }
/* ============ 列宽口径（用户 2026-09-22 拍板）============
   ① 表格**撑满**容器（`.wh-tbl { width:100% }`，别改成 auto）；
   ② **除「货品名称」外一律按内容自适应** —— 靠单元格 `white-space:nowrap` 让每列的"内容宽"就是它的一行宽度，
      ⛔ 别再往 `<th>` 上写 `style="width:...px"`（那就是被用户否掉的"写死像素"）；
   ③ 「货品名称」固定 **22.5 个汉字宽**（`.wh-c-name`：`width:22.5em`，13px 字号 ⇒ 292.5px 内容宽；
      V11.9 用户要求「加宽到原来的约 1.5 倍」＝ 15em × 1.5）；
   ④ 「审核结果 / 称重出库结果」**例外**：上限 220px ＋ **允许换行**（那两列会出现旺店通返回的长中文）。 */
.wh-tbl th, .wh-tbl td { white-space:nowrap; }
/* 货品名称：固定 15 汉字宽 ＋ 省略号截断。
   ⚠️ 截断/限宽**必须作用在内层块元素上**，不能只写在 `<td>` 上 —— 表格自动布局里
   `td` 的 `max-width` 基本不被采纳（列宽由 min/max-content 决定），写 `td { overflow:hidden }` 也不会出现省略号。
   ⇒ 内层 `.wh-name-in` 用 `display:block; width:22.5em` 把该列的 max-content 钉死，再对**内层**做 overflow 截断。
      ⚠️ 改了外层 `width` 就必须同步改内层这一处，否则内层还是老宽（列宽由内层决定）。
   全名由 `title` 属性给出（用户第 3 点：「鼠标悬停看全名」）。 */
.wh-tbl .wh-c-name { width:22.5em; }
.wh-name-in { display:block; width:22.5em; max-width:100%; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
/* ============ V11.9（用户 2026-09-22）：「货品名称」列**加宽到原来的 1.5 倍** ============
   用户原话：「将『货品名称』列宽度增加约半列宽，即加宽至原来的约 1.5 倍。」
   ⇒ 15em → **22.5em**（13px 字号 ⇒ 内容宽 195 → **292.5px**）。
   ⚠️ **两处都要改**（`.wh-c-name` 管列、`.wh-name-in` 管内层块把该列的 max-content 钉死），只改一处＝列宽不变。
   ⚠️ 并排的「采购数量 / 入库数量」是定宽（`.wh-c-num`）⇒ 这次加宽吃掉的是**自适应那些列**的余量，不会把它们压扁。 */
/* 🔴 V11.9：「入库数量」的**数字调节箭头去掉**（用户 2026-09-22：「移除入库数量输入框的加减调节按钮」）。
   定式＝`::-webkit-*spin-button` 关掉 WebKit/Blink 的箭头 ＋ `appearance:textfield` 关掉 Firefox 的。
   ⚠️ 只针对**表格里的数字输入框**（`.wh-tbl`），⛔ 别全局干掉（别的页可能就想要箭头）；
   ⚠️ 输入框仍是 `type=number`（语义/校验不变，键盘上下键仍可用，只是不画箭头）。 */
.wh-tbl input[type=number]::-webkit-outer-spin-button,
.wh-tbl input[type=number]::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
.wh-tbl input[type=number] { -moz-appearance:textfield; appearance:textfield; }
/* ============ V11.22（用户 2026-09-22）：**只把订单表**的「货品名称」缩短 1/3 ＋ 改成自动换行 ============
   用户原话：「订单明细的货品名称列缩短约三分之一，超出单行时自动换行」（**只改订单明细**，采购表保持原样）。
   ⇒ 22.5em → **15em**（13px 字号 ⇒ 内容宽 292.5 → **195px**）；由「单行＋省略号」改成 **自动换行**。
   ⚠️ 与 V11.9 同一条老坑：**两处都要改** —— 外层 `.wh-c-name`（管列）＋ 内层 `.wh-name-in`
      （管把该列的 max-content 钉死），只改一处＝列宽不变。
   ⚠️ 用 `#wh-ttbl` 前缀写覆盖（ID 特异性足够），⛔ 别动上面那两条**全局**规则（采购表还要 22.5em ＋ 截断）。
   ⚠️ 换行兜底用 `overflow-wrap:anywhere`（照相邻的 `.wh-res-in` 同口径）：长英文 / 货品编码那种
      **没有断点**的串也能断开，不会把列撑破。 */
#wh-ttbl .wh-c-name { width:15em; }
#wh-ttbl .wh-name-in { width:15em; max-width:100%; overflow:visible; text-overflow:clip;
  white-space:normal; overflow-wrap:anywhere; }
/* ============ V11.22（用户 2026-09-22）：订单表新增「订单状态」列（`.wh-c-st`）============
   口径**照相邻的 `.wh-c-res`**（它会放「已审核（待补货）」这类中文，出库单状态还可能是
   「待补货，已完成」这种多值串）。V11.23 起本栏**改为自适应**（订单表整体改成 fixed 布局，
   不再靠 220px 防溢出）—— 见上方 V11.23 那一段。
   ⚠️ 与 `.wh-c-res` 同一条坑：内边距要**挪到内层块**上（本项目全局 `box-sizing:border-box`，
      td 220 ＋ 内层 220 各算各的会溢出到 padding 里）。 */
/* ============ V11.23（用户 2026-09-22）：订单表**4 栏定宽 ＋ 3 栏自适应 ＋ 整表不横向滚动** ============
   用户原话：「订单号、仓库、货品编码、物流单号，这几个字段全部改为固定宽度，4 个汉字的宽度，
   订单状态、审核结果、称重出库结果，这 3 个字段全部自适应，但不能超出界面的表格范围。」
   ⚠️ 他另一条已确认的取舍：内容装不下就**换行**（我把数字摆给他看过 —— 订单号实测需要 125px、
      4 个汉字只有 52px，他仍选「真按 4 汉字 ＋ 换行」）⇒ 订单号/货品编码/物流单号在这些栏里
      **会折成多行**，这是**预期结果**，⛔ 别擅自放宽或改成省略号。

   🔴 为什么必须给订单表上 `table-layout: fixed`（否则「4 汉字」根本落不了地）：
      本项目的既有教训是「`td` 上的 `width`/`max-width` 在**自动布局**里基本不被采纳，列宽由 min/max-content 决定」。
      「订单号」内容是 14 个字符（≈125px）⇒ 自动布局会**把列撑到 125px**，写 `width:4em` 也没用。
      `fixed` 布局才会**照第一行（表头）声明的宽度定列宽**，且**剩余宽度由未声明宽度的列平分**
      ⇒ 这正好就是用户要的「4 栏固定 ＋ 3 栏自适应」。另一层好处：`fixed` ＋ `width:100%` 的表
      **永远不会横向溢出**（内容超了在格子里换行，不会把表顶宽）⇒ 同时满足「不能超出界面的表格范围」。
   🔴 所以「允许换行」在 fixed 布局下是**必需项**（不是可选美化）：长串（JY…／SF…／CP… 都没有断点）
      不写 `overflow-wrap` 就会**溢出到隔壁格**。
   ⚠️ 全部只作用于 `#wh-ttbl`；⛔ 采购表（`#wh-ptbl`）的列宽口径一个字都别动。 */
#wh-ttbl { table-layout: fixed; }
#wh-ttbl th, #wh-ttbl td { white-space: normal; overflow-wrap: anywhere; }
/* 4 个固定列（1 订单号 / 2 仓库 / 3 货品编码 / 6 物流单号）＋ 5 订单数量：
   **内容宽 = 4em = 52px**。⚠️ 必须 `box-sizing:content-box` —— 本文件全局是 `border-box`，
   不改成 content-box 的话 52px 里还要扣掉 18px 内边距 ⇒ 实际只剩 2.6 个汉字，对不上用户要的「4 个汉字」。
   （「订单数量」用户没点名，但它若不给宽度，在 fixed 布局里会被动跟着自适应列变宽 ⇒
     按采购表「采购数量」的同一套口径给它 4em，保持窄列观感。） */
/* 🔴 声明宽度取 `calc(4em + 6px)` 而不是裸 `4em` —— 实测踩过：`table-layout:fixed ＋ width:100%`
   的表，当「声明的总宽」略超容器时，浏览器会**按比例微缩**每一列（实测 52px 被缩到 **50px**），
   于是「文字区」装不下 4 个汉字（52px）⇒ 「上海总仓」被折成「上海／总仓」两行 ——
   那就不叫「4 个汉字的宽度」了。补 6px 冲掉这约 4% 的微缩，让**文字区稳稳 ≥52px**。
   ⚠️ 判据（`wcwh_layout_check.js` ㉕b）判的是「文字区 ≥4 汉字（52px）且 ≤60px」＋
      「『仓库』那格必须是 1 行」(㉕c)，⛔ 别只判某个写死的像素值。 */
#wh-ttbl th.wh-c-fix, #wh-ttbl th:nth-child(5) { width: calc(4em + 6px); box-sizing: content-box; }
/* 🔴 V11.24（用户 2026-09-22）：「订单号和物流单号，改为 5 个汉字的栏宽」⇒
   这两栏单独放宽到 **5 个汉字**（13px ⇒ 5em=65px），仓库/货品编码仍是 4 个汉字。
   ⚠️ 用 `nth-child` 而不是新加类：这两栏本来就带 `wh-c-fix`，加类会让「wh-c-fix 恰好 4 个」
      那条生成期断言失效；而且这文件里「订单数量」本来就是用 `nth-child(5)` 指位的，口径一致。
   🔴 **必须写在上一条之后**：两条同为 `th:nth-child(...)`（特异性相同）⇒ 后写的赢；
      ⛔ 挪到前面就失效（1/6 栏会退回 4 个汉字）。
   ⚠️ 同样补 6px 冲掉约 4% 的按比例微缩（裸 `5em` 会被缩到 ~63px、装不下 5 个汉字 65px）。 */
#wh-ttbl th:nth-child(1), #wh-ttbl th:nth-child(6) { width: calc(5em + 6px); box-sizing: content-box; }
/* 4 货品名称：仍是 15em，但也要 content-box —— 否则 fixed 布局按 border-box 算 ⇒ 内容宽从 195 掉到 176
   （会打坏 V11.22 那条「订单表名称列 ≈195px」的判据）。 */
#wh-ttbl th.wh-c-name { width: 15em; box-sizing: content-box; }
/* 3 个自适应列（7 订单状态 / 8 审核结果 / 9 称重出库结果）：**不给宽度** ⇒ 平分剩余宽度。
   ⚠️ 原来那几条 `width:220px` 是「自动布局时代」用来防长中文把表撑破的（见下面 V11.22 那段注释）；
      现在表是 `fixed ＋ width:100%`，本身就不会溢出 ⇒ 把 220px 撤掉才谈得上「自适应」。
      ⛔ 别把 220px 加回来（加了就不再自适应，且会把表顶出容器）。 */
#wh-ttbl th.wh-c-st, #wh-ttbl th.wh-c-res,
#wh-ttbl td.wh-c-st, #wh-ttbl td.wh-c-res { width: auto; max-width: none; }
#wh-ttbl .wh-st-in, #wh-ttbl .wh-res-in { width: auto; max-width: none; }
/* 🔴 V11.23 更正：**这两条 220px 是「自动布局时代」防长中文撑破表用的**，现在订单表已改成
   `table-layout:fixed ＋ width:100%`（本身就不会溢出）⇒ **改成自适应**（不给宽度，与另外两栏
   一起平分剩余宽度）。⛔ 别把 220px 加回来：加了就不再自适应，还会把表顶出容器。 */
#wh-ttbl .wh-c-st { width:auto; max-width: none; }
#wh-ttbl td.wh-c-st { padding:0; }
#wh-ttbl .wh-st-in { display:block; width:auto; max-width: none; padding:7px 9px; box-sizing:border-box;
  white-space:normal; overflow-wrap:anywhere; }
/* ============ V11.6（用户 2026-09-22）：「采购数量 / 入库数量」两栏**固定 4 个汉字宽** ============
   用户原话：「原本是货品名称栏宽固定，其他栏宽自适应，现在将采购数量、入库数量栏宽也固定，
   显示 4 个汉字的宽度即可。」⇒ 与「货品名称」同一套口径：**内容宽 = 4em = 52px**（13px 字号），
   加上单元格内边距后列宽约 70px。
   🔴 **为什么必须给该列的 input 也写定宽**：「入库数量」里是 `<input type="number">`，它的
      **固有宽度约 191px**（浏览器按 size 默认值算）—— 自动布局会照它把整列撑到 **~209px**（实测），
      这正是"这两栏太宽"的根因；而 `width:100%` 在自动布局里**压不住**这个固有宽度（百分比不参与列宽计算）。
   ⚠️ 该列 td 的内边距也瘦一档（9px → 6px）：52px 的内容宽里，光 td 内边距就占 18px。 */
.wh-tbl .wh-c-num { width:4em; }
/* 🔴 V11.21（用户 2026-09-22）：「入库数量字段中显示的数字，靠右显示」（追问后定为**两列都靠右**）。
   三处一起才完整：① `td` 的文本（＝「采购数量」那一格）② `th` 表头（两列方向要一致 —— `th` 里
   4 个汉字恰好占满 4em 列宽，观感差别很小，但不改会与数据方向相反）③ **输入框内部的数字**
   （`input` 上的 `text-align`，⚠️ 只写 `td` 是**管不到输入框里的文字**的）。 */
.wh-tbl th.wh-c-num, .wh-tbl td.wh-c-num { text-align: right; }
.wh-tbl td.wh-c-num { padding:7px 6px; }
.wh-tbl td.wh-c-num input[type=number] { width:4em; max-width:100%; padding:5px 5px; text-align: right; }
/* 审核结果 / 称重出库结果：**定宽 220px ＋ 允许换行**（用户 2026-09-22：「保留上限宽（约 220px）＋ 允许换行」）。
   ⚠️ 必须写**定宽** `width:220px`，只写 `max-width` 会塌列 —— 中文可在任意字符断行 ⇒ 该列的 min-content
      只有 1 个字宽，实测被压成 **69px 宽、270px 高**（一行字竖排 10 行）。定宽后该列的 min-content＝220，
      既不会被压塌、也不会超过上限。`min-width` 在这种写法下是冗余的（已去掉，别加回来当"保险"）。
   ⚠️ 限宽必须落在**内层块**上（`td` 上的 max-width 在表格自动布局里基本不生效）。 */
/* ⚠️ V11.23：下面这两条（`.wh-c-res` / `.wh-res-in` 的 220px）**只对采购表之外的用法无效** ——
   订单表那两栏（审核结果 / 称重出库结果）已改成**自适应**（由 `#wh-ttbl ... { width:auto }` 覆盖，
   ID 前缀特异性更高）。保留这两条是为了兼容表内其它引用处，⛔ 别顺手删。 */
.wh-tbl .wh-c-res { width:220px; max-width:220px; }
/* ⚠️ 本项目全局 `box-sizing:border-box` ⇒ `td` 的 width 算的是**边框盒**。若 td 仍留 7/9 的内边距，
   内层 220px 的块就会比内容盒宽 18px、溢出到 padding 里（实测 td 220 / 内层 220 各算各的）⇒
   把**内边距挪到内层块上**，让 td 与内层严格都是 220（文字区 202）。 */
.wh-tbl td.wh-c-res { padding:0; }
.wh-res-in { display:block; width:220px; max-width:220px; padding:7px 9px; box-sizing:border-box;
  white-space:normal; overflow-wrap:anywhere; }
.wh-tbl td.num { text-align:right; }
.wh-tbl input[type=text], .wh-tbl input[type=number], .wh-tbl input[type=date] {
  width:100%; box-sizing:border-box; padding:5px 8px; font-size:13px;
  border:1px solid #cfdcd4; border-radius:6px; }
.wh-tbl select { padding:5px 8px; font-size:13px; border:1px solid #cfdcd4; border-radius:6px; }
.wh-loc { display:flex; gap:6px; align-items:center; }
.wh-loc select { flex:0 0 96px; } .wh-loc input { flex:1 1 auto; }
.wh-copy { font-size:12px; padding:3px 9px; border:1px solid #b9d0f5; background:#eef4ff;
  color:#0b57d0; border-radius:6px; cursor:pointer; white-space:nowrap; }
/* V10.7（用户 2026-09-22）：「选择货位」一行 —— 3 个单选按钮 ＋ 1 个输入框（整批一个货位）。
   与工具栏同高（40px），单选标签横向排、不换行。 */
.wh-locrow { gap:10px; margin-top:6px; }
.wh-locrow .lab { min-width:auto; white-space:nowrap; }
.wh-locs { display:flex; align-items:center; gap:14px; flex:0 0 auto; }
.wh-radio { display:inline-flex; align-items:center; gap:6px; font-size:13px; cursor:pointer; white-space:nowrap; }
.wh-radio input[type="radio"] { width:16px; height:16px; margin:0; cursor:pointer; }
.wh-locrow .text-input { flex:1 1 200px; width:auto; min-width:160px; height:40px; margin:0;
  padding:0 12px; font-size:14px; box-sizing:border-box; }
/* ============ V10.6：相关图片截图（拖拽 / 粘贴 / 选文件 ⇒ 缩略图）============
   用户 2026-09-22：「界面最下方，增加一个功能，可以让我拖拽或粘贴图片进来，并且是可以多个」。
   ⚠️ 手机端没有拖拽与粘贴 ⇒ 文案与入口要能降级（「选择图片」按钮在两端都可用）。 */
.wh-drop { border:2px dashed #c9dcd2; border-radius:12px; background:#fbfdfc;
  padding:10px 16px; transition:background .15s, border-color .15s; }
.wh-drop.over { border-color:var(--primary,#4fae87); background:#f1f9f4; }   /* 拖到上面时高亮 */
.wh-drop-in { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.wh-drop-t { font-size:13px; font-weight:600; color:#20302a; }
.wh-drop-h { font-size:12.5px; color:#66766e; }
.wh-imgs { display:flex; flex-wrap:wrap; gap:10px; margin-top:6px; }
.wh-thumb { position:relative; width:104px; height:104px; border:1px solid #dfe9e3;
  border-radius:10px; overflow:hidden; background:#fff; }
.wh-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.wh-thumb-x { position:absolute; top:2px; right:2px; width:22px; height:22px; padding:0;
  border:none; border-radius:50%; background:rgba(0,0,0,.55); color:#fff; font-size:14px;
  line-height:22px; text-align:center; cursor:pointer; }
.wh-thumb-x:hover { background:rgba(0,0,0,.75); }
/* 已上传成功的缩略图给个淡绿描边（用户能分清"哪些已经进伙伴云了"） */
.wh-thumb.done { border-color:#9fd3ae; box-shadow:0 0 0 2px rgba(26,158,75,.15); }
.wh-msg { margin-top:6px; font-size:13px; line-height:1.7; }
.wh-msg .ok { color:#137333; } .wh-msg .bad { color:#b3261e; } .wh-msg .warn { color:#a15c00; }
.wh-pre { background:#f8fafc; border:1px solid #e3ebe6; border-radius:8px; padding:9px 12px;
  font-family: ui-monospace, Menlo, monospace; font-size:12px; white-space:pre-wrap; margin-top:8px; }
.wh-skip { font-size:12px; color:#a15c00; }
/* 单号分组的换单分隔线 —— 照「付款申请」页范式：**inset box-shadow**，⛔ 不用 border-top */
.wh-tbl tr.wh-grp { box-shadow: inset 0 3px 0 0 var(--primary, #4fae87); }
