/* ============================================================
   供销链管理系统 · 设计规范
   —— 复刻云擎系统设计令牌（YunQing Design Tokens）
   —— 字体：微软雅黑 light/regular/bold + Arial
   —— 主色：4F46E5 / 0F172A（品牌靛蓝 · 导航 · 强文字）
   —— 语义色：8B5CF6 / 10B981 / 0EA5E9 / EF4444 / F59E0B
   ============================================================ */

:root {
  --font-base: "Microsoft YaHei", "微软雅黑", "HarmonyOS Sans SC", "PingFang SC", Arial, sans-serif;
  --fw-light: 300;
  --fw-regular: 400;
  --fw-medium: 600;
  --fw-bold: 700;

  --fs-24: 24px;
  --fs-18: 18px;
  --fs-16: 16px;
  --fs-14: 14px;
  --fs-13: 13px;
  --fs-12: 12px;

  --primary: #4F46E5;
  /* 「深底上的文字/链接色」：浅色主题下 = 主色；暗色主题下要换成浅靛蓝，否则 #4F46E5 在深底上几乎看不清 */
  --link: #4F46E5;
  --primary-dark: #0F172A;
  --primary-soft: rgba(79,70,229,.10);
  --primary-soft-2: rgba(79,70,229,.16);
  --blue: #4F46E5;

  --c-accent: #8B5CF6;
  --c-success: #10B981;
  /* **按钮绿**（用户 2026-09-19 定 #13ce66；2026-09-22 收成一个令牌）——
     新增 / 转入销售订单 / 创建 / 添加明细行 全部走它 ⇒ 不会再出现"两个不同的绿" */
  --c-green: #13ce66;
  --c-info: #0EA5E9;
  --c-gray: #94A3B8;
  --c-danger: #EF4444;
  --c-warning: #F59E0B;
  --danger: #EF4444;
  --danger-soft: rgba(239,68,68,.10);

  --bg: #F8FAFC;
  --bg-list: #F1F5F9;
  --card: #FFFFFF;
  --card-2: #F1F5F9;
  --text: #1E293B;
  /* 列表正文文字色（用户 2026-09-21）：
     · 亮色主题 = **纯黑 #000000**（用户 2026-09-21 上午定的列表规范）
     · 暗色主题 = 主题浅字（见 [data-theme="dark"]）——
       用户 2026-09-21 12:2x：「暗色模式下，**不允许出现黑色字或者黑色无背景的字**」⇒
       列表里的黑字必须走这个变量，**不许再硬编码 #000000** */
  --list-text: #000000;
  /* 「次要深灰」文字（明细表头 / 单据动态标签 / 灰胶囊 …）——
     亮色 = #494949；**暗色 = --muted 浅灰**（用户 2026-09-21：「暗色模式下不允许出现黑色字」）；
     原来这些地方**硬编码 #494949** ⇒ 暗色底上几乎看不见 ⇒ 一律改走本变量 */
  --ink-2: #494949;
  --muted: #64748B;
  --line: #E2E8F0;
  --line-strong: #CBD5E1;
  --field-bg: #F8FAFC;
  --field-border: #CBD5E1;
  --chip: #F1F5F9;
  /* 主色的**不透明**浅色版（= rgba(79,70,229,.1) 铺在白底上的实际颜色）——
     给吸顶表头这类**会盖住滚动内容**的元素用：用半透明会被下面的行透出来 （用户 2026-09-17 晚报的） */
  --tint-primary: #EDEDFC;

  /* 圆角策略（用户 2026-09-19 更正：「取消全局直角，除了 table 和卡片必须直角，
       其他的按钮什么的你可以按照规范来设计」）→ 令牌恢复**设计规范圆角**；
       「卡片 + 表格（含列表 / 明细单）」由**文件末尾的直角白名单**单独压回 0
       （旧口径 2026-09-16 是全局归零，已作废） */
  --radius: 6px;
  --radius-sm: 4px;
  --radius-lg: 8px;
  --shadow: 0 1px 2px rgba(15,23,42,.04), 0 4px 16px rgba(15,23,42,.05);
  --shadow-lg: 0 2px 4px rgba(15,23,42,.08), 0 8px 32px rgba(15,23,42,.10);
  --shadow-hover: 0 8px 28px rgba(15,23,42,.10);
  --ring: 0 0 0 3px rgba(79,70,229,.18);

  /* 导航配色（C · 品牌靛蓝）：侧栏明暗两主题都是深色，仅底色/默认文字随主题微调 */
  --side-bg: #1E1B4B;
  --nav-text: #9DA6D8;
  --nav-hover-bg: rgba(255,255,255,.07);
  --nav-hover-text: #E0E7FF;
  --nav-active-bg: rgba(129,140,248,.24);
  --nav-active-text: #E0E7FF;
  --nav-active-bar: #818CF8;
  --nav-line: rgba(255,255,255,.10);
  --header-bg: rgba(255,255,255,.72);
  --header-border: rgba(255,255,255,.55);
}

[data-theme="dark"] {
  --bg: #0B1430;
  --bg-list: #101B3D;
  --card: #12204A;
  --card-2: #0F1A3D;
  --text: #E8ECFF;
  /* 列表正文色：暗色下**必须是浅字**（用户 2026-09-21：暗色模式不许有黑字）*/
  --list-text: #E8ECFF;
  --ink-2: var(--muted);       /* 次要深灰 → 暗色下转浅灰（否则 #494949 在深底上看不见）*/
  --muted: #8893B8;
  --line: #233160;
  --line-strong: #2C3C70;
  --field-bg: #0C173A;
  --field-border: #2C3E6E;
  --chip: #1A2A57;
  --primary-soft: rgba(79,70,229,.20);
  --primary-soft-2: rgba(79,70,229,.28);
  --tint-primary: #1E2869;      /* = 上面的 primary-soft 铺在暗色卡片 #12204A 上的实际颜色（不透明） */
  --danger-soft: rgba(255,0,0,.16);
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);
  /* 暗色下补 `--shadow-lg`（用户 2026-09-19 22:0x）—— 原来**只有亮色定义** →
     暗色主题里会继续用「深色投影」，铺在深色卡片上**几乎看不见** */
  --shadow-lg: 0 2px 6px rgba(0,0,0,.45), 0 10px 34px rgba(0,0,0,.5);
  --shadow-hover: 0 12px 34px rgba(0,0,0,.55);
  --ring: 0 0 0 3px rgba(79,70,229,.30);
  --side-bg: #151938;
  --nav-text: #94A3B8;
  --header-bg: rgba(34,49,95,.66);
  --header-border: rgba(255,255,255,.08);
  --link: #A5B4FC;            /* 深底上的文字/链接色（浅靛蓝，对比度足够） */
}

/* ============================================================
   暗色模式 · 对比度修正
   主色 #4F46E5 在深底上当**文字色**用几乎看不清（编号链接 / 人员标签 /
   权限页的选中项与操作按钮…）→ 统一换成 --link（浅靛蓝）；红色操作也提亮。
   ============================================================ */
[data-theme="dark"] .entity-name-link,
[data-theme="dark"] .cell-link,
[data-theme="dark"] .link-btn,
[data-theme="dark"] .tag-chip,
[data-theme="dark"] .tnode.on,
[data-theme="dark"] .tnode .dots:hover,
[data-theme="dark"] .perm-seg a:hover,
[data-theme="dark"] .pm-sub-tabs a.on,
[data-theme="dark"] .tb-role,
[data-theme="dark"] .ph-crumb,
[data-theme="dark"] .auto-preview,
[data-theme="dark"] .fd-chip.sys,
[data-theme="dark"] .tag-check.is-on,
[data-theme="dark"] .cat-lv.lv1,
[data-theme="dark"] .dp-cell.today,
[data-theme="dark"] .dp-trigger:hover,
[data-theme="dark"] .dp-title:hover,
[data-theme="dark"] .dp-nav button:hover,
[data-theme="dark"] .dp-footer button,
[data-theme="dark"] .fd-move:hover,
[data-theme="dark"] .tb-menu-item:hover,
[data-theme="dark"] .tb-theme:hover,
[data-theme="dark"] .btn-ghost:hover,
[data-theme="dark"] .pg-num:hover,
[data-theme="dark"] .pg-btn:hover,
[data-theme="dark"] .cat-caret:hover,
[data-theme="dark"] .cat-ico-btn:hover,
[data-theme="dark"] .cat-row .cat-label:hover { color: var(--link); }
[data-theme="dark"] .tnode.on::before { background: var(--link); }
[data-theme="dark"] .tag-chip,
[data-theme="dark"] .tnode.on,
[data-theme="dark"] .tag-check.is-on,
[data-theme="dark"] .auto-preview { background: rgba(129,140,248,.18); }
/* ---------- 暗色下的「表单 / 就地编辑」：彩色选项**恢复为不带背景**（用户 2026-09-15：「要不看不清楚」）----------
   · 下拉选项只写了**内联 `background`**（浅色底）没写 color → 暗色里文字是浅色 → **浅底 + 浅字**看不清
   · 多选 / 标签的 chip 是内联 `background+color` 的浅色药丸 → 在深色面板上也刺眼
   处理：只在这两个上下文里把内联底色/字色**压掉**（`!important` 是因为要盖内联样式），
   列表/详情页的彩色标签**保持不变**（那边浅底深字本来就清楚，用户要的就是那边有颜色）。 */
[data-theme="dark"] .embed-body select option,
[data-theme="dark"] .inline-edit-grid select option,
[data-theme="dark"] .embed-body .opt-chip,
[data-theme="dark"] .inline-edit-grid .opt-chip,
[data-theme="dark"] .embed-body .tag-check .tag-chip,
[data-theme="dark"] .inline-edit-grid .tag-check .tag-chip {
  background: transparent !important;
  color: var(--text) !important;
}
/* 红色操作（删除 / 移除…）在深底上提亮 */
[data-theme="dark"] .link-btn.danger,
[data-theme="dark"] .link-btn.danger:hover { color: #FCA5A5; }
/* 禁用态：统一压低透明度，避免「看起来还能点」 */
[data-theme="dark"] .btn:disabled,
[data-theme="dark"] .link-btn:disabled { opacity: .42; cursor: not-allowed; }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; }

body {
  font-family: var(--font-base);
  font-weight: var(--fw-regular);
  background-color: var(--bg);
  color: var(--text);
  font-size: var(--fs-14);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  transition: background-color .35s ease, color .35s ease;
}
a { color: inherit; text-decoration: none; }

.app-sidebar, .card, .filter-bar, .stat, input, select, textarea, .btn, .nav-item, .badge {
  transition: background-color .35s ease, border-color .35s ease, color .35s ease, box-shadow .25s ease;
}

/* ============================================================
   布局：左侧固定导航（深色）+ 右侧内容
   ============================================================ */
.app-shell { min-height: 100vh; }

.app-sidebar {
  position: fixed; top: 60px; bottom: 0; left: 0; width: 176px;
  background: var(--side-bg); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; padding: 20px 14px 14px; z-index: 30;
}
.side-nav { display: flex; flex-direction: column; gap: 8px; flex: 1; overflow-y: auto; }
.nav-item {
  display: flex; align-items: center; gap: 10px;
  min-height: 36px; padding: 0 14px; line-height: 1.2;
  border-radius: var(--radius-sm); color: var(--nav-text);
  font-size: var(--fs-14); white-space: nowrap; position: relative;
}
.nav-item svg { flex-shrink: 0; }
.nav-item:hover { background: var(--nav-hover-bg); color: var(--nav-hover-text); }
.nav-item.active { background: var(--nav-active-bg); color: var(--nav-active-text); font-weight: var(--fw-bold); }
.nav-item.active::before {
  content: ''; position: absolute; left: 0; top: 9px; bottom: 9px;
  width: 3px; border-radius: 0; background: var(--nav-active-bar);
}
.side-bottom { padding-top: 12px; border-top: 1px solid var(--nav-line); }
.side-collapse {
  display: flex; align-items: center; justify-content: center; width: 100%;
  background: none; border: none; color: var(--nav-text); cursor: pointer; padding: 8px 0;
}
.side-collapse:hover { color: var(--nav-hover-text); }

.app-scrim { display: none; }

.app-content { margin-left: 176px; padding-top: 60px; min-height: 100vh; display: flex; flex-direction: column; min-width: 0; }

/* ---------- 顶栏（全宽固定，对齐参考系统：logo 贴窗口左缘，分隔线与侧栏右缘对齐） ---------- */
.app-topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  height: 60px; display: flex; align-items: center; gap: 0; padding: 0 22px;
  background: var(--header-bg); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
/* 品牌区：宽度**固定** 178px（= 侧栏宽 176 − 左内边距 22），确保右侧「页面标题 / 角色」起点恒定，
   不随 Logo 缩放或名称长短而左右漂移；内容超出时由 Logo 收缩、名称省略号消化。 */
.tb-left { display: flex; align-items: center; gap: 8px; width: 154px; flex-shrink: 0; min-width: 0; }
/* 未填系统名称：Logo 独占品牌区并水平居中（放大显示） */
.tb-left.no-name { justify-content: center; }
/* Logo 画布比例固定 702:180；不写名称时放大，写了名称时缩放与文字并排。
   Logo 不参与收缩（flex-shrink:0），名称超出时由自身省略号消化，保证 Logo 尺寸稳定 */
.tb-logo { display: block; height: 24px; width: auto; max-width: 100%; min-width: 0; aspect-ratio: 702 / 180; object-fit: contain; flex-shrink: 0; }
.tb-left.no-name .tb-logo { height: 40px; }
.tb-brand {
  font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--primary-dark);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0;
}
[data-theme="dark"] .tb-brand { color: var(--text); }
.tb-divider { position: absolute; left: 176px; top: 18px; width: 1px; height: 24px; background: var(--line); flex-shrink: 0; }
.tb-head { display: flex; align-items: baseline; gap: 8px; margin-left: 16px; flex: 1; min-width: 0; }
.tb-title { font-size: var(--fs-14); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-right { display: flex; align-items: center; gap: 10px; margin-left: auto; }

.tb-theme {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--card); color: var(--muted); cursor: pointer;
}
.tb-theme:hover { color: var(--primary); border-color: var(--primary); }
[data-theme="light"] .ic-moon { display: none; }
[data-theme="dark"] .ic-sun { display: none; }

.tb-menu-btn {
  display: none; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin-right: 10px; flex-shrink: 0;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--card); color: var(--text); cursor: pointer;
}

.tb-me-wrap { position: relative; }
.tb-me {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  background: none; border: none; font-family: inherit; color: var(--text); font-size: var(--fs-14); padding: 4px 6px;
}
.tb-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 0;
  background: var(--primary); color: #fff; font-size: var(--fs-13); font-weight: var(--fw-bold);
}
.tb-me-caret { color: var(--muted); }
.tb-menu {
  display: none; position: absolute; right: 0; top: calc(100% + 6px); min-width: 200px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-hover); padding: 6px; z-index: 50;
}
.tb-menu.open { display: block; }
.tb-menu-head { padding: 8px 10px; border-bottom: 1px solid var(--line); margin-bottom: 6px; }
.tb-menu-name { font-weight: var(--fw-bold); font-size: var(--fs-14); }
.tb-menu-acc { font-size: var(--fs-12); color: var(--muted); }
.tb-menu-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border-radius: var(--radius-sm); border: none; background: none;
  color: var(--text); font-family: inherit; font-size: var(--fs-14); cursor: pointer; text-align: left;
}
.tb-menu-item:hover { background: var(--primary-soft); color: var(--primary); }

/* ---------- 主区 ---------- */
/* 内容区跟随窗口铺满：**不设最大宽度**（表格类业务希望超宽屏尽量多显示列）；
   左右固定 16px 内边距 → 与导航、与右侧窗口边缘各留 16px。 */
.app-main { flex: 1; padding: 16px; width: 100%; }

.page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.page-head h1 { font-size: var(--fs-24); font-weight: var(--fw-bold); color: var(--primary-dark); margin: 0; }
[data-theme="dark"] .page-head h1 { color: var(--text); }
.page-head .count { font-size: var(--fs-16); color: var(--muted); font-weight: var(--fw-regular); }
.ph-ops { display: flex; gap: 8px; margin-left: auto; }   /* 页面标题已去掉，按钮靠右对齐 */

/* ---------- 控件标准 · 按钮（用户 2026-09-17 给定，全站以此为准）----------
   档位      左右内距  高度   字号   圆角
   Small     8px      24px  14px  2px   → .btn-sm（.btn-xs 并入同档：49 处历史类名保留）
   Medium   16px      32px  14px  2px   → .btn（默认）
   Large    16px      40px  16px  2px   → .btn-lg
   一律用固定 height（不再 min-height）：min-height 会被行高/内边距撑出不同高度，
      统一标准的意义就是「同档按钮在任何页面都一模一样」。 */
:root {
  --btn-h-sm: 24px; --btn-px-sm: 8px;  --btn-fs-sm: 14px;
  --btn-h-md: 32px; --btn-px-md: 16px; --btn-fs-md: 14px;
  --btn-h-lg: 40px; --btn-px-lg: 16px; --btn-fs-lg: 16px;
  --btn-radius: 2px;
}
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  height: var(--btn-h-md); padding: 0 var(--btn-px-md); border-radius: var(--btn-radius);
  font-family: inherit; font-size: var(--btn-fs-md); line-height: 1; cursor: pointer;
  border: 1px solid transparent; white-space: nowrap;
}
.btn-primary { background: var(--primary); color: #fff; }
/* 绿色按钮变体（用户 2026-09-22：「这个按钮调整为绿色」；随后又要求
   「你添加明细行和转入销售订单是同一个绿色吗？」⇒ 必须与「新增 / 转入销售订单」**同一个绿**）——
   走 --c-green（#13ce66），不再是 --c-success（#10B981，那是**状态色**、与按钮绿不同）*/
.btn-green { background: var(--c-green); color: #fff; }
.btn-primary:hover { background: #4338CA; }
.btn-ghost { background: var(--card); border-color: var(--line-strong); color: var(--text); }
.btn-ghost:hover { border-color: var(--primary); color: var(--primary); }
/* 「新增」按钮专用绿色（用户 2026-09-19） */
#ptNew { background: var(--c-green) !important; border-color: var(--c-green) !important; color: #fff !important; }
#ptNew:hover { background: #0fb857 !important; border-color: #0fb857 !important; }
/* 「转入销售订单」也走同一款绿（用户 2026-09-19 20:3x：「转入销售订单按钮是绿色」）——
   它原本是 `.btn-primary`（主色蓝），现与「新增」统一为 #13ce66
   只改**颜色**，不改按钮档位/尺寸（仍是 `.btn-xs`）；`bTr.disabled` 的禁用逻辑照旧 */
#ptTransfer { background: var(--c-green) !important; border-color: var(--c-green) !important; color: #fff !important; }
#ptTransfer:hover { background: #0fb857 !important; border-color: #0fb857 !important; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: #DC2626; }
/* 工具条「删除」= **描边 + 红字**（用户 2026-09-18 19:43 按钮规范：删除统一这种形态，
   不用实心红底 —— 实心红会跟主操作按钮抢注意力）
   原来靠 `#ptDel` / `#orderDelBtn` 两个 id 选择器各写一遍，现在收敛成一个类 */
.btn-danger-ghost { color: var(--danger); border-color: rgba(239,68,68,.4); }
.btn-danger-ghost:hover { border-color: var(--danger); color: var(--danger); background: rgba(239,68,68,.06); }
/* Small 档（.btn-xs 是历史类名，一并归到 Small，尺寸跟标准走） */
.btn-sm, .btn-xs { height: var(--btn-h-sm); padding: 0 var(--btn-px-sm); font-size: var(--btn-fs-sm); }
/* Large 档 */
.btn-lg { height: var(--btn-h-lg); padding: 0 var(--btn-px-lg); font-size: var(--btn-fs-lg); border-radius: var(--btn-radius); }
.btn-block { width: 100%; min-height: var(--btn-h-lg); }
/* 禁用的按钮要看得出来：原来**只有暗色主题**写了 `.btn:disabled` →
   浅色下禁用的按钮看着和可用的一样（用户要靠点一下才知道 ，2026-09-16 补） */
.btn:disabled, .link-btn:disabled { opacity: .45; cursor: not-allowed; }
/* 工具条「删除」统一用 `.btn-danger-ghost`（见上方 .btn-danger 一段）——
   原来这两个 id 各写一遍告警色，已删除；`#orderDelOff`（有关联单据→灰掉）保留 */
#orderDelOff { opacity: .45; cursor: not-allowed; }
.link-btn { background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--fs-13); color: var(--primary); padding: 2px 4px; }
/* 「关联卡片」表格里可点的格子（用户 2026-09-27：采购编号要能跳到处理中心详情）——
   与全站链接同色，悬停下划线；**不要**加圆角（全站 border-radius:0）✓ */
.cell-link { color: var(--primary); text-decoration: none; }
.cell-link:hover { text-decoration: underline; }
/* 关联卡片里"链接样式的按钮"（如「查看质检」——点开应用内提示，不是跳页）✓ */
.cell-link-btn { background: none; border: 0; padding: 0; cursor: pointer; font-family: inherit; font-size: inherit; color: var(--primary); }
.cell-link-btn:hover { text-decoration: underline; }
.link-btn:hover { text-decoration: underline; }
.link-btn.danger { color: var(--danger); }

/* ---------- 筛选栏 ---------- */
.info-list { display: flex; flex-direction: column; gap: 12px; }
.filter-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; box-shadow: var(--shadow);
}
/* 查询行里的「新建 XX」按钮：推到整行最右侧 */
.filter-bar .fb-create { margin-left: auto; }
/* 🆕 列表页「批量导入」按钮（用户 2026-09-26）：
   只负责**推到整行最右**，观感与悬停完全跟 `.btn-ghost` 一致 ——
   以前它复用了 `.fb-create`（= 新建按钮的绿色样式，还带 `!important`）⇒ 悬停变绿，与旁边的
   「查询 / 重置」不一致 ✗ */
.filter-bar .imp-btn { margin-left: auto; }
/* 「整组右对齐」的按钮组（用户 2026-09-26 货品管理：「把 批量导入 / 批量录入 / 新增规格 / 导出 调整为右对齐」）——
   · 组内 `nowrap`：四个按钮不拆行、始终保持一组 ✓
   · 组本身 `margin-left: auto`：**每一行都生效** ⇒ 整组换到第二行时依然贴右边缘 ✓
   · 组内第一个按钮的 `.imp-btn`（也是 margin-left:auto）要**清掉**，否则会在组内再吃一次剩余空间、
     把"批量导入"推到左边、其余挤到右边 ⇒ 看起来就散了 ✗ */
.filter-actions-end { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; margin-left: auto; }
/* 组内第一个按钮（`.imp-btn`）与「新增」（`.fb-create`）都自带 `margin-left: auto`，要**清掉** ——
   否则会在组内再吃一次剩余空间，把成员拆到两头（用户 2026-09-26：供货的三个按钮要「右侧对齐、间隙一致」）✗ */
.filter-actions-end .imp-btn,
.filter-actions-end .fb-create { margin-left: 0; }
/* 🆕 「批量录入」的粘贴表格（用户 2026-09-26）：格子里的输入框**平时无框**，像 Excel 一样直接填 / 粘贴 ✓ */
#grid td { padding: 2px 4px; }
#grid .cell {
  width: 100%; min-height: 30px; padding: 0 8px; font-size: var(--fs-13);
  border: 1px solid transparent; background: transparent; color: var(--text);
  font-family: inherit; outline: none;
}
#grid .cell:hover { border-color: var(--line); }
#grid .cell:focus { border-color: var(--primary); background: var(--card); box-shadow: var(--ring); }
/* 粘贴的值不在选项里 ⇒ 那一格标红（提示区在表格下方，见 `#pasteWarn`）——
   用户 2026-09-26：「售卖状态只有 2 个选项，我在批量录入页面，把别的值粘贴过来，你也允许，这个是不对的」
   ⚠️ 必须排在 `:hover` / `:focus` **之后**（同特异性靠后生效），否则悬停/聚焦时红框会被盖掉 ✗ */
#grid .cell.is-err { border-color: var(--danger); background: var(--danger-soft); }
#grid select.cell.is-err { color: var(--danger); }
/* 下拉列（售卖状态 / 主单位…）在格子里也是**真下拉框** ✓：平时同样无框，箭头留出右侧内距 ✓ */
#grid select.cell { background: transparent; padding-right: 22px; cursor: pointer; }
/* 「带单位」字段（如 规格 = CM）：**每一行都把单位显示在输入框后面**（用户 2026-09-26）✓
   外套 `.cellwrap` = flex 行：控件撑满、单位 `flex:none` 贴在最后 ✓ */
#grid .cellwrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
#grid .cellwrap > .cell { flex: 1 1 auto; min-width: 0; }
#grid .cellwrap > .num-suffix { flex: none; font-size: var(--fs-13); }
/* 「允许填写时新增选项」的列 = **可输入下拉**（`.ssel` 包着输入框，用户 2026-09-26）——
   `.cell` 挂在里面的 `.ssel-inp` 上 ⇒ 撑满这格的规则要落在中间那层 `.ssel` 上 ✓
   （否则 `.cellwrap > .cell` 匹配不到输入框，格子会被压成内容宽 ✗） */
#grid .cellwrap > .ssel { flex: 1 1 auto; min-width: 0; }
/* 🆕 「批量录入」卡片**铺满浏览器可用高度**（用户 2026-09-26）——
   卡片高度由页面 JS 按「卡片自身位置 + 窗口高」算好写进 style；这里只负责**纵向分配**：
   标题 / 说明 / 底部按钮都不动，中间的滚动表格吃掉剩余高度 ⇒ 一屏尽量多显示几行 ✓
   ⚠️ 每一层都要 `min-height: 0`：flex 子项默认 min-height:auto ⇒ 不写的话表格会被内容撑开、滚不动 ✗ */
#entryCard { display: flex; flex-direction: column; }
#entryCard > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
#entryCard .table-wrap { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; }
/* 「导入核对」卡片 = **同一套铺满**（用户 2026-09-26：「解析结果这个卡片，高度默认占满浏览器」）——
   与上面 `#entryCard` 完全同构：卡片纵向 flex ⇒ 标题 / 来源说明 / 底部按钮都不动，
   中间那张核对表格吃掉剩余高度（一屏尽量多显示行）✓
   ⚠️ 同样每一层都要 `min-height: 0`：flex 子项默认 `min-height:auto` ⇒ 不写会被内容撑开、滚不动 ✗ */
#chkCard { display: flex; flex-direction: column; }
#chkCard > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
#chkCard .table-wrap { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; }
/* 🆕 表头**冻结**（用户 2026-09-26：「这一行表头冻结」）—— 「批量录入」与导入核对两张表都用 ✓
   · `.table-wrap` 就是滚动容器 ⇒ sticky 在这里才生效 ✓
   · 底色用与普通表头**完全相同**的 `--card-2`，粘住之后不会看着像另一块 ✗
   · z-index 3：压过普通单元格，又不抢冻结列的 4 ✓ */
.tbl-frz thead th { position: sticky; top: 0; z-index: 3; background: var(--card-2); }
/* ============================================================
   「导入核对页」（供货报价导入 / 货品批量导入）—— **两页共用这一套**（用户 2026-09-26：
   「货品管理，批量导入，解析并预览后也需要支持可以编辑」）
   · 两页的卡片 id 都是 `#chkCard` ⇒ 规则可以共用一份（别在各自视图里再写一遍 ✗）
   · 🔴 出错的红边一律走 **class**（`.cell-bad` / `.imp-bad`），**不写内联 style** ——
     内联优先级高于 class ⇒ 实时重校验撤不掉「已修好的那格红标」✗
   ============================================================ */
#chkCard .table-wrap { overflow: auto; }
#chkCard td.cell-bad { background: #FEF2F2; }
#chkCard td.cell-bad:hover { background: #FEE2E2; }
/* 字段列给**最小宽度**：列一多就把表格撑得比容器宽 ⇒ 出横向滚动条，
   绝不靠压窄输入框硬塞进一屏（用户 2026-09-26：「后面的字段输入值宽度不够了，你就横向滑动」）✓ */
#chkCard th.f-imp { min-width: 170px; }
/* 下拉多选框（`.ms-field`，复用系统样式，压到与其它输入框一致的 28px）——
   面板打开时会被挂到 body ⇒ 面板那几条**不能带 `#chkCard` 前缀** ✓ */
#chkCard .imp-ms { display: block; width: 100%; }
#chkCard .imp-ms .ms-text { font-size: 12px; }
.imp-ms-panel { font-size: 12px; }
.imp-ms-panel .ms-opt { padding: 5px 9px; font-size: 12px; }
/* 系统日期选择器（`.dp-field`）在核对表里压到 28px；放开全局的 max-width:220px ✓ */
#chkCard .imp-dp { display: inline-flex; width: 100%; max-width: none; }
#chkCard .imp-dp .dp-display { height: 28px; min-height: 28px; font-size: 12px; padding: 0 26px 0 6px; line-height: 26px; }
#chkCard .imp-dp .dp-trigger { width: 26px; }
#chkCard .imp-dp .dp-trigger svg { width: 14px; height: 14px; }
/* 出错控件的红边（class 化 ⇒ paintRow 一 toggle 就同步出现/消失）✓ */
#chkCard input.imp-bad,
#chkCard select.imp-bad { border-color: #FCA5A5 !important; background: #FFF !important; color: #DC2626 !important; }
#chkCard .dp-field.imp-bad .dp-display { border-color: #FCA5A5; background: #FFF; color: #DC2626; }
#chkCard .dp-field.imp-bad .dp-trigger { color: #DC2626; }
#chkCard .ms-field.imp-bad .ms-trigger { border-color: #FCA5A5; background: #FFF; color: #DC2626; }
/* 订单中心筛选行控件多（搜索 + 6 下拉 + 2 日期 + 4 按钮）→ 适当压缩宽度，保证一行放得下（用户 2026-09-17） */
.filter-bar-orders { gap: 6px; }                                  /* 行内间距 8→6，省约 26px */
.filter-bar-orders .cs-input { flex: 0 1 180px; min-width: 140px; } /* 搜索框（用户 2026-09-17 指定 180px） */
.filter-bar-orders .filter-select { padding: 0 4px; }             /* 下拉左右内边距 6→4 */
.filter-bar-orders .btn { padding: 0 10px; }                      /* 按钮左右内边距 12→10 */
/* 提示 / 选项字号缩小（14→12）：筛选行框窄，14px 会把「搜索订单号/客户/销售人」「下单开始日期」等提示截断（用户 2026-09-17） */
.filter-bar-orders .cs-input,
.filter-bar-orders .filter-select,
.filter-bar-orders .csel-trigger,
.filter-bar-orders .dp-display { font-size: var(--fs-12); }
.filter-bar-orders .lg-tilde { font-size: var(--fs-12); }
/* 自定义下拉触发器比原生多「箭头 + 间距」，会挤掉文字宽度 → 筛选行里收紧（不改整体宽度，一行布局不变） */
.filter-bar-orders .csel-trigger { gap: 4px; }
.filter-bar-orders .csel-caret { width: 12px; height: 12px; }
/* 按钮分成两组（用户 2026-09-17）：
   · .filter-actions      = 查询 / 重置 → 紧跟在「结束日期」后面，**不**右推
   · .filter-actions-end  = 新建订单 / 立即同步 → 整组推到整行最右
   两组都 nowrap：组内不换行，放不下时整组换行 → 保证每组按钮永远同行 */
.filter-bar-orders .filter-actions { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.filter-bar-orders .filter-actions-end { margin-left: auto; }
.filter-bar-orders .fb-create { margin-left: 0; }                 /* 取消旧的「推到最右」，改由 .filter-actions-end 推 */
.info-count { font-size: var(--fs-13); color: var(--muted); margin-right: 4px; white-space: nowrap; }
.cs-input, .filter-select {
  min-height: 34px; padding: 0 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-14); outline: none;
}
.cs-input { flex: 0 1 260px; }
.cs-input:focus, .filter-select:focus { border-color: var(--primary); box-shadow: var(--ring); }
/* 🆕 筛选行里的**可搜索下拉**（`.ssel-filter`；用户 2026-09-26：供应商 / 货品的筛选要能模糊搜索）——
   必须**定宽**：`.ssel` 自身没有宽度约束、会按内容撑开 ⇒ 实测被撑到 **1191px**，把筛选行挤断 ✗✗
   （原生下拉只有 126px）；宽度取原生下拉的量级，高度与原生下拉（34）对齐 ✓ */
.filter-bar .ssel-filter { width: 130px; max-width: 130px; flex: 0 0 auto; }
.filter-bar .ssel-filter .ssel-inp { height: 34px; min-height: 34px; font-size: var(--fs-14); }

/* 🆕 筛选行**整体压缩**（用户 2026-09-26：「你搜索变成好多行了，不是1行了」）——
   背景：供货报价的筛选行原本合计约 **1225px**，而 1440 视口下筛选栏内宽只有 **1206px**
        ⇒ 只差 19px，右侧按钮组（.filter-actions-end）整组被挤到第二行、栏高 56 → **96（两行）** ✗
   做法：含可搜索筛选的筛选行打 `filter-bar-fs`（见 views/entity/list.ejs）⇒ 只压这一类页面：
        · 间隙 8 → 6（7 处，省 14）
        · 搜索框 260 → 180（省 80）
        · 按钮左右内边距 12 → 10（查询 / 重置，省 24）
        · 右侧按钮组间隙 8 → 6（省 4）
        ⇒ 给「供应商 / 全部货品」两个下拉**让出加宽的空间** ✓
   ⚠️ 不支持搜索的页面**一点不变**（它们没有 `filter-bar-fs` 类）✓

   「供应商 / 全部货品」两个可搜索下拉的宽度**按窗口宽度分三档**（用户 2026-09-26：
   「供应商和货品的搜索行宽度都要增宽一些」）—— 原则：**能用多宽就给多宽，但整行必须还是一行**：
        · 视口 > 1400（栏内宽 > 1168）：**180**（与关键字搜索框同宽）✓
        · 视口 ≤ 1400（栏内宽 ≤ 1168）：cs-input 140 + 两下拉 160 ⇒ 合计约 1088 ✓（1366 视口内宽 1132）
        · 视口 ≤ 1319（栏内宽 ≤ 1087）：两下拉降到 126 ⇒ 合计约 1020 ✓（1280 视口内宽 1046）
   公式：两个下拉之外的元素合计 628（含 7 个 6px 间隙）⇒ 总宽 = 628 + 搜索框 + 2 × 下拉宽 ✗ */
.filter-bar-fs { gap: 6px; }
.filter-bar-fs .cs-input { flex: 0 1 180px; min-width: 120px; }
.filter-bar-fs .btn { padding: 0 10px; }
.filter-bar-fs .filter-actions-end { gap: 6px; }
.filter-bar.filter-bar-fs .ssel-filter { width: 180px; max-width: 180px; }
@media (max-width: 1400px) {
  .filter-bar-fs .cs-input { flex: 0 1 140px; }
  .filter-bar.filter-bar-fs .ssel-filter { width: 160px; max-width: 160px; }
}
@media (max-width: 1319px) {
  .filter-bar.filter-bar-fs .ssel-filter { width: 126px; max-width: 126px; }
}

/* ---------- 表格 ---------- */.info-table-shell { display: flex; flex-direction: column; gap: 12px; }
/* 列表所在卡片要有阴影（用户 2026-09-21）——只作用于列表页的表格外壳，不动详情页/报表里的 .table-wrap */
.info-table-shell > .table-wrap { background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow); }
.table-wrap { overflow-x: auto; }
.card .table-wrap { border-radius: var(--radius); }
.data-table {
  width: 100%;
  /* 用 **separate + spacing 0** 而不是 collapse（用户 2026-09-21：「列与列竖线有的不显示」）——
     根因：`border-collapse: collapse` 下，**position:sticky 的单元格**（冻结列 / 吸顶表头）
     边框由「表格层」绘制，会被 sticky 单元格自己的绘制层盖掉 ⇒ 竖线时有时无
     separate 模式下边框归各单元格自己画 ⇒ 任何 sticky / 背景都盖不掉
     单元格只有 right / bottom 两条边 → 不会出现双线；外边距为 0 → 列宽/冻结偏移都不变 */
  border-collapse: separate;
  border-spacing: 0;
  background: var(--card);
  font-size: 14px;
  color: var(--list-text);
}
.data-table th, .data-table td { padding: 0 14px; height: 32px; text-align: left; border-bottom: 1px solid var(--line); border-right: 1px solid var(--line); white-space: nowrap; color: var(--list-text); }
.data-table th { font-size: 14px; font-weight: var(--fw-bold); color: var(--list-text); background: var(--card-2); text-align: center; vertical-align: middle; }
/* 暗色模式下列表表头文字 = **白色**（用户 2026-09-21 明确要求；正文由 --list-text 转浅字） */
[data-theme="dark"] .data-table th { color: #ffffff; }
/* ---------- 列表表格行规范（用户 2026-09-18 14:59，2026-09-21 10:47 改）----------
   「列表页 table：**固定行高 30**、文字内容**统一 14**、颜色 **#000000**」
   · 行高由 `height` 定（td 自带 `vertical-align: middle` → 内容垂直居中 ）——
     上下内距去掉只留左右（padding: 0 14px），否则 height 与 padding 叠加会忽高忽低
   · 字号统一 14（表头 13 → 一并统一，整张表一个字号）；正文与表头颜色统一 #000000
   · 只作用于**列表页 / 报表页**的 `.entity-table` + `.data-table`；详情页、明细表（`.pi-table`）、首页仪表盘不受影响 */
.entity-table th, .entity-table td { height: 32px; padding: 0 14px; font-size: 14px; color: var(--list-text); border-right: 1px solid var(--line); }
.entity-table .muted { color: var(--list-text); }
/* ---------- 列表表头「点击上下排序」（用户 2026-09-18 规范；图标照 14:49 给的参考页面）----------
   图标 = 参考系统 `.lxy-table .lxy-table-sort-wrap` 的**纯 CSS 三角**（border 技巧），
   数值直接照抄参考页面：每颗三角 **宽 8（border 左右各 4）× 高 5**、两颗之间 **2px**、
   默认色 **#BFBFBF**、正在生效的那颗转 **主色**（参考系统用的是它的 primary-500）。
   · 上三角 = 升序（`border-color` 的**下边**有色）、下三角 = 降序（**上边**有色）
   · 按钮里**不放 SVG**（伪元素画），`margin-left: 6px` 与参考一致
   · 三态循环（用户 14:49）：未排序 → 升序 → 降序 → **恢复默认**（见 partial thSort.ejs 的 data-dir）
   · 不可排序的类型（选项 / 人员 / 关联 / 公式 / 明细 / 派生列）**不出按钮** */
.th-sortable { white-space: nowrap; }
.th-sort {
  display: inline-block; cursor: pointer; margin-left: 6px;
  padding: 0; border: 0; background: none; vertical-align: -1px;
}
.th-sort::before, .th-sort::after { content: ''; display: block; width: 0; height: 0; border-style: solid; }
/* 上三角（升序）*/
.th-sort::before { border-width: 0 4px 5px; border-color: transparent transparent #BFBFBF; margin-bottom: 2px; }
/* 下三角（降序）*/
.th-sort::after { border-width: 5px 4px 0; border-color: #BFBFBF transparent transparent; }
/* 悬停：整颗转主色（提示可点）*/
.th-sort:hover::before { border-bottom-color: var(--primary); }
.th-sort:hover::after { border-top-color: var(--primary); }
/* 当前排序方向：**只有生效的那颗**是主色，另一颗保持灰 */
.th-sort.is-asc::before { border-bottom-color: var(--primary); }
.th-sort.is-desc::after { border-top-color: var(--primary); }
.data-table tbody tr:hover { background: var(--bg-list); }
.data-table .g-name { font-weight: var(--fw-bold); }
.entity-name-link { color: var(--primary); cursor: pointer; font-weight: normal; font-size: 14px; }
.entity-name-link:hover { text-decoration: underline; }
.data-table .muted { color: var(--list-text); }
.data-table .num { font-variant-numeric: tabular-nums; }
.data-table .empty { text-align: center; color: var(--muted); padding: 32px 0; }
.op-col { white-space: nowrap; }
.op-col .btn { margin-right: 4px; }

/* ---------- 列表「汇总行」（用户 2026-09-21）----------
   「所有字段类型，增加 1 个按钮，就是列表汇总…汇总显示在列表页的最下方」
   · 位置 = 表格**最后一行**（`<tfoot>` 里那一行）→ 分页器之前
   · 只有字段设置里开了「列表汇总」的列才有数字；没开的列留空格，保证列对齐
   · 外观：比正文略重（加粗 + 浅底），与「明细汇总」那一行同一观感；
     文字色走 `--list-text`（亮色纯黑 / 暗色浅字，见变量定义处），不硬编码 */
.sum-row td {
  height: 32px; padding: 0 14px; font-size: 14px; font-weight: var(--fw-bold);
  color: var(--list-text); background: var(--bg-list); border-top: 1px solid var(--line);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sum-row td { border-right: 1px solid var(--line); }
/* 首格 = 「合计」+「全部 N 条」小字：说明汇总口径是**筛选后的全部记录**（不是只算当页）*/
.sum-row .sum-tag { font-weight: var(--fw-bold); color: inherit; }
.sum-row .sum-sub { margin-left: 8px; font-size: 12px; font-weight: 400; color: var(--muted); }

/* ---------- 列表「冻结列」（用户 2026-09-18 15:0x）----------
   「在字段显示设置里点某个字段的冻结 → 该字段**及其前面的字段**在列表里固定在左侧；
     只允许冻结 1 个字段；但是从来都不影响系统编号」
   · 冻结范围 = **系统编号（第 0 列，恒冻结）+ 该字段及之前的列**；
   · 每个单元格的 `left` 偏移由 `public/js/app.js` 在页面渲染后按真实列宽算出（列宽是自适应的）；
   · 冻结格必须**不透明**，否则横向滚动时下面的内容会透出来 */
.data-table td.is-fz { position: sticky; z-index: 3; background: var(--card); }
.data-table th.is-fz { position: sticky; z-index: 4; background: var(--card-2); }
/* 行悬停时冻结格跟随高亮（否则白色块在灰底行里很跳） */
.data-table tbody tr:hover td.is-fz { background: var(--bg-list); }
/* 冻结块的右边界：
   · 静态一条 1px 分隔线（标记冻结区的边界）—— 用 box-shadow 而不是 border，border 会改变列宽、
     把「输入框 350」那类对齐打乱；
   · **横向滚动时加一层投影**（用户 2026-09-18 15:30：「冻结行在往右拖动的时候，
     冻结行右侧需要有 1 个阴影」）—— 表示右侧还有内容正从冻结块下面滑过去。
     JS（`public/js/app.js` 冻结列那段）按 `.table-wrap` 的 `scrollLeft > 0` 给表格加 `.is-scrolled-x` 类，
     滚回最左就摘掉 → 没滚动时不会有一条莫名其妙的阴影
     阴影能盖住右侧滚过来的内容，靠的是冻结格自身的 `z-index`（td 3 / th 4 > 普通格 auto）
   · **改「柔和渐隐」**（用户 2026-09-18 17:52，截图问「你能看出来这个投影吗」→ 选「柔和渐隐（推荐）」）：
     旧值 `8px 0 10px -6px rgba(…,.30)` 是**单层**，净可见约 4px、边缘均匀 → 看着像**一条灰边**、不像阴影 ；
     试过的第二版（三层 `box-shadow`，alpha .14/.11/.08）**太淡看不出** →
     最终改用 **伪元素 + linear-gradient** 做真正的「渐隐带」：从冻结列右缘起 16px 内
     `0.20 → 0.10（40%）→ 0.03（75%）→ 0` 平滑化开 —— 边缘有存在感、又不会硬边
     · 用 ::after 绝对定位在 td/th 的**右外侧**（`left:100%`），`pointer-events:none` 不挡点击；
     · 冻结格自身 `z-index`（td 3 / th 4）> 普通格 auto → 渐变带盖在滑过来的内容上
     · 只在 `.is-scrolled-x`（横向滚动过）时才有
     想调强弱：只改这一行渐变的 alpha / 宽度（16px）；**不要**回到单层 box-shadow 写法。 */
.data-table .is-fz-last { box-shadow: 1px 0 0 0 var(--line-strong); }
/* 渐隐带本体（常驻在 DOM 里，靠 opacity 淡入淡出 → 保留 0.16s 的呼吸感） */
.data-table .is-fz-last::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 100%; width: 16px;
  pointer-events: none; opacity: 0; transition: opacity .16s ease-out;
  background: linear-gradient(to right,
    rgba(15, 23, 42, .20) 0%,
    rgba(15, 23, 42, .10) 40%,
    rgba(15, 23, 42, .03) 75%,
    rgba(15, 23, 42, 0) 100%);
}
.data-table.is-scrolled-x .is-fz-last::after { opacity: 1; }
html[data-theme="dark"] .data-table .is-fz-last::after {
  background: linear-gradient(to right,
    rgba(0, 0, 0, .55) 0%,
    rgba(0, 0, 0, .28) 40%,
    rgba(0, 0, 0, .08) 75%,
    rgba(0, 0, 0, 0) 100%);
}

/* 状态徽标 */
.badge {
  display: inline-flex; align-items: center; padding: 1px 10px;
  border-radius: 0; font-size: var(--fs-12); font-weight: var(--fw-regular); line-height: 20px;
}
.badge.is-success { background: rgba(16,185,129,.12); color: #10B981; }
/* 「新增」按钮专用绿色（用户 2026-09-19：#13ce66）—— 已收进 --c-green 令牌 */
.btn.btn-create, .fb-create { background: var(--c-green) !important; color: #fff; }
.btn.btn-create:hover, .fb-create:hover { background: #0fb857 !important; }
.badge.is-info { background: rgba(14,165,233,.12); color: #0EA5E9; }
.badge.is-warning { background: rgba(245,158,11,.14); color: #F59E0B; }
.badge.is-danger { background: rgba(239,68,68,.12); color: #EF4444; }
.badge.is-muted { background: var(--chip); color: var(--muted); }
/* 汇率看板：标题 + 「按日期查看」筛选 */
.rates-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: space-between; }
.rates-filter { display: flex; align-items: center; gap: 8px; }
.rates-filter input[type=date], .rates-filter input[type=month], .rates-year { padding: 5px 9px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-13); }
.rates-note { margin: 8px 0 10px; }
.rates-dates { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.rates-dates .badge { text-decoration: none; }
/* 「手动调整汇率」月 × 币种网格（仿用户给的月度表：月份行、币种列） */
.rates-grid-wrap { overflow-x: auto; }
.rates-grid { width: 100%; border-collapse: collapse; }
.rates-grid th, .rates-grid td { padding: 6px 10px; text-align: center; white-space: nowrap; }
.rates-grid th:first-child, .rates-grid td:first-child { text-align: left; position: sticky; left: 0; background: var(--card-bg); z-index: 1; }
.rates-grid thead th { background: var(--primary-soft); }
.rates-grid tbody tr:nth-child(even) { background: rgba(0,0,0,.02); }
.rates-grid .rate-cell { width: 128px; max-width: 150px; text-align: right; font-variant-numeric: tabular-nums; padding: 5px 8px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-13); }

/* ---------- 分页（按用户 2026-09-17 给定的规范参数）----------
   参数含义（用户原话）：32 = 高度 · 48 = 宽度 · 22 = 高度（紧凑档）· 12 = 内容距边界间隙 · 16 / 14 = 间隙
   落地：
     · 页码 / 箭头按钮：高 32 · 左右内距 12（内容距边界）· 按钮之间 8 · 字号 14 · 图标 16
     · 「每页 N 条」下拉：按「控件标准 · 下拉」Medium 档（高 32）
     · 「跳至」输入框：宽 48 · 高 32 · 数字居中
     · 三个分组（共 N 页 / 页码 / 每页 / 跳至）之间 16
   圆角写 2px 但被文件末尾的全站直角规则压成 0（用户：圆角不用管）。 */
.cust-pager { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 4px 2px; }
.pg-total { font-size: var(--fs-14); color: var(--muted); }
.pg-nav { display: flex; align-items: center; gap: 8px; }
.pg-btn, .pg-num {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 32px; padding: 0 12px;
  border-radius: 2px; border: 1px solid var(--line);
  background: var(--card); color: var(--text); font-size: var(--fs-14);
}
.pg-btn svg { display: block; }
.pg-num:hover, .pg-btn:hover { border-color: var(--primary); color: var(--primary); }
.pg-num.is-active { background: var(--primary); border-color: var(--primary); color: #fff; }
.pg-btn.is-disabled { opacity: .4; pointer-events: none; }
.pg-gap { color: var(--muted); padding: 0 2px; }
.pg-size, .pg-jump { display: flex; align-items: center; gap: 8px; font-size: var(--fs-14); color: var(--muted); }
.pg-jump { gap: 16px; }                                  /* 「跳至」与输入框、「页」字之间 16 */
.pg-size select, .pg-input {
  height: 32px; min-height: 32px; padding: 0 8px; border-radius: 2px;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-14); outline: none;
}
.pg-input { width: 48px; text-align: center; }           /* 跳至输入框：宽 48 */

/* ---------- 分页 · 五档样式（用户 2026-09-17）----------
   data-style = basic（基本，≤9 页）| more（更多，9–19 页）| jump（跳转，>20 页）| mini（迷你）| simple（简洁）
   · basic / more / jump = 列表页三档，高 32（由 partial 按页数自动切）
   · mini / simple = 弹窗 / 表格内的紧凑档，高 22（.is-compact）
   「基本 / 更多 / 跳转」的差别只在**页码省略策略与是否带「跳至」**（页码窗口逻辑在 partial 里） */
.pg-state { font-size: var(--fs-14); color: var(--muted); white-space: nowrap; }
.pg-state b { color: var(--text); font-weight: 600; }
/* 紧凑档：迷你 / 简洁 —— 高 22、图标 12、间隙收窄 */
.cust-pager.is-compact { gap: 8px; padding: 2px 0; }
.cust-pager.is-compact .pg-total { font-size: var(--fs-12); }
.cust-pager.is-compact .pg-btn,
.cust-pager.is-compact .pg-num { min-width: 22px; height: 22px; padding: 0 8px; }
.cust-pager.is-compact .pg-btn svg { width: 12px; height: 12px; }
/* 简洁档：无边框纯文字（‹ 上一页 / 第 x / y 页 / 下一页 ›） */
.cust-pager[data-style="simple"] .pg-btn.pg-text {
  border: none; background: transparent; color: var(--link); min-width: 0; padding: 0 4px;
}
.cust-pager[data-style="simple"] .pg-btn.pg-text:hover { color: var(--primary); }
/* 弹窗内的分页（转入选单 .tp-pager）= 迷你档：数字按钮高 22 */
.tp-pager .pg-num { min-width: 22px; height: 22px; padding: 0 6px; border-radius: 2px; }

/* ---------- 卡片 ---------- */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px; box-shadow: var(--shadow);
}
.card h2 { margin: 0 0 14px; font-size: var(--fs-18); color: var(--primary-dark); }
[data-theme="dark"] .card h2 { color: var(--text); }
.rel-title { margin: 0 0 12px; font-size: var(--fs-16); font-weight: var(--fw-bold); color: var(--primary-dark); }
[data-theme="dark"] .rel-title { color: var(--text); }
/* **区块标题**（用户 2026-09-18：「区块名字在弹窗和卡片中，如果是显示的状态下，以这种样式显示」）
   = 「16px 加粗深色标题」 + 左侧一条**竖线**。
   尺寸口径照参考图（153×48 截图）实测：竖条 x20-22 / y21-36 → **3×16px**；
   条右边缘 x22 → 文字左边缘 x32 → **条↔字 9px**；文字高 16px（= 字号 16，与 .rel-title 一致，颜色也是它）；
   竖条直角（全站直角规范 `--radius*: 0`；参考图那点微圆是缩放抗锯齿，3px 宽下看不出）。
   **竖条颜色 = 本系统主色 `var(--primary)`**（用户 2026-09-18 下午改：原来照参考图取的橙 #FF9052，
      「前面的竖线颜色不要是橙色，还是本系统的主色」→ 走令牌而不是写死十六进制，主题切换也跟得上）。
   只给**区块**（type=group）的标题加这个类（3 处：entity/form、orders/form、orders/detail）——
      不要写到 .rel-title 本身：那是模块卡 / 详情卡 / 面板标题（「订单信息」卡、「生产维度」卡…）的通用标题，
      它们不该长竖条（用户只要求区块名用这个样式）。 */
.sec-title { display: flex; align-items: center; gap: 9px; }
.sec-title::before { content: ''; flex: none; width: 3px; height: 1em; background: var(--primary); }
/* 区块标题里若还有 pm-hint 之类的小字，别被竖条挤变形（对齐保持居中） */
.sec-title > * { min-width: 0; }
.related-card { margin-top: 16px; }
/* 关联卡片标题行：标题 + 可选操作按钮（如货品详情 → 供货卡片「新建供货」）左右分布 */
.rel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.rel-head .rel-title { margin: 0; }
/* 关联卡片 = 页签形态（用户 2026-09-26 货品详情：「这两张卡片，变成两个 tab 页」）——
   页签直接用现成的 `.pi-subtabs / .pi-subtab`（采购处理详情页「采购下单」下方那套，
   项目自定的 tab 规范：白底 + 选中项主色文字 + 3px 主色下划线）⇒ 这里只补两件事：
   · 右上角按钮（如「+ 新增供货」）靠右：用 `padding-right` 给页签让位，按钮本身绝对定位
   · 面板 / 按钮的显隐（`is-off` = `display:none`，不要用 `hidden` 属性，会被 flex 规则压掉）
   注意（2026-09-26 用户实测反馈：「我切换这个 tab 的时候，这一行的高度会变化」）：
     · 按钮在流里时高 32 + 上下 margin 5 = 42，比页签行（33 + 1 边框 = 34）还高
     · 切到没有按钮的页签时按钮 `is-off`（`display:none`）退出布局 ⇒ 行高 43 掉到 34，整行向上跳 9px
     ⇒ 按钮必须**绝对定位脱离文档流**，行高才恒等于页签自身高度（与范本页一致），且按钮位置固定
     ⇒ 选择器写成 `.pi-subtabs.rel-tabs-head`（两个类）才能压过后面的 `.pi-subtabs`（单类） */

.pi-subtabs.rel-tabs-head { position: relative; padding-right: 160px; }
.pi-subtabs.rel-tabs-head .rt-act { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); margin: 0; }
.rel-pane { padding-top: 4px; }
.rel-pane.is-off, .rt-act.is-off { display: none; }

/* ---------- 控件标准 · 下拉 select（用户 2026-09-17 给定，全站以此为准）----------
   档位      高度   文字左距  文字↔箭头  箭头↔右边  字号   箭头尺寸
   Small     24px   8px      8px        8px       14px  12px   → .sel-sm
   Medium    32px   16px     8px        12px      14px  12px   → .sel（默认）
   Large     40px   16px     8px        12px      16px  16px   → .sel-lg
   原生 select 必须 appearance:none + **自绘箭头**：浏览器自带箭头的位置/间距不可控，
      做不到「箭头↔右边 12px」这种精确值。
      padding-right = 8(文字↔箭头) + 箭头 + 箭头↔右边：
        Small 8+12+8=28 · Medium 8+12+12=32 · Large 8+16+12=36
   全站「直角」规则（文件末尾 `border-radius: 0 !important`）在生效 → 圆角仍为 0；
      要 2px 得把 select 从那条规则里放行，需用户确认（2026-09-17 待定）。 */
:root {
  --sel-h-sm: 24px;  --sel-h-md: 32px;  --sel-h-lg: 40px;
  --sel-px-sm: 8px;  --sel-px-md: 16px; --sel-px-lg: 16px;
  --sel-fs-sm: 14px; --sel-fs-md: 14px; --sel-fs-lg: 16px;
  --sel-caret-sm: 12px; --sel-caret-md: 12px; --sel-caret-lg: 16px;
  --sel-pr-sm: 8px;     --sel-pr-md: 12px;    --sel-pr-lg: 12px;
  --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748B' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5l4-4'/%3E%3C/svg%3E");
}
[data-theme="dark"] {
  --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238893B8' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5l4-4'/%3E%3C/svg%3E");
}
.sel {
  appearance: none; -webkit-appearance: none;
  height: var(--sel-h-md); padding: 0 32px 0 var(--sel-px-md);
  border: 1px solid var(--field-border); outline: none;
  background-color: var(--field-bg);
  background-image: var(--sel-arrow); background-repeat: no-repeat;
  background-position: right var(--sel-pr-md) center;
  background-size: var(--sel-caret-md) var(--sel-caret-md);
  color: var(--text); font-family: inherit; font-size: var(--sel-fs-md); line-height: 1;
}
.sel-sm {
  height: var(--sel-h-sm); padding: 0 28px 0 var(--sel-px-sm); font-size: var(--sel-fs-sm);
  background-position: right var(--sel-pr-sm) center;
  background-size: var(--sel-caret-sm) var(--sel-caret-sm);
}
.sel-lg {
  height: var(--sel-h-lg); padding: 0 36px 0 var(--sel-px-lg); font-size: var(--sel-fs-lg);
  background-position: right var(--sel-pr-lg) center;
  background-size: var(--sel-caret-lg) var(--sel-caret-lg);
}
.sel:focus, .sel-sm:focus, .sel-lg:focus { border-color: var(--primary); box-shadow: var(--ring); }

/* ---------- 控件标准 · 输入框 input（用户 2026-09-17 给定，全站以此为准）----------
   档位      高度   文字距边界  字号   类名
   Small     24px   8px        14px   → .inp-sm
   Medium    32px   12px       14px   → .inp（默认）
   Large     40px   12px       16px   → .inp-lg
   Medium + 前缀 / 后缀图标：图标 16px、**图标距边界 12px**、**图标↔文字 8px**、文字 14px、高 32
     → 带图标那一侧的文字内距 = 12 + 16 + 8 = 36px（.inp-wrap + .inp-ico）
   与下拉的差别：下拉「文字左距」是 16（用户给定），input 是 12 —— 别混用。 */
:root {
  --inp-h-sm: 24px;  --inp-h-md: 32px;  --inp-h-lg: 40px;
  --inp-px-sm: 8px;  --inp-px-md: 12px; --inp-px-lg: 12px;
  --inp-fs-sm: 14px; --inp-fs-md: 14px; --inp-fs-lg: 16px;
  --inp-ico: 16px;      /* 前缀 / 后缀图标尺寸 */
  --inp-ico-in: 12px;   /* 图标距边界 */
  --inp-ico-gap: 8px;   /* 图标 ↔ 文字 */
  --inp-px-ico: 36px;   /* 12 + 16 + 8 = 带图标一侧的文字内距 */
  /* 搜索框的**后缀线性搜索图标**（描边圆 + 手柄，16px；同 select 的自绘箭头思路：CSS 背景图，
     这样全站搜索框不必逐个改 HTML，也不会漏）用户 2026-09-17 */
  --inp-search-ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748B' stroke-width='1.5' stroke-linecap='round'%3E%3Ccircle cx='6.8' cy='6.8' r='4.5'/%3E%3Cpath d='M10.5 10.5 14 14'/%3E%3C/svg%3E");
}
[data-theme="dark"] {
  --inp-search-ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238893B8' stroke-width='1.5' stroke-linecap='round'%3E%3Ccircle cx='6.8' cy='6.8' r='4.5'/%3E%3Cpath d='M10.5 10.5 14 14'/%3E%3C/svg%3E");
}
.inp, .inp-sm, .inp-lg {
  height: var(--inp-h-md); padding: 0 var(--inp-px-md); box-sizing: border-box;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--inp-fs-md); line-height: 1; outline: none; width: 100%;
}
.inp:focus, .inp-sm:focus, .inp-lg:focus { border-color: var(--primary); box-shadow: var(--ring); }
.inp-sm { height: var(--inp-h-sm); padding: 0 var(--inp-px-sm); font-size: var(--inp-fs-sm); }
.inp-lg { height: var(--inp-h-lg); padding: 0 var(--inp-px-lg); font-size: var(--inp-fs-lg); }
/* 带前缀 / 后缀图标（Medium 档）：外层 .inp-wrap，图标用内联 SVG 并加 .inp-ico.is-pre / .is-suf */
.inp-wrap { position: relative; display: flex; align-items: center; width: 100%; }
.inp-wrap .inp-ico {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: var(--inp-ico); height: var(--inp-ico); display: block;
  color: var(--muted); pointer-events: none;
}
.inp-wrap .inp-ico.is-pre { left: var(--inp-ico-in); }
.inp-wrap .inp-ico.is-suf { right: var(--inp-ico-in); }
.inp-wrap .inp.is-pre { padding-left: var(--inp-px-ico); }
.inp-wrap .inp.is-suf { padding-right: var(--inp-px-ico); }

/* ---------- 表单 ---------- */
/* 表单布局标准（用户 2026-09-17 给定，三种布局共用一套参数）：
   ① **垂直**（默认）：标签在上、输入框在下 → 标签↔输入框 **8**；字段与字段（列与列）上下 **24**
      适用于标签较长的表单
   ② **水平**：标签在左（**右对齐**）、输入框在右（左对齐） → 标签↔输入框 **16**；字段与字段上下 **24**
      适用于标签较短的表单 → 单行加 `.is-h`，或给表单容器加 `.form-h` 整表生效
   ③ **直线**：所有表单内容排成 **1 行** → 容器加 `.form-line`（适用于内容较少的表单） */
:root {
  --form-lb-v: 8px;        /* 垂直布局：标签 ↔ 输入框（上下） */
  --form-lb-h: 16px;       /* 水平布局：标签 ↔ 输入框（左右） */
  --form-row-gap: 14px;    /* 列与列（字段与字段）上下间距 —— 三种布局统一 14（2026-09-20 用户改） */
  --form-lb-w: 96px;       /* 水平布局的标签列宽（右对齐用；长标签可自行覆盖） */
  /* 详情页字段的「列名」列宽（右对齐用）
     —— 用户 2026-09-18 16:38：「整体调整一下，字段列名称，确保 8 个中文汉字不换行」
     实测（14px）：纯 8 个汉字 + 全角冒号 = **126px**；最坏情况「销售额（收含税/运费）：」
     （8 汉字 + 全角括号 + 半角斜杠 + 冒号）= **160px** → 统一取 **168px**（留 8px 余量）。
     只改宽度、**不动列数**（详情页仍是一行 4 个）→ 每列内容由 181 变窄到约 **108**，
     值较长时会多折几行，属预期（用户要的是列名单行）。 */
  --dt-lb-w: 100px;   /* 2026-09-18 17:02 用户指定 100（96 → 168 → 160 → 100，都是他一轮轮试出来的） */
  /* 详情页字段的字号 / 颜色（用户 2026-09-18 15:40 给定 → **列名与内容同字号、各自一个色**） */
  --dt-fs: 14px;           /* 列名 + 内容 都 14 */
  --dt-label: var(--ink-2);   /* 列名颜色（跟主题走：亮 #494949 / 暗 浅灰）*/
  /* 列名 ↔ 内容 的间隔（用户 2026-09-18 16:54：「降为现在的一半」→ 16 → **8**）——
     详情页（`.detail-item` / `.kv-item`）/ 采购出货生产卡片（`.po-item`）共用这一个变量 */
  --dt-gap: 8px;
  --dt-text: #130928;      /* 内容颜色 */
}
/* 暗色主题：`#130928`（近黑）在深底上会看不见 → 内容用暗色主题的正文色、列名用它的弱化色 */
[data-theme="dark"] { --dt-label: var(--muted); --dt-text: var(--text); }
.edit-form { display: flex; flex-direction: column; gap: var(--form-row-gap); }
.form-row { display: flex; flex-direction: column; gap: var(--form-lb-v); }
/* ② 水平布局：标签右对齐、输入框左对齐；两者间距 16 */
.form-row.is-h,
.form-h .form-row,
.form-line > .form-row { flex-direction: row; align-items: center; gap: var(--form-lb-h); }
.form-row.is-h > label,
.form-h .form-row > label,
.form-line > .form-row > label {
  flex: none; width: var(--form-lb-w); justify-content: flex-end; text-align: right;
  /* 标签要与**内容的第一行**对齐（如「数字格式」要和上面的类型选择框同一行、垂直居中），
     不能整块内容居中 —— 内容多行时（数字格式 / 货币 / 进度）居中的话标签会跑到中间去
     （用户 2026-09-17 反馈：「数字格式四个字一直在数字类型选择框的那一行，不是居中」） */
  align-self: flex-start; align-items: center; min-height: var(--sel-h-md);
}
/* 水平布局下输入框占满标签右边的剩余宽度（默认的 width:100% 会把 label 挤扁） */
.form-row.is-h > input, .form-row.is-h > select, .form-row.is-h > textarea, .form-row.is-h > .csel, .form-row.is-h > .dp-field,
.form-h .form-row > input, .form-h .form-row > select, .form-h .form-row > textarea, .form-h .form-row > .csel,
.form-line > .form-row > input, .form-line > .form-row > select {
  flex: 1 1 auto; width: auto; min-width: 0;
}
/* ③ 直线布局：一行排开（内容少的表单，如筛选 / 快填） */
.form-line { display: flex; align-items: center; gap: var(--form-lb-h); flex-wrap: nowrap; }
.form-line > label { flex: none; text-align: right; }
.form-line > .form-row { flex: 1 1 auto; min-width: 0; }

/* ---------- Switch 开关规范（用户 2026-09-17 给定）----------
   三种样式：① 常规（默认） ② 带文字（开关内写「开 / 关」等补充说明） ③ 不可用（失效态）
   两种尺寸：**Large 42×24**（全页面行动点，默认）/ **Small 28×16**（较小场景，表单行里用它）
   用法：<label class="sw sw-sm"><input type="checkbox"><span class="sw-pill"></span><span class="sw-label">文案</span></label>
   圆角：pill / 圆点都不在全局「强制直角」名单里 → 这里的药丸形状不会被压平 */
:root {
  --sw-lg-w: 42px; --sw-lg-h: 24px; --sw-lg-knob: 20px;
  --sw-sm-w: 28px; --sw-sm-h: 16px; --sw-sm-knob: 12px;
  --sw-on: var(--primary);   /* 开：主色 */
  --sw-off: #C9CDD4;         /* 关：灰 */
}
.sw { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; font-size: var(--fs-14); color: var(--text); }
.sw > input[type=checkbox] { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.sw-pill {
  position: relative; flex: none; box-sizing: border-box;
  width: var(--sw-lg-w); height: var(--sw-lg-h); border-radius: 999px;
  background: var(--sw-off); display: inline-flex; align-items: center;
  padding: 0 2px; transition: background .18s ease;
}
.sw-pill::after {
  content: ''; flex: none; width: var(--sw-lg-knob); height: var(--sw-lg-knob);
  border-radius: 50%; background: #fff; transition: margin-left .18s ease, width .18s ease;
}
.sw > input:checked + .sw-pill { background: var(--sw-on); }
.sw > input:checked + .sw-pill::after { margin-left: calc(var(--sw-lg-w) - var(--sw-lg-knob) - 4px); }
/* Small 尺寸 */
.sw-sm .sw-pill { width: var(--sw-sm-w); height: var(--sw-sm-h); }
.sw-sm .sw-pill::after { width: var(--sw-sm-knob); height: var(--sw-sm-knob); }
.sw-sm > input:checked + .sw-pill::after { margin-left: calc(var(--sw-sm-w) - var(--sw-sm-knob) - 4px); }
/* ② 带文字：文字画在开关**内部**（开→左、关→右），颜色为白、字号 12 */
.sw-text .sw-pill::before {
  content: attr(data-off); position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  color: #fff; font-size: var(--fs-12); line-height: 1; white-space: nowrap;
}
.sw-text .sw-sm .sw-pill::before, .sw-sm.sw-text .sw-pill::before { right: 4px; font-size: 11px; }
.sw-text > input:checked + .sw-pill::before { content: attr(data-on); left: 5px; right: auto; }
.sw-text.sw-sm > input:checked + .sw-pill::before { left: 4px; }
/* ③ 不可用（失效态）：整块变淡、不可点 */
.sw > input:disabled + .sw-pill { opacity: .45; }
.sw:has(> input:disabled) { cursor: not-allowed; }
.sw:has(> input:disabled) .sw-label { color: var(--muted); }
.sw-label { font-size: var(--fs-13); color: var(--text); }
.sw-hint { font-size: var(--fs-12); color: var(--muted); }
.form-row label { font-size: var(--fs-13); color: var(--muted); display: flex; align-items: baseline; gap: 6px; }
.form-row .req { color: var(--danger); }
.form-row .hint { font-size: var(--fs-12); color: var(--c-gray); font-weight: var(--fw-light); }
.form-row input[type=text], .form-row input[type=number], .form-row input[type=password],
.form-row input[type=date], .form-row textarea {
  min-height: var(--sel-h-md); height: var(--sel-h-md); padding: 0 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-14); outline: none; width: 100%;
}
.form-row textarea { height: auto; min-height: calc(var(--sel-h-md) * 2); padding: 6px 10px; }
/* 多行文本（textarea）的**默认高度 = 普通输入框高度的 2 倍**（用户 2026-09-18 14:42 定的规范）：
   32 × 2 = **64**；上面用 `min-height` 实现 → 模板里 `rows` 给得更大时取较大值 */
/* 表单下拉：走「控件标准 · 下拉」Medium 档（高度 32 / 文字左距 16 / 箭头 12px / 箭头↔右边 12px） */
.form-row select {
  min-height: var(--sel-h-md); height: var(--sel-h-md);
  padding: 0 32px 0 var(--sel-px-md); border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background-color: var(--field-bg);
  background-image: var(--sel-arrow); background-repeat: no-repeat;
  background-position: right var(--sel-pr-md) center;
  background-size: var(--sel-caret-md) var(--sel-caret-md);
  color: var(--text); font-family: inherit; font-size: var(--sel-fs-md);
  outline: none; width: 100%; appearance: none; -webkit-appearance: none;
}
.form-row textarea { resize: vertical; }
.form-row input:focus, .form-row select:focus, .form-row textarea:focus { border-color: var(--primary); box-shadow: var(--ring); }
.form-row input[readonly] { opacity: .72; background: var(--chip); }
.form-row .is-invalid { border-color: var(--danger) !important; box-shadow: 0 0 0 3px rgba(239,68,68,.12) !important; }
/* 供货「货品信息」带出区：颜色 / 主单位 / 子单位换算 */
.form-row[hidden] { display: none !important; }        /* .form-row 是 flex，会盖掉 UA 的 [hidden] */
.form-row .gp-box { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--card-2); padding: 10px 12px; }
.gp-line { display: flex; align-items: center; gap: 10px; min-height: 34px; }
.gp-line-top { align-items: flex-start; }
.gp-k { flex: none; width: 76px; font-size: var(--fs-13); color: var(--muted); }
.gp-v { font-size: var(--fs-14); color: var(--text); }
.form-row .gp-line select { flex: 1; min-height: 34px; padding: 5px 9px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-14); outline: none; }
.form-row .gp-line select:focus { border-color: var(--primary); box-shadow: var(--ring); }
.gp-convs { flex: 1; display: flex; flex-direction: column; gap: 8px; padding-top: 3px; }
.gp-conv-row { display: flex; align-items: center; gap: 8px; }
.form-row .gp-conv-row input.gp-conv-num { width: 120px; min-height: 34px; padding: 5px 9px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-14); outline: none; }
.form-row .gp-conv-row input.gp-conv-num:focus { border-color: var(--primary); box-shadow: var(--ring); }
.gp-conv-txt { font-size: var(--fs-13); color: var(--muted); white-space: nowrap; }
/* 「供货填值」的货品字段录入行 + 供货单价后面的「主单位/币别」 */
.gp-fields { display: flex; flex-direction: column; gap: 8px; }
.form-row input.gp-fill, .form-row select.gp-fill, .form-row textarea.gp-fill { flex: 1; width: auto; min-height: 34px; padding: 5px 9px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-14); outline: none; }
.form-row input.gp-fill:focus, .form-row select.gp-fill:focus, .form-row textarea.gp-fill:focus { border-color: var(--primary); box-shadow: var(--ring); }
.form-row textarea.gp-fill { width: 100%; resize: vertical; }
.amount-wrap.has-unit .amount-input { border-radius: 0 !important; }
.gp-unit { flex: none; font-size: var(--fs-13); color: var(--muted); white-space: nowrap; }
/* 有主单位时：隐藏重复的 ¥ 前缀，输入框变成最左侧，需要左圆角 */
.amount-wrap.no-sym .amount-pre { display: none; }
.amount-wrap.no-sym .amount-input { border-radius: var(--radius-sm) 0 0 var(--radius-sm) !important; }
.gp-price-unit { display: flex; align-items: center; padding: 0 10px; height: 36px; background: var(--chip); border: 1px solid var(--field-border); border-left: none; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; color: var(--muted); font-size: var(--fs-13); white-space: nowrap; }
/* 两级分类合并呈现：「子类」下拉跟在「分类」下拉下面，仅当所选分类有子类时出现 */
.cat-sub-inline { display: flex; align-items: center; gap: 8px; }
.cat-sub-inline .cat-sub-label { font-size: var(--fs-13); color: var(--muted); flex: none; }
.cat-sub-inline select { flex: 1; }
.amount-wrap { display: flex; align-items: center; width: 100%; }
/* 货币符号块的高度**必须跟输入框同档**：原来写死 36px，而系统输入框是 `--sel-h-md`(32)
   → 符号块上下各突出 2px，与右边数字框**不齐**（用户 2026-09-18 14:27 截图问「货币符号所占高度
   为什么和输入框高度不一致？」）→ 统一走 `--sel-h-md` （换档位时一起跟着变，别再写死数值） */
.amount-pre { flex: none; padding: 0 10px; height: var(--sel-h-md); display: flex; align-items: center; background: var(--chip); border: 1px solid var(--field-border); border-right: none; border-radius: var(--radius-sm) 0 0 var(--radius-sm); color: var(--muted); font-size: var(--fs-14); }
.amount-input { flex: 1 1 auto; width: auto; min-width: 0; border-radius: 0 var(--radius-sm) var(--radius-sm) 0 !important; }
/* 金额的「币别」下拉：接在金额输入框左侧（外观与原来的 ¥ 前缀一致，但可手动改）。
   必须「窄 + 固定宽」：否则会被 `.form-row select{width:100%}`（特异性 0,1,1）
      盖掉这里的 `flex:none`（0,1,0）→ 下拉撑满整行、把右侧数字输入框挤没。故用 `.form-row .amount-cur`（0,2,0）提权。 */
.form-row .amount-cur { flex: none; width: auto; max-width: 40%; padding: 0 8px; height: var(--sel-h-md); background: var(--chip); border: 1px solid var(--field-border); border-right: none; border-radius: var(--radius-sm) 0 0 var(--radius-sm); color: var(--text); font-family: inherit; font-size: var(--fs-13); cursor: pointer; outline: none; }
.form-row .amount-cur:focus { border-color: var(--primary); box-shadow: var(--ring); }
/* 「固定币别」字段（如「客户扣款」恒为人民币）：不是下拉，就是个静态符号 —— 不可点、不看记录币别 */
.form-row .amount-cur-fixed { display: inline-flex; align-items: center; justify-content: center; cursor: default; color: var(--muted); }
/* 数字字段（百分数 % 后缀等） */
.num-wrap { display: inline-flex; align-items: center; gap: 6px; min-width: 160px; }
.num-wrap input[type=number] { flex: 1; }
/* 「带单位」字段的单位后缀（如 规格 = CM）——
   🚨 必须 `white-space: nowrap` + `flex: none`：不加的话，格子一窄 "CM" 就会被**折成竖着的两行**
   （C 一行 M 一行 —— 用户 2026-09-26 报：「CM 是被什么压到竖项了」）✗✗
   · `nowrap`：文字永不折行 ✓
   · `flex: none`：作为 flex 子项（`.num-wrap` / `.cellwrap`）时不参与收缩 ✓ */
.num-suffix { color: var(--muted); font-size: var(--fs-14); white-space: nowrap; flex: none; }
/* 🆕 「带单位」（用户 2026-09-26）：文本 / 多行文本也套一层 `.num-wrap` ⇒ 单位永远在**最后** ✓
   数字框本来就有 `flex:1`；文本 / 多行文本必须补上，否则输入框不撑满、后缀会飘在中间 ✗ */
.num-wrap > input[type=text] { flex: 1 1 auto; min-width: 0; }
.num-wrap > textarea { flex: 1 1 auto; width: auto; min-width: 0; }

/* 字段设置 · 数字格式配置 */
.num-cfg { display: flex; flex-direction: column; gap: 10px; }
/* 水平布局（.form-row.is-h）下，数字格式这类**配置块**要占满标签右边（否则整块被压到最小宽度） */
.form-row.is-h > .num-cfg,
.form-row.is-h > .num-line,
.form-row.is-h > .num-wrap,
.form-row.is-h > .num-sub,
.form-row.is-h > .date-fmt,
.form-row.is-h > .fd-opts-wrap,
.form-row.is-h > .fd-casc-wrap,
.form-row.is-h > .person-cfg,
.form-row.is-h > .fd-history,
.form-row.is-h > .auto-wrap,
.form-row.is-h > .amount-wrap { flex: 1 1 auto; min-width: 0; }
.num-line { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.num-line > select { min-width: 130px; }
.num-dec { gap: 8px; color: var(--txt); font-size: var(--fs-14); white-space: nowrap; }
.num-dec select { margin-left: 6px; min-width: 110px; }
.num-cur-label { white-space: nowrap; }
/* 「人员」字段配置（字段设置里选类型=人员时出现）：选择方式 + 显示部门 */
.person-cfg { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
/* 「历史值处理」（用户 2026-09-17 晚）：保留历史值 / 更新历史值 两个单选，横排 */
/* 「历史值处理 / 已关闭的值」两行（用户 2026-09-17 晚）：各一行、标签在左列（与上面各行右对齐同列） */
.fd-history { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; min-width: 0; }

/* ---------- 级联字段控件（用户 2026-09-17 · Cascader 规范）----------
   输入框：右侧留 28px 给箭头（箭头 12px，规范给的 icon 尺寸）；
   面板：多列联动，`padding: 4px 0`（规范 4 / 32 / 4），项高 32、字号 14、左内距 12、右侧 12px 箭头；
   面板由 JS 挂到 document.body + position:fixed（避免被弹窗 / iframe 裁掉）。 */
.casc { position: relative; display: block; min-width: 0; }
.casc-input { width: 100%; padding-right: 28px !important; cursor: pointer; }
.form-row .casc-input[readonly] { background: var(--field-bg); }
.casc-arrow {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; color: var(--muted); pointer-events: none;
}
.casc.open .casc-arrow { color: var(--primary); transform: translateY(-50%) rotate(180deg); }
.casc.is-sm .casc-input { height: 24px; font-size: var(--fs-13); }
.casc.is-lg .casc-input { height: 40px; font-size: var(--fs-16); }
.casc-panel {
  position: fixed; z-index: 400; display: flex;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 8px 28px rgba(15,23,42,.14);
  padding: 4px 0; max-height: 320px;
  width: max-content; max-width: min(760px, calc(100vw - 24px));   /* 按内容撑开（规范图：面板比输入框宽） */
}
.casc-col { min-width: 92px; max-width: 240px; overflow-y: auto; overflow-x: hidden; border-right: 1px solid var(--line); }
.casc-col:last-child { border-right: none; }
.casc-item {
  display: flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 12px; font-size: var(--fs-14); color: var(--text);
  cursor: pointer; white-space: nowrap;
}
.casc-item .casc-txt { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
.casc-item .casc-ico { flex: none; width: 12px; height: 12px; }
.casc-item:hover { background: var(--bg-list); }
.casc-item.on { background: var(--primary-soft); color: var(--primary); font-weight: var(--fw-medium); }
.casc-empty { height: 32px; padding: 0 12px; display: flex; align-items: center; font-size: var(--fs-14); color: var(--muted); }
/* 「支持搜索」开关打开时（关联字段 + 级联取值）：每一列顶部一个**吸顶**搜索框 ——
   任一级都能模糊过滤；列本身是滚动容器（overflow-y:auto），sticky 正好粘在列顶 ✓ */
.casc-sbox { position: sticky; top: 0; z-index: 2; background: var(--card); padding: 4px 6px 6px; border-bottom: 1px solid var(--line); }
.casc-sinp {
  width: 100%; height: 28px; padding: 0 8px; font-size: var(--fs-13);
  color: var(--text); background: var(--field-bg); border: 1px solid var(--line);
  border-radius: 0; outline: none;
}
.casc-sinp:focus { border-color: var(--primary); background: var(--card); }
.casc-none { height: 28px; padding: 0 12px; display: flex; align-items: center; font-size: var(--fs-13); color: var(--muted); }

/* 字段设置弹窗里「级联选项」的树编辑器：缩进代表层级 */
.fd-casc-wrap { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-width: 0; width: 100%; }
.fd-casc-list { display: flex; flex-direction: column; align-items: stretch; gap: 6px; width: 100%; }
.fd-casc-row { display: flex; align-items: center; gap: 8px; }
.form-row .fd-casc-row .fd-casc-name {
  flex: 1 1 auto; min-width: 0; height: var(--sel-h-md); padding: 0 10px;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-14); outline: none;
}
.fd-casc-btns { display: flex; align-items: center; gap: 8px; }
.person-line { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
/* 「选择方式」四个字已删（用户 2026-09-17）→ 去掉左间距；下拉**按内容宽度、不撑满整行**
   （撑满会把右边的「显示部门」挤到第二行 ）。选择器带 `.form-row` 前缀才压得住通用的 `.form-row select` */
.form-row .person-line > select { min-width: 190px; width: auto; flex: none; }
/* 人员字段「限制范围」（用户 2026-09-17 改版）：行里只放「选择部门 / 人员」按钮 + 已选摘要，
   候选项列表在**弹窗** `#fdScopeModal` 里勾选（用户：不是让你展示所有部门和人员的，而是弹窗选择） */
.person-scope-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; width: 100%; }
/* 写了 display:flex 就必须补这条：否则 `hidden` 属性会被 display:flex 覆盖、永远关不掉 */
.person-scope-line[hidden] { display: none; }
.ps-summary { font-size: var(--fs-13); color: var(--text); word-break: break-all; }
.ps-summary:empty { display: none; }                     /* 没选 → 整段不占位、不显示任何文字 */
.ps-summary.is-err { color: var(--danger); font-weight: var(--fw-bold); }   /* 开了没选就保存 → 红字拦住 */
/* 弹窗里的两列（部门 / 人员） */
.scope-cols { display: flex; gap: 16px; flex-wrap: wrap; }
.ps-group { flex: 1 1 240px; min-width: 0; }
.ps-head { font-size: var(--fs-13); color: var(--muted); margin-bottom: 6px; }
.ps-list {
  max-height: 280px; overflow-y: auto; padding: 8px 10px;
  border: 1px solid var(--line); background: var(--field-bg);
  display: flex; flex-direction: column; gap: 6px;
}
.ps-list .sw-inline { font-size: var(--fs-13); }
.num-sub { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 8px 10px; border: 1px dashed var(--line); border-radius: var(--radius-sm); background: var(--card-2); }
/* 「限制范围」（用户 2026-09-17）：[最小值] ≤ X ≤ [最大值] */
.num-range { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.num-range .num-range-inp { width: 110px; }       /* 特异性高于 .form-row input[type=number] 的 width:100% */
.num-range-op { font-size: var(--fs-13); color: var(--muted); white-space: nowrap; }
/* 背景框里内容是**多行**时用它（如日期「显示方式」）：纵向铺满，不居中堆在左上 */
.num-sub-col { flex-direction: column; align-items: stretch; gap: 10px; flex-wrap: nowrap; }
.num-color-label { margin-right: 4px; }
.color-pick { display: inline-flex; gap: 10px; flex-wrap: wrap; }
.color-opt { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: var(--fs-14); padding: 3px 8px 3px 4px; border: 1px solid transparent; border-radius: 0; }
.color-opt:hover { border-color: var(--line); }
.color-opt input { accent-color: var(--primary); }
.color-dot { width: 14px; height: 14px; border-radius: 0; display: inline-block; box-shadow: inset 0 0 0 1px rgba(0,0,0,.12); }
.form-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
/* 可搜索下拉里的**副文本**（关联字段的辅助字段，如供应商简称）—— 只在候选行里当灰色小字辨识，
   **不参与回填**（选中后格子里只显示主字段值 ✓ 用户 2026-09-26）*/
/* 候选行 = **多列（像表格）**：主字段（没拼接时即输出值）+ 辅助字段 1/2…（只作选择参考 ✓）
   用户 2026-09-26：「主字段 辅字段1 辅字段2，就像一个表格一样选择」*/
/* 列数自适应（辅助字段可能 1 个也可能多个，不能写死列数）
   必须用两个类 .ssel .ssel-opt：文件后面还有一条单类 .ssel-opt 会压过来；
   并显式 overflow:visible，否则那条的 overflow:hidden 会把副列裁掉 */
.ssel .ssel-opt { display: flex; align-items: center; gap: 12px; overflow: visible; }
/* 关联字段的可搜索下拉（.sup-ssel）：候选项前面给个**选中圆圈** ——
   与详情页 / 表单的关联控件（radio）保持一致（用户 2026-09-26：「前面为什么没有那个选中的圆圈呢」）；
   只作用于 .sup-ssel ⇒ 普通下拉（单位 / 状态…）不受影响 ✓
   未选中＝空心圆，选中＝主色实心 ✓ 标题行也留同宽占位，保证列与标题对齐 ✓ */
.sup-ssel .ssel-opt { gap: 8px; }
.sup-ssel .ssel-opt::before { content: ''; flex: 0 0 auto; width: 13px; height: 13px; border-radius: 50%;
  border: 1px solid var(--line-strong); background: var(--card); }
.sup-ssel .ssel-opt.is-on::before { border-color: var(--primary); background: var(--primary);
  box-shadow: inset 0 0 0 3px var(--card); }
.sup-ssel .ssel-head::before { content: ''; flex: 0 0 auto; width: 13px; }
.ssel .ssel-opt .ssel-main { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 详情页 / 表单的关联控件（.rel-field）候选行也用**同一套多列口径** ✓ 用户 2026-09-26 */
/* ⚠️ 写成 .rel-field .ms-opt.rel-opt（三个类）：实测单类 .rel-opt 会被既有规则压成 flex ✗
   （老坑：元素"改不动 / 没生效"先数选择器类数 ✓）*/
/* 列数**自适应**（辅助字段可能多个 ⇒ 用 flex 而非写死网格列 ✗ 用户 2026-09-26）；
   注意必须写三个类，否则会被既有规则压掉（实测被压成 flex 且列宽不对 ✗）*/
.rel-field .ms-opt.rel-opt { display: flex; align-items: center; gap: 12px; overflow: visible; }
.rel-field .ms-opt.rel-opt .rel-main { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rel-field .ms-opt.rel-opt .rel-sub { flex: 0 0 auto; min-width: 72px; color: var(--muted); font-size: var(--fs-12); white-space: nowrap; }
/* 关联控件面板顶部的**列标题行**（冻结）✓ */
.rel-cols-head { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 12px;
  padding: 6px 10px; background: var(--card-2); border-bottom: 1px solid var(--line);
  font-size: var(--fs-12); color: var(--muted); }
.rel-cols-head > span:first-child { flex: 1 1 auto; min-width: 0; }
.rel-cols-head > span + span { flex: 0 0 auto; min-width: 72px; }
/* 副列固定最小宽，各行辅字段竖着对齐；没值的行留空但仍占位 */
.ssel .ssel-opt .ssel-sub { flex: 0 0 auto; min-width: 72px; color: var(--muted); font-size: var(--fs-12); white-space: nowrap; }
/* 面板顶部的列标题行（sticky 冻结）：主字段名 + 各辅助字段名 */
.ssel-head { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 12px;
  padding: 6px 10px; background: var(--card-2); border-bottom: 1px solid var(--line);
  font-size: var(--fs-12); color: var(--muted); }
.ssel-head > span:first-child { flex: 1 1 auto; min-width: 0; }
.ssel-head > span + span { flex: 0 0 auto; min-width: 72px; }
/* 「已选 N 条」这类**贴着按钮的计数**：与按钮同一行、上下居中（用户 2026-09-26：
   「已选2条，为什么是一个小字？应该和确认导入按钮一行，上下居中」）——
   · 以前用 `.pm-hint`（12px 灰 + flex 默认 stretch ⇒ 文字贴行顶），看着像脚注 ✗
   · 现在高度与字号都取按钮的值（`--btn-h-md` / `--btn-fs-md`）⇒ 天然与按钮齐平 ✓
   · 只换这一处；其它弹窗底部同款小字要不要一起对齐，等用户拍板 ✓ */
.cnt-inline { display: inline-flex; align-items: center; align-self: center; height: var(--btn-h-md); font-size: var(--btn-fs-md); color: var(--text); }
.cnt-inline b { font-weight: var(--fw-medium); }
/* 字段设置弹窗：表单行之间按标准行距 24 排（该表单不是网格布局，行距由容器 gap 给） */
#fdForm { display: flex; flex-direction: column; gap: var(--form-row-gap); }

/* ---------- 弹窗 ---------- */
.modal-scrim, .to-modal {
  position: fixed; inset: 0; background: rgba(15,23,42,.45); z-index: 90;
  display: none; align-items: center; justify-content: center; backdrop-filter: blur(2px);
}
.modal-scrim.open, .to-modal.open { display: flex; }
.modal {
  /* 原来写的是 `inset: 0; margin: auto` —— 上下都锚到 0 时，`height: auto` 会被解析成
     **填满视口**（height 反而不再是内容高）→ 弹窗永远是满高、按钮下面留白 。
     改成「左上角 50% + translate 居中」→ 高度才能真正随内容走 （配合 fitModalFrame）*/
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 95;
  /* 宽度（用户 2026-09-18 14:11 定「输入框统一 350」，14:47 又定「内容距弹窗边缘 24」）→
     由这两条**一起**反推（一行 2 个 + 上下输入 + 卡片左右内距 6 的规范下）：
       350 ×2(列) + 18(列距) = 718  →  + 卡片内距 6×2 = 730  →  + 卡片框线 1×2 = 732
       →  + 弹窗外层内距 18×2 = **768**
     （内容距弹窗边缘 = 18 + 1 + 6 = 25 ≈ 24，输入框正好 350，两条都满足）
     历史：2026-09-15 曾要求「加一倍」→ 1280（那时是一行 3 个 + 左右布局）；14:11 按 350 反推成 804。 */
  /* 高度：**随内容自适应**（用户 2026-09-18 14:51：「最后一个输入控件底部 ↕ 下方分割线 = 24」）——
     iframe 的真实高度由 `public/js/app.js` 的 `fitModalFrame()` 量出来写进 inline style，
     这里只留上限：超过 `86vh / 760px` 就让 iframe 内部滚动（操作栏 sticky 吸底）*/
  width: min(768px, 94vw); height: auto; max-height: min(86vh, 760px);
  background: var(--card); border-radius: var(--radius-lg); box-shadow: var(--shadow-hover);
  display: none; flex-direction: column; overflow: hidden;
}
.modal.open { display: flex; }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--line); }
.modal-title { font-size: var(--fs-16); font-weight: var(--fw-bold); }
.modal-close { background: none; border: none; font-size: 20px; color: var(--muted); cursor: pointer; line-height: 1; padding: 2px 6px; }
.modal-close:hover { color: var(--danger); }
/* iframe 高度不再吃 flex 剩余空间（那样与「弹窗高度由内容决定」循环依赖）→ 由 fitModalFrame() 显式给值 */
.modal-frame { flex: 0 0 auto; border: none; width: 100%; }
/* 「采购详情单」弹窗 = **大窗**（用户 2026-09-22：「单据详情是打开弹窗查看。采购详情单。」）——
   要装下**整张**采购处理详情页（左侧导航 + 卡片 + 明细表）⇒ 宽 1360 / 高 92vh，
   并让 iframe **填满**弹窗（`!important` 压掉 fitModalFrame() 写进来的内联高度，否则内容会被 overflow:hidden 裁掉、
   而 iframe 自己的滚动条又在视口外 ⇒ 里外都滚不动 ）*/
.modal.is-doc { width: min(1360px, 96vw); height: 92vh; max-height: 92vh; }
.modal.is-doc .modal-frame { flex: 1 1 auto; height: 100% !important; min-height: 0; }
.embed-body { background: var(--card); padding: 18px; }
.modal-form { max-width: 100%; }
/* ============================================================
   【弹窗字段区规范 · 完整版】用户 2026-09-18 定稿
   ① 布局：**一行 2 个字段**（原 3 个 → 14:02 改）
   ② 形态：**上下输入** —— 字段标题在上、输入框在下（弹窗不再有专属水平布局）
   ③ 列与列（字段与字段）：**上下间距 16**、左右列距 18
   ④ 字段标题 ↔ 字段输入框：**上下间距 8**
   ⑤ 字号/字重：区块名称标题 **16 加粗**（距卡片左边界 24）；字段标题 **14 + 正文黑**
   ⑥ 卡片左右内距 **24**；弹窗宽度 **804**（由输入框 350 反推，推导见 `.modal` 上方注释）
   · 作用域：**只在弹窗（iframe）内生效** —— `body.embed-body` 只有 embed 版页面才有；
     整页编辑 / 详情页不在此作用域 → 仍是标准 24 行距、13px 灰字段名
   · 网格必须包在 `.modal-grid` 上，**不要直接给 `<form>` 加 display:grid** ——
     那样 `.form-actions` 会变成 grid item，`position:sticky` 只在它自己的 grid area 内滚动 → **吸底失效**
   · `.gp-full`（如「货品信息」带出区）整行跨列
   ============================================================ */
body.embed-body .modal-form .modal-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px 18px;                                  /* ③ 字段与字段：上下 16 / 左右 18 */
  align-items: start;
}
body.embed-body .modal-form .modal-grid > .form-row { margin-top: 0; }
body.embed-body .modal-form .modal-grid > .form-row.gp-full { grid-column: 1 / -1; }
/* 弹窗字段跨行规则（用户 2026-09-18 14:45）：
   「默认**多行文本**字段独占弹窗 1 行、**下拉多选**字段独占 1 行」→ 这两类**整行跨列**（占满 2 列 = 718px）
   用模板传下来的 `data-ftype` 精确匹配类型，**不要**用 `:has(textarea)` / `:has(.ms-field)` ——
      后者会把「标签 / 人员多选 / 关联多选」（同样用 `.ms-field` 渲染）一起拉成整行
   （模板：`entity/form.ejs`、`orders/form.ejs` 的 `<div class="form-row" data-ftype="...">`） */
body.embed-body .modal-form .modal-grid > .form-row[data-ftype="textarea"],
body.embed-body .modal-form .modal-grid > .form-row[data-ftype="multi_select"] { grid-column: 1 / -1; }
/* ④ 字段标题 ↔ 输入框 上下 8 —— 与全局 `--form-lb-v` 同值，这里**显式写出来**，
   让这套弹窗规范自成一套、不依赖外部变量（改弹窗不用去翻 :root） */
body.embed-body .modal-form .form-row { gap: 8px; }
/* 弹窗里**单列表单**（如「调整单价」，容器是 .edit-form 而不是网格）的行距也统一 16，
   免得同一个弹窗体系里两种行距（网格 16 / 单列 24）看起来不统一 */
body.embed-body .modal-form .edit-form { gap: 16px; }
/* ⑤-a 区块名称标题 = 16px 加粗：
   必须带 `.card` 前缀提权 —— `.card h2`（0,1,1）会盖掉 `.rel-title`（0,1,0）的 16px，
      弹窗里原来**实际是 18px**（用户看到的就是这个）→ 这里按规范压回 16 */
body.embed-body .modal-form .card > h2 { font-size: var(--fs-16); font-weight: var(--fw-bold); }
/* ⑤-a 补充「标题 / 内容距**弹窗边缘** 24」（2026-09-18 14:47 用户再次明确：「默认状态下，这个间距也是 24」）→
   卡片左右内距 = **6**：`body.embed-body` 内距 18 + 卡片 1px 边框 + 6 = **25 ≈ 24**
   这里给 6 而不是 24 是为了**同时**满足两条硬约束：
      · 内容（标题 / 字段 / 明细表）距弹窗边缘 24
      · 字段列宽（= 输入框宽度）正好 **350**
     验证：弹窗宽 768 − 36(embed 内距) − 2(边框) − 12(卡片内距) − 18(列距) = 700 → 700 ÷ 2 = **350** */
body.embed-body .modal-form .card { padding-left: 6px; padding-right: 6px; }
/* ⑤-b 字段标题 = 14px + 正文黑（原来是 13px 的 --muted 灰）
   提示小字 `.hint` 仍是 12px 灰（不动，它是辅助信息不是标题） */
body.embed-body .modal-form .form-row > label { font-size: var(--fs-14); color: var(--text); }
/* ---------- 弹窗里**只有 1 个区块**时：不要区块标题、不要卡片外框（用户 2026-09-18 14:15）----------
   「当弹窗中只有 1 个区块的时候，就不需要这个区块再出现 1 个单独的卡片了 … 内容区域只有 1 张卡片」
   · 标题那一层由模板负责：`entity/form.ejs` / `orders/form.ejs` 的 `_singleCard` 为真时**不渲染 `<h2>`**
     （口径 = 按实际渲染的字段分桶，只有 1 桶才算「只有一个区块」）
   · 这里只去掉卡片的**边框 / 阴影 / 底色** → 视觉上字段直接铺在弹窗里，不再「卡套卡」
   · **必须保留 1px 边框占位 + 24 内距**：去掉它们内容会宽出 50px → 输入框就不是 350 了 */
body.embed-body .modal-form .card.card-bare {
  background: transparent; border-color: transparent; box-shadow: none;
}
/* 「只有 1 张卡片」时：**弹窗顶部 ↕ 第一个字段 = 24**（用户 2026-09-18 14:37 圈出这段空白要「默认 24」）
   24 = `body.embed-body` 上内距 18 + 卡片补的 6。
   只能动**上**内距 —— 左右 24 不能碰（那是「输入框正好 350」的推导基础，见 `.modal` 注释）；
      下内距 18 也不动（它和操作栏的 `margin-top: 14` 一起构成到按钮行的留白）。 */
body.embed-body .modal-form .card.card-bare { padding-top: 6px; padding-bottom: 0; }
/* 弹窗字段**不再有**「弹窗专用水平布局」（原 `--form-lb-w:112px` / is-h 那三条，2026-09-18 14:02 随新规范删除）——
   弹窗字段一律「上下输入」；`.is-h` 只服务于**字段设置弹窗**（`views/admin/fields.ejs`）里那些标签很短的配置行 */
@media (max-width: 760px) { body.embed-body .modal-form .modal-grid { grid-template-columns: 1fr; } }
.to-panel { background: var(--card); border-radius: var(--radius-lg); box-shadow: var(--shadow-hover); overflow: hidden; }
/* 左右内距 24 = 内容区 16 + 滚动条槽 8（见下面 `.to-body` 的 `scrollbar-gutter`）——
   标题 / 关闭按钮要和内容块的左右边缘对齐，别各写各的（用户 2026-09-17 晚：弹窗两侧间隙不对） */
/* 标题栏**统一 48 高**（用户 2026-09-18 19:31：「转入订单弹窗的外框标准不对」）——
   原来是 `padding:12px 24px` 让标题行高度随内容走：里面的关闭控件若是 `<a>`（行高 27）→ 头部变成 **54**，
   而 `<button>` 那些弹窗是 48 → 同一个系统里两种标题栏高度 。
   现在：`min-height:48 + 上下内距 0 + 垂直居中` → 一律 48（内容更高时仍可撑开，不会截断）*/
.to-head { display: flex; align-items: center; justify-content: space-between; min-height: 48px; padding: 0 24px; border-bottom: 1px solid var(--line); font-weight: var(--fw-bold); }
/* 关闭控件定成 24×24 的居中盒子（`<a>` / `<button>` 两种写法渲染出的高度才一致）*/
.to-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; line-height: 1;
  background: none; border: none; padding: 0; font-size: 18px; color: var(--muted); cursor: pointer;
}
.to-body { padding: 16px; }

/* ---------- 弹窗里的操作栏（取消 / 保存）：标准（用户 2026-09-18 14:34）----------
   「取消保存按钮所在行**高度固定 52**」「上方分割线默认**贯穿弹窗宽度**」
   · **仍是吸底**（2026-09-15 定的：按钮在弹窗最底部始终可见，只有内容区滚动）
   · 52 = 按钮 32 + 上下各 10（按钮垂直居中）→ 换按钮档位时要重算这个数
   · 分割线要**从弹窗左边缘贯到右边缘** → 操作栏用**负外边距**吃掉容器的左右内距
     （iframe 弹窗 = `body.embed-body` 的 18px；同页弹窗 = `.to-body` 的 16px），
     再把这份内距用 padding 还给按钮 → 按钮位置不变、线却贯穿
   · iframe 弹窗（货品 / 供应商 / 供货 / 订单 / 调整单价…）：内容 = iframe 里的整篇文档
     → 用 `.modal-form .form-actions`（`.modal-form` 没有 overflow，滚动容器就是 iframe 视口 ）
   · 同页弹窗（字段设置 / 权限设置 / 顶栏「字段显示」）：内容 = `.to-body`
     → `.to-body` 自己当滚动容器（外层 `.to-panel` 有 `overflow:hidden`，位它下面吸底会失效）
   吸底靠 `position:sticky`：中间任何祖先只要有 `overflow:hidden` 且**不可滚动**，吸底就会失效 —— 改布局时留意。 */
.modal-form .form-actions,
.to-body .form-actions,
.to-body .fv-actions {
  position: sticky; bottom: 0; z-index: 6;
  height: 52px; min-height: 52px;      /* 固定 52（按钮 32 + 上下各 10）*/
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 0;
  background: var(--card);
  border-top: 1px solid var(--line);
  margin-top: 14px;
}
/* 分割线贯穿弹窗：iframe 弹窗（容器 `body.embed-body` 左右内距 18） */
body.embed-body .modal-form .form-actions { margin-left: -18px; margin-right: -18px; padding-left: 18px; padding-right: 18px; }
/* ---------- 「最后一个输入控件底部 ↕ 下方分割线 = 24」（用户 2026-09-18 14:51）----------
   这段距离上原本**同时**有几个来源在叠加，所以要逐个收拢，只留一个 24：
     ① `.edit-form` 的 `gap`（16）——它作用在「卡片 ↔ 操作栏」之间（不只是字段行之间）；
     ② 卡片模板里写死的 `style="margin:0 0 12px"`；
     ③ `card-bare` 的下内距（曾给 10）。
   做法：弹窗里 `.edit-form` 改成**相邻兄弟行距**（`> * + *` 16，效果与原来的 gap 相同），
   唯独**操作栏**用 24；卡片下外边距清零、卡片下内距归 0 → 净间距就正好是操作栏那 24（+1px 分割线）
   只在 iframe 弹窗作用域（`body.embed-body .modal-form`）里生效，整页表单不受影响。 */
body.embed-body .modal-form .edit-form { gap: 0; }
body.embed-body .modal-form .edit-form > * + * { margin-top: 16px; }
body.embed-body .modal-form .edit-form > .form-actions { margin-top: 24px; }
body.embed-body .modal-form .card { margin-bottom: 0 !important; }
body.embed-body .modal-form > .form-actions,
body.embed-body .modal-form form > .form-actions { margin-top: 24px; }
/* 分割线贯穿弹窗：同页弹窗（容器 `.to-body` 左右内距 16 **+ 两侧滚动条槽 8** = 24；
   必须连 8px 的槽一起吃掉，否则线离左右边各差 8px ——
   `.to-body` 有 `scrollbar-gutter: stable both-edges`，所以元素撑到 24 正好等于容器宽、不会溢出 ；
   顺带：按钮右边缘 = 距弹窗右边 24，正好与标题行 `.to-head`（padding 12px 24px）对齐 */
.to-body .form-actions,
.to-body .fv-actions { margin-left: -24px; margin-right: -24px; padding-left: 24px; padding-right: 24px; }
/* 有吸底操作栏时，**滚动容器底部不能再有 padding**：
   否则滚到底时 sticky 回到「自然位置」，正好比底边高出一个 padding —— 看起来就是「按钮往上跳」
   （用户 2026-09-15 报的正是这个：货品/供应商跳 18px、订单跳 36px）。留白交给操作栏自己的 padding。 */
.embed-body:has(.form-actions) { padding-bottom: 0; }
/* 这里原来还有「撑满一屏（min-height:100vh）+ 操作栏 margin-top:auto 贴底」那一组规则 ——
   **2026-09-18 14:51 已移除**：用户要求「最后一个输入控件底部 ↕ 下方分割线 = 24」，
   而"贴底"必然把这段距离变成弹性空白（实测订单弹窗 110px）；
   两条要求只有**弹窗高度随内容自适应**时才能同时成立 →
   改由 `public/js/app.js` 的 `fitModalFrame()` 量 iframe 内容高度后设 iframe 高度，
   `.modal` 变成 `height:auto; max-height:...`，操作栏自然紧跟内容、下面也不留空白
   （内容超出上限时 iframe 内部滚动，操作栏 `sticky bottom:0` 照旧吸底） */
.to-body:has(.form-actions),
.to-body:has(.fv-actions) { padding-bottom: 0; }
/* 同页弹窗的内容区自己滚动（原来内容一高，底部按钮就够不到） */
/* 两侧间隙必须**对称**（用户 2026-09-17 晚：内容高 → 出现滚动条时右侧被吃掉 15px，
   左边 16、右边 31，一眼就看出歪 ）：
   · `scrollbar-gutter: stable both-edges` → 两侧都预留滚动条槽，内容始终居中；
   · 同时把滚动条改成 8px 细条（默认 Windows 滚动条 15px，在 520px 宽的弹窗里又粗又难看）。 */
.to-body { max-height: min(78vh, 640px); overflow-y: auto; scrollbar-gutter: stable both-edges; }
.to-body::-webkit-scrollbar { width: 8px; }
.to-body::-webkit-scrollbar-track { background: transparent; }
.to-body::-webkit-scrollbar-thumb { background: var(--line-strong); }

/* ---------- 列表「字段显示」设置弹窗（顶栏齿轮，按用户生效） ---------- */
.fv-tip { line-height: 1.7; margin: 0 0 10px; }
.fv-list {
  /* **不要再给它 max-height + overflow**：外层 `.to-body` 已经是滚动容器（78vh/640px）——
     两个滚动容器会变成「右侧双层滚动条」（用户 2026-09-15 报「字段显示设置怎么变成了双层右侧滑动」）。
     只让 `.to-body` 滚，`.fv-actions` 才能吸底。 */
  list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.fv-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  border-bottom: 1px solid var(--line); background: var(--card);
}
.fv-item:last-child { border-bottom: none; }
.fv-item { touch-action: none; }
.fv-item.dragging { opacity: .5; background: var(--bg-list); }
.fv-handle { display: inline-flex; flex-shrink: 0; color: var(--muted); cursor: grab; }
.fv-handle:active { cursor: grabbing; }
.fv-name {
  flex: 1; min-width: 0; font-size: var(--fs-14); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fv-item.is-off .fv-name { color: var(--muted); }
.fv-item .switch { flex-shrink: 0; }
/* 「冻结」按钮（用户 2026-09-18 15:1x：「冻结是个**按钮**，不是你的雪花符号」）——
   每一行末尾一个**普通文字按钮**，用全站标准按钮类：`.btn .btn-xs .btn-ghost`（未冻结）/
   已冻结时 JS 换成 `.btn-primary` + 文案「已冻结」，再点一次取消
   （只允许 1 个：点别的字段会顶掉前一个） */
.fv-freeze { flex: none; }
/* 「字段设置里已关闭」的字段（用户 2026-09-18 规则：**字段设置 > 个人显隐 / 列设置**）——
   行仍然列出来（不让它凭空消失，否则用户会以为字段丢了），但**整行置灰 + 开关禁用**，
   名字后加一句原因（`.fv-lock`）。个人怎么设都开不出来 （后端也强制 on=false） */
.fv-item.is-locked { opacity: .6; }
.fv-item.is-locked .switch { cursor: not-allowed; }
.fv-lock { margin-left: 6px; font-style: normal; font-size: var(--fs-12); color: var(--muted); }
/* 「转入带出字段」= 带表头的列表（用户 2026-09-17 深夜：「给这些字段弄个标题，有字段名、字段类型、
   转入、编辑支持、原值处理」）：
   · 列名统一放在**表头行**（观感与上面「自定义字段」表格的表头一致：黑字 14 加粗 + 品牌浅靛蓝底）；
   · 表头与数据行**共用同一套 .fv-cell / .fv-name 类与同一组宽度** → 列天然对齐，
     表头文字严格落在自己的控件上方（要调列宽只改这一处）
   · 行内不再重复写「转入 / 支持编辑 / 原值处理」小标签（表头已经说明了） */
.fv-head {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px;
  background: var(--tint-primary); border-bottom: 1px solid var(--line-strong);
  font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--text);
}
.fv-list .fv-cell { display: flex; align-items: center; gap: 6px; flex: none; }
.fv-list .fv-cell-type { min-width: 110px; font-size: var(--fs-14); color: var(--text); }
.fv-list .fv-cell-carry { min-width: 74px; }
.fv-list .fv-cell-edit { min-width: 108px; }
.fv-list .fv-cell-keep { min-width: 178px; }
.fv-list .fv-cell-keep select {
  height: 28px; min-height: 28px; width: 108px; padding: 0 6px;
  font-size: var(--fs-13); color: var(--text);
}
/* 「不转入」的行：字段名与类型一起变灰，一眼看出哪些不带出 */
.fv-item.is-off .fv-cell-type { color: var(--muted); }
/* 表头第一格 = 拖拽手柄的占位（与行内手柄同宽 → 后面各列不错位） */
.fv-head .fv-handle-ph { width: 14px; height: 14px; }
/* 「有来源单号才能保存」开关（用户 2026-09-17 深夜）：在「转入带出字段」标题行的**右侧**
   —— 打开 = 必须有来源单据才能保存；关闭 = 没有来源单号也能直接新建 */
.fv-tit { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.fv-tit h2 { flex: 1; margin: 0 0 14px; }
.fv-req { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; }
.fv-req-lb { font-size: var(--fs-14); color: var(--text); }
.fv-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
/* 「转入表」选择行（用户 2026-09-17 晚）：下拉 + 「读取列」按钮 + 已读取字段数
   —— 放在「转入带出字段」卡片顶部，下面用一条分隔线把它与候选字段列表分开 */
.fv-src { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0 0; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.fv-src-lb { display: flex; align-items: center; gap: 8px; font-size: var(--fs-14); color: var(--text); }
/* ⚠️ 标签里的文字**绝不允许折行**（用户 2026-09-26：『名称两个字被压成竖项显示了』）——
   行内容总宽超容器时，flex 会把标签压窄 ⇒ 两个汉字竖排 ✗ ⇒ 标签 nowrap + 不收缩，
   让长说明（.pm-hint）去换行承担宽度 ✓ */
.fv-src .fv-src-lb { white-space: nowrap; flex: none; }
.fv-src .fv-src-lb > span { flex: none; }

/* 可搜索下拉（「searchSelect.js」，采购明细选产品/供应商用的同一个组件）——
   「产品品类」引用字段要用它（用户 2026-09-26：「我无法搜索筛选」⇒ 普通下拉不达标 ✗）——
   这里按**表单标准尺寸**落地（--sel-h-md = 32 / fs-14 ✓），与同一行的其它控件等高 ✓ */
.ssel { position: relative; display: block; width: 100%; }
.ssel .ssel-inp {
  width: 100%; box-sizing: border-box;
  height: var(--sel-h-md); min-height: var(--sel-h-md); padding: 0 10px;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-14); text-align: left;
}
.ssel .ssel-inp:focus { outline: none; border-color: var(--primary); }
.ssel.is-open .ssel-inp { border-color: var(--primary); }
.ssel-panel { max-height: 260px; overflow: auto; padding: 2px 0; background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow-lg); }
.ssel-opt { padding: 6px 10px; font-size: var(--fs-14); color: var(--text); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ssel-opt:hover, .ssel-opt.is-hot { background: var(--chip, #eef2f7); }
.ssel-opt.is-on { color: var(--primary); font-weight: 600; }
/* 级联态：每一级一个可搜索下拉，横向排 ✓ */
.catref-chain { display: flex; gap: 8px; flex-wrap: wrap; }
.catref-chain .ssel { flex: 1 1 160px; }
.fv-src select { height: var(--sel-h-md); min-height: var(--sel-h-md); min-width: 170px; }
/* 「左侧品类」那张卡里的**文本输入框**也要与下拉同高同宽（用户 2026-09-26：控件要标准 ✓）*/
.fv-src input.inp { height: var(--sel-h-md); min-height: var(--sel-h-md); min-width: 170px; }
/* 系统下拉的**实际渲染高度**比输入框多 2px（它没走 border-box，32 + 上下边框 = 34 ✗）——
   这一行里两者并排，必须**视觉等高**（用户 2026-09-26：「都不标准」）⇒ 给下拉触发器补 border-box ✓ */
/* ⚠️  自带 `min-height: 34px` ⇒ 只写 box-sizing 压不住它 ✗（实测仍 34）
   ⇒ 这一行里要连 min-height 一起覆盖，才能与输入框（系统标准 32 = --inp-h-md）等高 ✓ */
.fv-src .csel-trigger,
.fv-src select.filter-select {
  box-sizing: border-box;
  min-height: var(--sel-h-md);
  height: var(--sel-h-md);
}
.gc-wrap { display: flex; gap: 12px; align-items: flex-start; }
.gc-icon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 0; background: var(--danger-soft); color: var(--danger); }
/* 通知型弹窗的图标（用户 2026-09-25）：**绿色对勾**（与 `.badge.is-success` 同色值）——
   由 `app.js` 的 `openNotice()` 给 `.gc-icon` 加 `.is-ok` */
.gc-icon.is-ok { background: rgba(16,185,129,.12); color: #10B981; }
.gc-icon svg { width: 20px; height: 20px; }
.gc-msg { font-size: var(--fs-14); }
.gc-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }

/* ---------- 详情页 ---------- */
.detail-card { margin-bottom: 16px; }
.detail-name { font-size: var(--fs-18); font-weight: var(--fw-bold); color: var(--primary-dark); margin-bottom: 14px; display: flex; align-items: center; flex-wrap: wrap; }
[data-theme="dark"] .detail-name { color: var(--text); }
/* 详情页卡片字段布局标准（用户 2026-09-18 15:32）：**一行 4 个字段 + 每个字段左右布局**
   · 字段名（列名）**右对齐**、占固定列宽 `--dt-lb-w`（默认 96，与表单水平布局同档）
   · 字段名 ↔ 内容 **间隔 16**；内容占满剩余宽度
   · **上下两行控件之间也 = 16**（用户 2026-09-18 15:38「上下控件间隔16」）
   · **形态与「订单详情」完全一致**（用户 2026-09-18 16:17：「货品 / 供应商 / 供货 / 采购 / 出货 / 生产详情（灰底块），
     这个用订单详情的标准」）→ **去掉灰底块**，改成和 `.kv-item` 一样的「无底色、无内距、无边框的普通行」：
       行高由内容撑（14px × 1.45 ≈ 20.3）、值不再加粗（`--fw-regular`，与订单详情一致）、行间无分割线。
     （改前是 `background: var(--bg-list)` + `padding: 10px 12px` + 值 `--fw-bold`）
   · **一行 4 个**（用户 2026-09-18 15:32 定，别动列数）—— 列名加宽到 168 后每列内容自然变窄，
     属预期；网格列数**不参与**本次调整
   · 窄屏逐级降列（4 → 3 → 2 → 1），免得挤成一团 */
.detail-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.detail-item {
  /* 列名 ↔ 内容 **16 → 8**（用户 2026-09-18 16:54：「把字段名称与输入内容之间的间隔降为现在的一半」）*/
  display: flex; align-items: center; gap: var(--dt-gap); min-width: 0; padding: 0; background: none; border-radius: 0;
  min-height: 32px;   /* 与「订单详情」字段行同高（用户 2026-09-18 16:26：订单详情点编辑时
                         卡片不能动 → 那边只读行改成 32 与输入框同高；这里同步，否则同一站里
                         两套详情页行高不一致（20 vs 32）） */
}
/* **详情页就地编辑**（供应商 / 供货，用户 2026-09-18 16:43「详情页编辑状态，你又不按照统一标准执行了」）——
   与「订单详情」同一口径：卡片形态 / 一行 4 个 / 列名宽 168 全不变，只有「值那一格」变控件：
   · 行高仍是 32（`.detail-item` 的 min-height，与只读态同高 → 切换时卡片不动 ）
   · 控件占满值那一格；日期 / 时间控件的全局 `max-width:220` 在这里没影响（值区本来就 109）
   · 只读控件（自动编号 / 公式 / 查找引用）淡一点，跟只读态文字区分开 */
.detail-item.is-edit .di-value.form-row { align-self: center; }
.detail-item.is-edit .di-value.form-row .dp-field,
.detail-item.is-edit .di-value.form-row .tp-field { max-width: none; }
.detail-item.is-edit .di-value.form-row input[readonly] { opacity: .72; background: var(--chip); }
.detail-item.is-edit .di-value.form-row .tag-options { flex-wrap: wrap; }
/* 列名后面也带全角冒号、并**靠列名格最右**（用户 2026-09-18 17:07：
   「刚才的调整只是适配了订单详情页卡片吧，没有调整其他的详情页卡片」→ 与订单详情 `.kv-k` 完全同一口径）：
   文字左对齐（flex 匿名 item）、冒号 `margin-left:auto` 顶到最右 */
.di-label { display: flex; align-items: baseline; }
.di-label::after { content: '：'; flex: none; margin-left: auto; }
.di-label {
  /* 列名**左对齐**（用户 2026-09-18 16:54：「列名称改为左对齐」；15:32 那版是右对齐） */
  flex: 0 0 var(--dt-lb-w); min-width: 0; text-align: left; line-height: 1.4;
  font-size: var(--dt-fs); color: var(--dt-label); overflow-wrap: anywhere; white-space: normal;
}
.di-value { flex: 1 1 auto; min-width: 0; font-size: var(--dt-fs); color: var(--dt-text); font-weight: var(--fw-regular); line-height: 1.45; word-break: break-word; white-space: normal; }

/* ---------- 数据中心 ---------- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 16px; }
.stat { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; box-shadow: var(--shadow); }
.stat-link:hover { box-shadow: var(--shadow-hover); border-color: var(--primary); }
.stat-label { font-size: var(--fs-13); color: var(--muted); }
.stat-value { font-size: 26px; font-weight: var(--fw-bold); color: var(--primary-dark); margin: 4px 0 2px; font-variant-numeric: tabular-nums; }
[data-theme="dark"] .stat-value { color: var(--text); }
.stat-value.is-warn { color: var(--c-warning); }
.stat-sub { font-size: var(--fs-12); color: var(--c-gray); }
/* 列表页「顶部统计卡片」（用户 2026-09-18 19:11）—— 一个模块最多 4 张 → **固定 4 列**（一行一张不换行）；
   沿用上面那套 `.stat` 样式（订单中心原本就长这样），只多两件事：
   · `.stat-grid-cards` 固定 4 列（原来的 auto-fit 在只有 1~2 张卡时会拉得很宽 ）
   · `.stat.is-on` = 「正在按这张卡的条件筛选」高亮（再点一次取消筛选）
   · 窄屏降到 2 列 / 1 列 */
.stat-grid-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat.is-on { border-color: var(--primary); box-shadow: var(--ring); }
@media (max-width: 1100px) { .stat-grid-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .stat-grid-cards { grid-template-columns: 1fr; } }
.dash-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 14px; margin-bottom: 16px; }
/* 订单详情：3 张关联卡片（生产维度 / 采购维度 / 发货维度）**一行一张**（用户 2026-09-17：「每张卡片占一行」）
   上下间距必须与卡内 gap 一致（都是 14px）：曾经上 0 / 卡间 14 / 下 16 三种值，
      用户 2026-09-17 报「3 个卡片上下间隙不一致」→ 统一 14px */
.dash-grid.rel-cards { grid-template-columns: 1fr; margin-top: 14px; margin-bottom: 14px; }
.dash-grid + .card { margin-top: 0; }
.app-main > .card { margin-top: 14px; }
/* **第一张卡片不要再吃这个 14px**：否则「距上方间距」= 主区 padding 22 + 14，比其他页面多一截
   （用户 2026-09-15 指出「订单编号卡片距离上方间距不正常」）。同页多张卡片之间仍靠它分隔。 */
.app-main > .card:first-of-type { margin-top: 0; }
.app-main > .dash-grid + .card { margin-top: 0; }

/* ---------- Toast ---------- */
.toast {
  position: fixed; top: 72px; right: 22px; z-index: 120;
  padding: 10px 16px; border-radius: var(--radius-sm); font-size: var(--fs-14);
  box-shadow: var(--shadow-hover); animation: toastIn .25s ease;
}
.toast-ok { background: #ECFDF5; color: #059669; border: 1px solid rgba(5,150,105,.25); }
.toast-err { background: #FEF2F2; color: #DC2626; border: 1px solid rgba(220,38,38,.25); }
@keyframes toastIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ---------- 登录页 ---------- */
.login-body {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 55%, #0EA5E9 130%);
}
.login-card {
  width: min(400px, 92vw); background: var(--card); border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(15,23,42,.35); padding: 32px 30px 24px;
}
.login-head { text-align: center; margin-bottom: 18px; }
.login-head h1 { font-size: var(--fs-24); color: var(--primary-dark); margin: 10px 0 4px; }
/* 暗色模式：登录卡片是深底，系统名称须用白色（--primary-dark 在暗色下仍是深藏青，会看不见） */
[data-theme="dark"] .login-head h1 { color: #FFFFFF; }
.login-head p { font-size: var(--fs-13); color: var(--muted); margin: 0; }
.login-form { display: flex; flex-direction: column; gap: 14px; margin-top: 8px; }
.login-err { background: var(--danger-soft); color: var(--danger); border-radius: var(--radius-sm); padding: 8px 12px; font-size: var(--fs-13); margin-bottom: 4px; }
.login-msg { background: rgba(16,185,129,.10); color: #059669; border-radius: var(--radius-sm); padding: 8px 12px; font-size: var(--fs-13); margin-bottom: 4px; }
.login-foot { text-align: center; font-size: var(--fs-12); color: var(--c-gray); margin-top: 16px; }

/* ---------- 折叠态 & 响应式 ---------- */
@media (max-width: 860px) {
  .app-sidebar { transform: translateX(-100%); transition: transform .25s ease; top: 0; padding-top: 74px; }
  .app-sidebar.mobile-open { transform: none; }
  .app-scrim.mobile-open { display: block; position: fixed; inset: 0; background: rgba(15,23,42,.4); z-index: 45; }
  .app-content { margin-left: 0; }
  .tb-menu-btn { display: flex; }
  .tb-divider { display: none; }
  .tb-left { width: auto; }
  .tb-head { margin-left: 12px; }
  .tb-user { display: none; }
  .app-main { padding: 14px; }
  .dash-grid { grid-template-columns: 1fr; }
  .data-table th, .data-table td { padding: 0 10px; }
}

@media (max-width: 560px) {
  .tb-title { display: none; }
}

/* ============================================================
   设置中心 · 权限设置（左右分栏：左树 + 右详情）
   ============================================================ */
.perm-wrap { display: grid; grid-template-columns: 260px 1fr; gap: 14px; align-items: start; }
.perm-side { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.perm-search { padding: 10px 12px 6px; }
.perm-search input {
  width: 100%; min-height: 32px; padding: 0 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-13); outline: none;
}
.perm-search input:focus { border-color: var(--primary); box-shadow: var(--ring); }
.perm-seg { display: flex; gap: 4px; padding: 0 12px 10px; }
.perm-seg a {
  flex: 1; text-align: center; padding: 6px 0; border-radius: var(--radius-sm);
  border: 1px solid var(--line); font-size: var(--fs-13); color: var(--muted);
}
.perm-seg a:hover { border-color: var(--primary); color: var(--primary); }
.perm-seg a.on { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: var(--fw-bold); }
.perm-side-body { max-height: 62vh; overflow: auto; padding-bottom: 10px; }
.perm-side-group {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 12px; font-size: var(--fs-12); color: var(--muted); background: var(--card-2);
}
.perm-side-group .add {
  width: 20px; height: 20px; line-height: 1; border-radius: 0; border: 1px solid var(--line);
  background: var(--card); color: var(--primary); cursor: pointer; font-size: var(--fs-13);
}
.tnode {
  display: flex; align-items: center; gap: 6px; padding: 7px 10px 7px 12px;
  font-size: var(--fs-13); color: var(--text); cursor: pointer; position: relative;
}
.tnode:hover { background: var(--bg-list); }
.tnode.on { background: var(--primary-soft); color: var(--primary); font-weight: var(--fw-bold); }
.tnode.on::before { content: ''; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; border-radius: 0; background: var(--primary); }
.tnode .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tnode .cnt { font-size: var(--fs-12); color: var(--muted); }
.tnode .dots { color: var(--muted); padding: 0 4px; border-radius: 0; }
.tnode .dots:hover { background: var(--chip); color: var(--primary); }

.perm-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.perm-main-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 16px; box-shadow: var(--shadow);
}
.perm-main-head h3 { margin: 0; font-size: var(--fs-16); color: var(--primary-dark); }
[data-theme="dark"] .perm-main-head h3 { color: var(--text); }
.perm-main-head .spacer { flex: 1; }
.perm-tag { font-size: var(--fs-12); color: var(--muted); background: var(--chip); border-radius: 0; padding: 1px 10px; }
.pm-hint { font-size: var(--fs-12); color: var(--c-gray); }
.pm-sub-tabs { display: flex; gap: 18px; padding: 0 4px; border-bottom: 1px solid var(--line); }
.pm-sub-tabs a { padding: 8px 2px; font-size: var(--fs-14); color: var(--muted); border-bottom: 2px solid transparent; }
.pm-sub-tabs a.on { color: var(--primary); border-color: var(--primary); font-weight: var(--fw-bold); }
.pm-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.pm-card-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--line); }
.pm-card-head h4 { margin: 0; font-size: var(--fs-14); }
.pm-card-body { padding: 14px 16px; }
.pm-desc { font-size: var(--fs-12); color: var(--muted); margin: 0 0 12px; line-height: 1.8; }
.pm-row { padding: 4px 0; }
.pm-row label { display: flex; align-items: center; gap: 8px; font-size: var(--fs-14); cursor: pointer; }
.pm-child { margin-left: 22px; border-left: 1px dashed var(--line); padding-left: 12px; }
.pm-sub { font-size: var(--fs-12); color: var(--c-gray); margin: 8px 0 2px; }
.pm-scope-list { display: flex; flex-direction: column; gap: 8px; }
.pm-scope {
  display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
.pm-scope:hover { border-color: var(--primary); }
.pm-scope.on { border-color: var(--primary); background: var(--primary-soft); }
.pm-scope .t { font-size: var(--fs-14); }
.pm-scope .d { font-size: var(--fs-12); color: var(--muted); }
.pm-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pm-empty { padding: 26px 16px; text-align: center; color: var(--muted); font-size: var(--fs-13); line-height: 2; }
.perm-table { width: 100%; border-collapse: collapse; font-size: var(--fs-14); }
.perm-table th, .perm-table td { padding: 9px 14px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
.perm-table th { font-size: var(--fs-13); color: var(--muted); background: var(--card-2); }
.perm-table tr:hover td { background: var(--bg-list); }
.pm-field { display: flex; flex-direction: column; gap: 6px; }
.pm-field label { font-size: var(--fs-13); color: var(--muted); }
.pm-field input, .pm-field select {
  min-height: 34px; padding: 0 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-14); outline: none; width: 100%;
}
.pm-field input:focus, .pm-field select:focus { border-color: var(--primary); box-shadow: var(--ring); }

/* 操作日志 */
.lg-tilde { color: var(--c-gray); font-size: var(--fs-13); }
.lg-chip { display: inline-block; padding: 1px 10px; border-radius: 20px; font-size: var(--fs-12); background: rgba(79,70,229,.10); color: var(--primary); }
[data-theme="dark"] .lg-chip { background: rgba(129,140,248,.18); color: #A5B4FC; }
.nowrap { white-space: nowrap; }

/* 企业微信设置 */
.wc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* 顶部两列（凭证配置 / 测试消息推送）：各按自身内容高度，避免短卡片被拉得过高 */
.wc-grid.wc-top { align-items: start; }
.wc-full { grid-column: 1 / -1; }
.wc-status { border-radius: var(--radius-sm); padding: 10px 14px; margin-bottom: 10px; font-size: var(--fs-13); line-height: 1.7; }
.wc-status.ok { background: rgba(16,185,129,.10); color: #047857; border: 1px solid rgba(16,185,129,.28); }
.wc-status.err { background: var(--danger-soft); color: #B91C1C; border: 1px solid rgba(239,68,68,.28); }
[data-theme="dark"] .wc-status.ok { color: #6EE7B7; }
[data-theme="dark"] .wc-status.err { color: #FCA5A5; }
.wc-tip { background: rgba(79,70,229,.06); border: 1px solid rgba(79,70,229,.20); border-radius: var(--radius-sm); padding: 12px 16px; font-size: var(--fs-13); line-height: 1.9; color: var(--primary-dark); }
[data-theme="dark"] .wc-tip { color: #C7D2FE; }
.wc-tip code { background: rgba(79,70,229,.10); padding: 1px 6px; border-radius: 0; font-size: var(--fs-12); }
.wc-mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.wc-events { display: flex; flex-direction: column; gap: 8px; padding: 4px 0; }
.wc-events label { display: flex; align-items: center; gap: 8px; font-size: var(--fs-14); font-weight: var(--fw-regular); }
.switch { position: relative; display: inline-block; width: 42px; height: 22px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider { position: absolute; inset: 0; background: var(--line-strong); border-radius: 0; transition: .2s; cursor: pointer; }
.switch .slider::before { content: ''; position: absolute; width: 18px; height: 18px; left: 2px; top: 2px; background: #fff; border-radius: 0; transition: .2s; }
.switch input:checked + .slider { background: var(--primary); }
.switch input:checked + .slider::before { transform: translateX(20px); }

/* ---------- 审批设置页（仿库管王 #/layoutSet/audit：每个单据一张卡 + 审批人）---------- */
.apv-mod { border: 1px solid var(--line, #e5e7eb); border-radius: 8px; margin-bottom: 14px; overflow: hidden; background: #fff; }
.apv-mod-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; }
.apv-mod-name { font-size: 15px; font-weight: 600; color: var(--c-text, #1f2937); }
.apv-mod-body { padding: 4px 16px 16px; border-top: 1px dashed var(--line, #e5e7eb); }
.apv-field { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.apv-label { flex: 0 0 60px; line-height: 32px; color: var(--c-gray, #64748b); font-size: 13px; }
.apv-select { flex: 1 1 240px; min-width: 200px; max-width: 420px; height: auto; padding: 6px 8px; border: 1px solid var(--line, #e5e7eb); border-radius: 6px; background: #fff; color: var(--c-text, #1f2937); font-size: 13px; }
.apv-tip { flex-basis: 100%; color: var(--c-gray, #64748b); font-size: 12px; margin-top: 2px; }

/* ---------- 步骤条 Steps（横向流程规范：单据状态流 / 审批进度等）----------
   用法：
   <ul class="steps">
     <li class="step is-active"><i class="step-dot">1</i><span class="step-label">正在处理</span></li>
     <li class="step"><i class="step-dot">2</i><span class="step-label">等待处理</span></li>
     <li class="step"><i class="step-dot">3</i><span class="step-label">等待处理</span></li>
   </ul>
   状态：默认=待处理（白底灰圈灰字）；.is-active=进行中（蓝底白字+加粗）；.is-done=已完成（蓝底白字） */
.steps { display: flex; align-items: center; width: 100%; margin: 0; padding: 0; list-style: none; }
.steps .step { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.steps .step:not(:first-child) { flex: 1; }
.steps .step:not(:first-child)::before { content: ''; flex: 1; min-width: 40px; height: 1px; background: var(--line, #E5E6EB); margin: 0 12px; }
.steps .step-dot { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-style: normal; background: var(--card, #fff); border: 1px solid var(--line-strong, #C9CDD4); color: var(--c-gray, #86909C); }
.steps .step-label { font-size: 14px; color: var(--c-gray, #86909C); }
.steps .step.is-active .step-dot, .steps .step.is-done .step-dot { background: var(--primary, #4F46E5); border-color: var(--primary, #4F46E5); color: #fff; }
.steps .step.is-active .step-label { color: var(--text, #1D2129); font-weight: 600; }

/* 企微互通的额外资材（审批模板 id / 微盘空间 id / 发件邮箱） */
.interop-extra { display: flex; gap: 10px; flex-wrap: wrap; }
.interop-extra > label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); flex: 1; min-width: 160px; }
.interop-extra input {
  min-height: 32px; padding: 0 9px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-13); outline: none;
}

/* 全局默认字体选择（设置中心 → 企业微信 · 系统外观） */
.font-pick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.font-pick select { min-width: 230px; }
.font-pick input { flex: 1; min-width: 180px; }

@media (max-width: 960px) {
  .perm-wrap { grid-template-columns: 1fr; }
  .wc-grid { grid-template-columns: 1fr; }
}

/* ---------- 侧栏二级导航（设置中心分组） ---------- */
.nav-group {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 0 14px; min-height: 36px; border-radius: var(--radius-sm);
  color: var(--nav-text); font-size: var(--fs-14); white-space: nowrap;
  background: none; border: none; cursor: pointer; font-family: inherit; text-align: left; position: relative;
}
.nav-group:hover { background: var(--nav-hover-bg); color: var(--nav-hover-text); }
.nav-group.active { color: var(--nav-active-text); font-weight: var(--fw-bold); }
/* 设置中心分组箭头（二级导航开关）：展开 → 朝下（rotate 90°）；收起 → 朝右（rotate 0°，即图形原方向）。
   箭头图形本身是「向右」的 > ，所以 不转=右、顺时针90°=下。 */
.nav-group .nav-caret { margin-left: auto; transition: transform .2s ease; transform: rotate(90deg); }
.nav-group[aria-expanded="false"] .nav-caret { transform: rotate(0deg); }
.nav-sub { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px 0; }
.nav-sub.collapsed { display: none; }
.nav-sub .nav-item { min-height: 32px; padding-left: 40px; font-size: var(--fs-13); }
.tb-role { font-size: var(--fs-12); color: var(--primary); background: var(--primary-soft); border-radius: 0; padding: 1px 9px; }

/* 登录页 Logo（同一 702:180 画布比例） */
.login-logo { display: block; height: 44px; width: auto; aspect-ratio: 702 / 180; object-fit: contain; margin: 0 auto; }

/* 系统名称输入（带字数计数） */
.name-wrap { position: relative; display: flex; align-items: center; }
.name-wrap input { padding-right: 56px; }
.name-count {
  position: absolute; right: 10px; font-size: var(--fs-12); color: var(--c-gray);
  font-variant-numeric: tabular-nums; pointer-events: none;
}
.name-count.is-full { color: var(--c-warning); }
.wc-status.warn { background: rgba(245,158,11,.12); color: #B45309; border: 1px solid rgba(245,158,11,.35); }
[data-theme="dark"] .wc-status.warn { color: #FCD34D; }

/* 设置页 Logo 预览：与顶栏同一 702:180 画布规则，限制在表单内的合理尺寸 */
.logo-preview {
  display: block; height: 36px; width: auto; max-width: 220px; aspect-ratio: 702 / 180;
  object-fit: contain; border: 1px solid var(--line); border-radius: 8px;
  padding: 6px 8px; margin-bottom: 8px; background: var(--field-bg);
}

/* ============================================================
   货品三级标准类 · 左树导航（搜索 / 全部 / 展开收起 / 选中高亮）
   ============================================================ */
.cat-layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 14px; align-items: start; }
/* ===== 货品管理列表页「铺满一屏」+ 筛选行/表头「吸顶」（用户 2026-09-26）=====
   用户口径（第二版）：「共 7 页·123 这个浏览器置底，把上方的列表表格区增大一些；
                        左侧的产品分类卡片，也做到对齐」
   分工：**高度**由 public/js/listFit.js 算好写进 style（右侧铺到视口底、左侧同高 ⇒ 底边齐平）
        → 这里只负责「内部纵向分配」与「吸顶」：
   · `.info-list > *` 默认不伸缩，**只有表格壳体吃剩余高度** ⇒ 分页条被顶到最底（浏览器置底 ✓）、
     表格区随之变大 ✓
   · `.table-wrap` 跟着吃满壳体 ⇒ 它成为滚动容器 ⇒ 表头 sticky 生效 ✓
   · `.cat-side` 纵向 flex：标题/搜索固定，中间**分类树吃掉剩余高度**（原来写死 `max-height:58vh`，
     卡片变高后会留一大截空白 ✗）
   · 筛选行吸顶：`top: 74px` 与左侧卡片同一吸附位（顶栏 60 固定 + 14 间距）——
     铺满后通常不会有页面滚动，留着是给「窗口特别矮」时兜底 ✓
     ⚠️ 祖先不能有 `overflow`（会让 sticky 失效）—— `.cat-layout` / `.info-list` / `.app-main` 都没有 ✓
   只对开了 `cfg.sideFit` 的模块生效（模板给布局加 `.is-fit`）✓ */
.cat-layout.is-fit .cat-side { display: flex; flex-direction: column; }
.cat-layout.is-fit .cat-tree-inline { flex: 1 1 auto; min-height: 0; max-height: none; }
.cat-layout.is-fit .filter-bar { position: sticky; top: 74px; z-index: 6; }
.cat-layout.is-fit .info-list > * { flex: 0 0 auto; }
.cat-layout.is-fit .info-list > .info-table-shell { flex: 1 1 auto; min-height: 0; }
.cat-layout.is-fit .info-table-shell > .table-wrap { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; }
.cat-side {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); overflow: hidden; position: sticky; top: 74px;
}

/* 搜索行（照参考框架：输入关键字进行搜索 + 新增一级分类） */
.cat-search { display: flex; align-items: center; gap: 6px; padding: 9px 10px; border-bottom: 1px solid var(--line); background: var(--card-2); }
.cat-search input {
  flex: 1; min-width: 0; height: 30px; padding: 0 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--card); color: var(--text);
  font-family: inherit; font-size: var(--fs-13); outline: none;
}
.cat-search input:focus { border-color: var(--primary); box-shadow: var(--ring); }

.cat-tree-inline { max-height: 58vh; overflow: auto; padding: 4px 0; }
.cat-node[hidden] { display: none; }
.cat-children { display: block; }
.cat-children.is-collapsed { display: none; }

.cat-row {
  display: flex; align-items: center; gap: 4px; padding: 5px 8px;
  font-size: var(--fs-13); position: relative; min-height: 28px;
}
.cat-row:hover { background: var(--bg-list); }
.cat-row .cat-label {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); cursor: pointer;
}
.cat-row .cat-label:hover { color: var(--primary); text-decoration: underline; }

/* 一级 = 分组标题（浅灰底、加粗），二三级缩进 */
.cat-row.lv1 { background: var(--bg-list); font-weight: var(--fw-bold); }
.cat-row.lv1:hover { background: var(--chip); }
.cat-row.lv2 .cat-label { color: var(--primary-dark); }
[data-theme="dark"] .cat-row.lv2 .cat-label { color: var(--text); }
.cat-row.lv3 .cat-label { color: var(--muted); }
.cat-row.lv4 .cat-label { color: var(--muted); }

/* 「全部」项 */
.cat-all { background: transparent; }
.cat-all .cat-label { cursor: pointer; }

/* 选中态：蓝底高亮（照参考框架）。
   必须能压过「一级悬停」.cat-row.lv1:hover（0,3,0）：否则一级分类选中后再悬停，
   蓝底会被浅灰覆盖、而文字仍是白色 → 颜色看起来错乱/看不清 */
.cat-row.is-active,
.cat-row.is-active:hover,
.cat-row.lv1.is-active,
.cat-row.lv1.is-active:hover { background: var(--primary); }
.cat-row.is-active .cat-label,
.cat-row.is-active .cat-label:hover { color: #fff; text-decoration: none; font-weight: var(--fw-bold); }
.cat-row.is-active .cat-usage { background: rgba(255,255,255,.22); color: #fff; }
.cat-row.is-active .cat-caret { color: #fff; }
.cat-row.is-active .cat-ico-btn { color: rgba(255,255,255,.85); }
.cat-row.is-active .cat-ico-btn:hover { background: rgba(255,255,255,.18); color: #fff; }

/* 展开 / 收起箭头 */
.cat-caret {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; padding: 0; flex-shrink: 0;
  border: none; background: none; color: var(--c-gray); cursor: pointer;
  transition: transform .18s ease;
}
.cat-caret[aria-expanded="false"] { transform: rotate(-90deg); }
.cat-caret.is-leaf { pointer-events: none; }
.cat-caret:hover { color: var(--primary); }

.cat-row .cat-usage {
  font-size: 11px; color: var(--muted); background: var(--chip);
  border-radius: 0; padding: 0 6px; line-height: 16px; flex-shrink: 0;
}
.cat-row .cat-ops { display: none; gap: 0; flex-shrink: 0; }
.cat-row:hover .cat-ops { display: flex; }
.cat-ico-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0; border: none; border-radius: 0;
  background: none; color: var(--muted); cursor: pointer; flex-shrink: 0;
}
.cat-ico-btn:hover { background: var(--chip); color: var(--primary); }
.cat-ico-btn.danger:hover { color: var(--c-danger); }
.cat-ico-btn:disabled { opacity: .5; cursor: default; }

.cat-inline { display: flex; align-items: center; gap: 6px; padding: 4px 8px; }
.cat-inline input {
  flex: 1; min-width: 0; height: 28px; padding: 0 8px; border-radius: var(--radius-sm);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-13); outline: none;
}
.cat-inline input:focus { border-color: var(--primary); box-shadow: var(--ring); }
.cat-empty-hint { padding: 16px 12px; text-align: center; color: var(--muted); font-size: var(--fs-13); }

.cat-side-foot {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 10px; border-top: 1px solid var(--line); background: var(--card-2);
}
.cat-side-foot .pm-hint { margin-left: auto; }
.cat-lv { font-size: var(--fs-12); border-radius: 0; padding: 1px 8px; }
.cat-lv.lv1 { background: rgba(79,70,229,.12); color: var(--primary); }
.cat-lv.lv2 { background: rgba(14,165,233,.12); color: var(--c-info); }
.cat-lv.lv3 { background: rgba(16,185,129,.12); color: var(--c-success); }
/* 第 4 级（用户 2026-09-26：分类最多 4 级）*/
.cat-lv.lv4 { background: rgba(245,158,11,.12); color: var(--c-warning, #d97706); }

@media (max-width: 1100px) {
  .cat-layout { grid-template-columns: 1fr; }
  .cat-side { position: static; }
  .cat-tree-inline { max-height: none; }
}

/* ---------- 订单明细行（新建/编辑订单弹窗） ---------- */
.form-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.form-row-2 .form-row { margin-top: 0; }
/* 「1 行 4 个值」的网格（用户 2026-09-26：新增供货页的**共用字段卡片**用它）——
   与 .form-row-2 同一套写法，只改列数；窄屏逐级降列（4 → 2 → 1）✓ */
.form-row-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.form-row-4 .form-row { margin-top: 0; }
@media (max-width: 1100px) { .form-row-4 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 700px) { .form-row-4 { grid-template-columns: 1fr; } }
.oi-table-wrap { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: auto; max-height: 320px; }
.oi-table { width: 100%; border-collapse: collapse; font-size: var(--fs-13); }
.oi-table th { position: sticky; top: 0; background: var(--card-2); color: var(--muted); font-weight: var(--fw-regular); text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--line); }
.oi-table td { padding: 5px 6px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.oi-table tr:last-child td { border-bottom: none; }
.oi-table select, .oi-table input { width: 100%; min-height: 30px; padding: 0 8px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-13); outline: none; }
.oi-table select:focus, .oi-table input:focus { border-color: var(--primary); box-shadow: var(--ring); }
.oi-table .oi-amount { text-align: right; padding-right: 10px; color: var(--text); font-variant-numeric: tabular-nums; }
.oi-foot { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.oi-foot .oi-total { margin-left: auto; font-size: var(--fs-13); color: var(--muted); }
.oi-foot .oi-total b { font-size: var(--fs-16); color: var(--danger); font-variant-numeric: tabular-nums; }
[data-theme="dark"] .oi-foot .oi-total b { color: #FCA5A5; }
.kv-table { width: 100%; border-collapse: collapse; font-size: var(--fs-14); }
.kv-table th { width: 96px; text-align: left; color: var(--muted); font-weight: var(--fw-regular); padding: 7px 10px 7px 0; vertical-align: top; }
.kv-table td { padding: 7px 0; border-bottom: 1px dashed var(--line); }
.kv-table tr:last-child td, .kv-table tr:last-child th { border-bottom: none; }
/* 订单详情「订单信息」：**一行 4 个字段**的信息网格（用户 2026-09-15 从 3 列改成 4 列）。
   窄屏自动降为 3 / 2 / 1 列，避免把值和标签挤成竖排。
   用固定 `repeat(4, …)` 而不是 auto-fit —— auto-fit 的列数会随宽度乱跳，不是「固定每行几个」。 */
/* 详情页字段网格：**上下 16 / 左右也是 16**（用户 2026-09-18 16:03「字段与字段之间左右间距统一成 16」——
   原来左右是 20，与货品/供应商详情那套的 16 不一致 ） */
/* **一行 4 个**（用户 2026-09-18 15:32 定的口径，别动）——
   2026-09-18 16:38 的调整**只改列名宽度**（`--dt-lb-w` 96 → 168，为了 8 个汉字不换行），
   列数保持 4 个不动；列名变宽后每列内容自然变窄（约 108px），属预期 */
.kv-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; font-size: var(--fs-14); }
/* 窄屏逐级降列（详情页字段：一行 4 个是标准，窗口不够时 3 / 2 / 1；`.detail-grid` 共用同一组） */
@media (max-width: 1320px) { .kv-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1000px) { .kv-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px) { .kv-grid { grid-template-columns: 1fr; } }
@media (max-width: 1320px) { .detail-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1000px) { .detail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px) { .detail-grid { grid-template-columns: 1fr; } }
/* 「订单信息」每条：**左右**排 —— 列名称左对齐、列内容左对齐（用户 2026-09-15 口径）。
   · 用 flex（**不要用 grid 的 `max-content`**：配合百分比 max-width 时列会被压窄，实测标签全被截断）。
   · 列名称「装得下一行、装不下就换行」；**不要** `overflow:hidden + ellipsis`（截断成「…」用户否过）。 */
.kv-item {
  /* **上下列之间不带分割线**（用户 2026-09-18 15:34）——原来的 `border-bottom: 1px dashed` 已去掉；
     **上下间距改由网格的 `gap: 16px` 统一给**（用户 2026-09-18 15:38「上下控件间隔16」）→
     所以这里的内边距归 0，行距才正好 16（原来是 padding 8+9 自己撑出 17）*/
  min-width: 0; padding: 0;
  /* 列名 ↔ 内容 = **16 → 8**（用户 2026-09-18 15:32 定 16；16:54 要求「降为现在的一半」）*/
  display: flex; align-items: center; gap: var(--dt-gap);
  /* **只读 / 就地编辑两态行高严格一致 = 32**（用户 2026-09-18 16:26：
     「在订单详情页点击编辑，卡片就开始动」）——
     改前：只读态行高 = 文字行高 ≈20.3，编辑态控件 32 → 点编辑每行 +12px，
     卡片长高、下方所有内容整体下移 → 看着就是「卡片动了、变大了」
     → 现在只读态每行也占 **32**（值垂直居中，与全站输入框 Medium 档同高），
     编辑态控件同样是 32 → 两态像素级一致，点编辑时卡片纹丝不动
     （`align-items` 从 baseline 改 center：32 高的行里 baseline 对齐会让文字下沉） */
  min-height: 32px;
}
.kv-k {
  /* 字号 14 / 颜色 #767B9E（用户 2026-09-18 15:40；原来是 13px + muted）—— 与 `.di-label` 同口径 */
  color: var(--dt-label); font-weight: var(--fw-regular); font-size: var(--dt-fs); line-height: 1.4;
  /* 列名**左对齐** + 固定列宽 `--dt-lb-w`（用户 2026-09-18 16:54：「列名称改为左对齐」；
     15:32 那版是右对齐）—— 固定列宽才能让同一行/上下行的「内容」左边缘对齐
     列的**宽度**换成固定值（原 `flex:0 0 auto` 自然宽度）→ 长字段名照旧**换行显示**
       （`max-width:100%` + `white-space:normal` + `overflow-wrap:anywhere` 都保留，绝不截断加「…」）。 */
  flex: 0 0 var(--dt-lb-w); max-width: 100%;
  text-align: left;
  /* 宽度规则（2026-09-15 定稿，仍在遵守）：
     · `max-width: 100%` → 自然宽超过格子时**换行**，绝不顶出格子
     · `white-space: normal` + `overflow-wrap: anywhere` → 超长字段名换行显示（**不截断、不加「…」**）
     反面教材（都实测踩过）：
       · `flex:0 1 auto; min-width:0; overflow-wrap:anywhere` → 标签被压成**一个字宽**、内容竖排，
         整张卡片糊成一团（用户：「详情页变成啥了？乱七八糟的啊」）
       · `flex:0 0 auto; white-space:nowrap` → 超长标签**顶出格子**，也糊
       · `overflow:hidden; text-overflow:ellipsis` → 截成「订单编号…」，用户早前否过 */
  white-space: normal; overflow-wrap: anywhere;
}
/* 列名称后面统一带全角冒号（用户要求）；用 ::after 加，不动 DOM 文本。
   2026-09-18 17:04 用户：「订单详情页列标题后面有个『：』，把**冒号右对齐，文字左对齐**」——
   做法：把 `.kv-k` 变成 flex（文字是匿名 flex item），冒号用 `margin-left: auto` 顶到**列名格子的最右端**；
   文字仍 `text-align: left` 贴左 （`.kv-item-head` 里的标签宽度由内容撑，`auto` 没有余量 → 效果不变 ） */
.kv-k { display: flex; align-items: baseline; }
.kv-k::after { content: '：'; flex: none; margin-left: auto; }
/* 内容：字号 14 / 颜色 #130928（用户 2026-09-18 15:40）—— 左对齐、占满剩余宽度 */
.kv-v { min-width: 0; font-size: var(--dt-fs); color: var(--dt-text); line-height: 1.45; word-break: break-word; text-align: left; flex: 1 1 auto; }
/* 卡片标题行里的**第一个字段**（取代原来的标题文字「订单信息」，用户 2026-09-15）：
   继承 `.kv-item` 的左右排，但去掉下划线与内边距，并把字号/字重/颜色拉回普通正文
   （否则会继承 `.rel-title` 的 16px 加粗 + primary-dark）。 */
/* 卡片标题行里的**第一个字段**（取代原来的标题文字「订单信息」，用户 2026-09-15）：
   继承 `.kv-item` 的左右排，去掉下划线与内边距；**字号/字重/颜色一律「继承」标题行** ——
   这样它就和下方「生产维度 / 采购维度 / 发货维度」卡片标题（同为 `.rel-title`）**完全一致**（用户明确要求一致）。
   不要在这里写死 font-size/weight/color：`.kv-k` 有 13px + muted、`.kv-v` 会继承，两者都会跟标题行不一致。 */
.kv-item-head {
  border-bottom: none; padding: 0;
  flex: 0 0 auto;                      /* 它在 `.rel-title` 的弹性行里，默认会被压缩 */
  min-height: 32px;                    /* 与字段行同高（用户 2026-09-18 16:26：点编辑卡片不能动）——
                                          它也是「第 1 个字段」，编辑态里会换成控件（高 32）→ 标题行若不同高，卡片仍会变 */
}
/* 标题行里的编辑控件**不许占满整行**（只读态这里是一小段值文字，控件占满会把标题行撑宽、右侧按钮被挤走） */
.kv-item-head .kv-v.form-row > * { max-width: 220px; }
.kv-item-head .kv-k,
.kv-item-head .kv-v { font-size: inherit; font-weight: inherit; color: inherit; }
/* 标题行的标签**不能再套 `.kv-k` 的 `max-width:70%`**：这里父宽度是内容撑出来的（不定宽），
   百分比会算成一个很小的值 → 标签被截断成「订单编号…」（2026-09-15 放大截图才发现）。 */
/* 标题行的标签**不能再套 `.kv-k` 的固定列宽/右对齐**：那一行是卡片标题（只放第 1 个字段），
   宽度由内容撑出来 —— 固定 96 会让它和右边的值错位 （2026-09-15 也踩过 `max-width` 百分比那次）。 */
.kv-item-head .kv-k { max-width: none; overflow: visible; flex: 0 0 auto; text-align: left; }
/* 弹窗 / 就地编辑的**网格里，日期控件必须占满格子** ——
   `.dp-field` 全局带 `max-width:220px`（筛选栏那套用的）→ 在 3 列网格（~400px）里只占一半，看着"宽度不够"
   （用户 2026-09-15：「新建弹窗，下单日期的内容宽度不对…下单日期的宽度依旧不够」）。 */
body.embed-body .modal-grid .dp-field,
body.embed-body .modal-grid .tp-field,
.inline-edit-grid .dp-field,
.inline-edit-grid .tp-field { max-width: none; }

/* ---------- 采购表单：上方「订单只读卡片」+ 下方「货品明细录入」（用户 2026-09-16）---------- */
/* 表单标题行：「新建采购」+ 右侧的「转入销售订单」按钮 */
.form-title-row { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; }
.form-title-row h2 { margin: 0; }
/* 单头卡片：仿「库管王 · 新增单据」——**标签在左、控件在右**；用 12 栅格精确控制每行放几项
   （用户 2026-09-16：第 1 行 4 项、第 2 行 3 项…，每项跨度由 po-item 的 inline grid-column 决定 ） */
/* 卡片内距**不再单独覆盖**（用户 2026-09-18 17:43：转入卡片要按「订单中心卡片」的规范走）——
   原来这里写 `padding: 10px 14px`（+ `.form-flat` 那条再压成左右 8/14）→
   与订单中心字段卡的 `.card{padding:14px}` 不一致、字段列宽也对不上 → 现在一律走 `.card` 的 14 */
.po-card { margin: 0 0 12px; }
/* `.po-head` / `.po-title` 目前**已无模板使用**（用户 2026-09-18 17:38：「追溯原单」从卡片
   右上角上移到工具条按钮组最左 → `_orderCard.ejs` 里整块 `.po-head` 一起删掉了）——
   规则先留着不删（万一以后卡片顶部还要放东西）；要放内容时记得它会带来 min-height 18 + 4 的间距。 */
.po-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; min-height: 18px; }
.po-title { font-weight: 600; }
/* 采购 / 出货 / 生产 卡片上的字段行（详情页 + 新建页共用）：一行 4 项、列名右对齐
   与详情页字段标准完全统一（用户 2026-09-18 15:32~16:03）：**列名↔内容 16、上下 16、左右 16** */
.po-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 16px; }
/* 转入卡片的字段行 = **完全照订单中心字段卡**（用户 2026-09-18 17:43：
   「采购/生产/出货管理详情页的转入卡片，不是按照当前详情页卡片的规范来执行的，规范是以订单中心的卡片为准」）——
   · 行高 **32**（`.kv-item` 同款 `min-height:32px`；原来按内容撑，采购 28 / 出货·生产 22 ）
   · 列名宽 = `--dt-lb-w`（100，见下面 `.po-item .po-k`；原来是写死的 72 ）
   · 列名↔内容 8、上下/左右 16、字号色 14/#767B9E·14/#130928 —— 这四项本来就与详情页同口径 */
.po-item { display: flex; align-items: center; gap: var(--dt-gap); min-width: 0; padding: 0; min-height: 32px; }
/* 采购表单（.form-flat）三张卡片的间距**统一 16px**（用户 2026-09-16）
   原来：.edit-form 是 flex 纵向 + gap:14px，卡片各自还有 12px margin → 间距变成 52px
      → 这里把表单的 gap 归零、只靠卡片 margin 控制（flex 下 margin 不合并，正好可控 ） */
.form-flat .edit-form { gap: 0; }
.form-flat .pt-bar { margin: 0 0 16px; }
.form-flat .po-card, .form-flat .pi-card, .form-flat .pf-card, .form-flat .dt-card { margin: 0 0 16px; }
.form-flat .pf-card { margin-bottom: 0; }
/* 卡片字段的标签列：右对齐（同一行控件的左边界才能对齐 ）；宽度收到 72px，减小「前导空白」
   右对齐的标签天生带一截前导空白 → 看起来左边距比右边大（用户 2026-09-16 反馈「左右不对称」）
      → 标签列 92→72px + 卡片左内衬 14→8px，让左右**视觉上**接近
   此处曾出现一个**孤立的注释结束符**（缺少对应的注释开始符）→ 解析器当场出错，
        把紧随其后的 `.po-k` 规则整条吞掉 （实测：flex 与字号全部未生效，标签被日期控件挤到换行）
        教训：CSS 注释必须成对；**多余的结束符不会报错，但会静默丢掉后面的规则** ——
        改完样式务必扫一遍（守卫：`.shot/verify-css-integrity.js` ） */
/* 字号 / 颜色与详情页字段标准统一（用户 2026-09-18 16:00「一起统一」）：
   列名 14px + `--dt-label`(#767B9E)、内容 14px + `--dt-text`(#130928)；列名右对齐、宽 72px 不变 */
.po-k { flex: 0 0 72px; text-align: left; color: var(--dt-label); font-size: var(--dt-fs); }   /* 列名左对齐（用户 2026-09-18 16:54；原右对齐）*/
/* 卡片字段的冒号：与详情页同一口径（文字左对齐、**冒号靠列名格最右**）——
   冒号不再写死在模板文本里（`views/purchases/_orderCard.ejs`），改由这里提供
   只作用于 `.po-item` 内的 `.po-k`（卡片字段行）；标题行 `.form-title-no .po-k` 不带冒号 */
/* 卡片字段行的列名宽 = `--dt-lb-w`（100，与订单中心 `.kv-k` 同款；原来是写死的 **72px** ）
   —— 用户 2026-09-18 17:43「以订单中心的卡片为准」
   只作用于 `.po-item` 内的标签：工具条那行（`.form-title-no .po-k` = 系统编号）**不动**，
      它按内容自然宽（用户 17:17 刚定的工具条格式，别牵连） */
.po-item .po-k { flex: 0 0 var(--dt-lb-w); display: flex; align-items: baseline; }
.po-item .po-k::after { content: '：'; flex: none; margin-left: auto; }
/* 只保留 `.pf-card`（货品明细卡）的窄内衬；**转入卡片（`.po-card`）已从这条里摘掉** ——
   它现在跟随 `.card` 的 18，与订单中心字段卡对齐（用户 2026-09-18 17:43）*/
.form-flat .pf-card { padding-left: 8px; padding-right: 14px; }
/* 卡片**锁定态**的纯展示值（与「订单详情」一致，用户 2026-09-16：
   「第一个展示样式要和第二个一致」）—— 纯文字 / 彩色标签，不再出现输入框边框 */
.po-ro { color: var(--dt-text); font-size: var(--dt-fs); line-height: 22px; white-space: pre-wrap; overflow-wrap: anywhere; }
.po-ro-chips { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.po-k.is-req::before { content: '*'; color: var(--danger); margin-right: 2px; }
.po-v { flex: 1 1 auto; min-width: 0; color: var(--dt-text); font-size: var(--dt-fs); }
/* 采购处理「关联销售订单」卡片：字段 = **列名在上、值在下**（用户 2026-09-22 20:0x：
   「把关联销售订单卡片的内容，除了订单备注，调整为（参考图）这种上下样式」）——
   只作用于采购处理这张卡（`.po-card-lt`）：冻结的「采购管理」同款卡片**不受影响**
   「订单备注」那行是整行 span 12（`.po-item-full`）**保持原样**（左列名 + 右内容）不参与上下排列 */
.po-card-lt .po-item:not(.po-item-full) { flex-direction: column; align-items: flex-start; gap: 2px; min-height: 0; }
.po-card-lt .po-item:not(.po-item-full) .po-k { flex: none; }
.po-card-lt .po-item:not(.po-item-full) .po-k::after { content: ''; margin-left: 0; }   /* 去掉「：」（参考图标签后无冒号）*/
.po-card-lt .po-item:not(.po-item-full) .po-v { flex: none; width: 100%; }
/* 「修改销售订单」按钮（挂在「订单备注」行的最后，用户 2026-09-22 20:0x）——
   该行 .po-item 是 flex、.po-v 撑满 ⇒ 按钮作最后一个子元素自动靠右；不参与收缩、垂直居中 */
.po-item .po-mo { flex: 0 0 auto; align-self: center; }
.po-empty { color: var(--muted); font-size: var(--fs-13); padding: 10px 0; }
/* 窄屏**只降列数**（`.kv-grid` 上方那组媒体查询），间距一律保持 16 ——
   原来 1100/700px 各有一条「把列距压到 10/8」的规则，与「间距统一 16」相冲突 → 已删 */
/* 收起（仿参考图） */
.po-collapse { display: block; width: 100%; text-align: center; border: 0; background: transparent; color: var(--link); font-size: var(--fs-13); padding: 4px 0 0; cursor: pointer; }
/* 收起：保留**订单编号所在的整行**（3 列网格 → 前 3 项：订单编号 / 业务员 / 团队），其余隐藏
   （用户 2026-09-16：「至少显示订单编号的那一行，不是只显示订单编号」） */
/* 收起时保留哪些字段由脚本按「实际第一行」决定 （原来写死 nth-child(n+4) 会吞掉行内字段 ） */
.po-card.is-collapsed .pm-hint { display: none; }
/* 页面最底部的「备注 + 创建人 / 创建时间」块 */
.pf-card { margin: 12px 0 0; padding: 10px 14px; }
.pf-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.pf-row .po-k { flex: 0 0 92px; }
.pf-row .po-v { flex: 1 1 auto; }
.pf-meta { display: flex; align-items: center; gap: 24px; color: var(--muted); font-size: var(--fs-13); padding-top: 6px; }

/* 卡片里的字段：没转入时可手动填、转入后只读（样式保持一致，只靠底色区分） */
.po-in { width: 100%; padding: 4px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--field-bg); font-size: var(--fs-14); }
.po-in.is-readonly { background: var(--bg-list); color: var(--muted); cursor: default; }
.po-item-wide { grid-column: 1 / -1; }
.po-multi { min-height: 30px; }
.po-in:disabled { opacity: 1; -webkit-text-fill-color: var(--muted); }
/* 「无外层卡片」的表单容器（采购新增/详情/编辑同页）：自身不带边框底色与内边距 */
/* 采购单顶部工具条（新建 / 暂存 / 编辑 / 保存 / 取消 / 转入）+ 状态标记（用户 2026-09-16） */
.pt-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 10px; }
/* ═══ 工具条「进度」胶囊组（用户 2026-09-19 20:1x：「草稿状态是橙色按钮，白色文字」+
   「采购状态和质检状态，不要合并的1个按钮里面，要拆成两个按钮」）═══
   · 第 1 枚 = **主状态**（`.pt-state`）：**实底 + 白字**（草稿=橙 / 采购·质检·入库=主色 / 已结案=绿 / 已作废=灰）
   · 第 2 枚起 = **子状态**（`.pt-sub`）：浅底 + 深字；**各自独立**，不再用 " · " 合并成一枚 */
.pt-state-wrap { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* 状态标签改成 `.tag-chip`（与「产品分类」同款，用户 2026-09-21）后，
   它自带 `margin: 2px 5px 2px 0` 会与容器的 gap:6 叠加（变成 11px）⇒ 在容器里把外边距压掉，
   间距统一由 `.pt-state-wrap` 的 gap 给（= 原来的 6px 观感）*/
.pt-state-wrap > .tag-chip { margin: 0; }
.pt-state { font-size: var(--fs-13); line-height: 20px; padding: 1px 9px; white-space: nowrap;
  color: #fff; border: 1px solid transparent; }
/* 子状态胶囊（采购中 / 部分到货 / 合格 / 入库审核中 …）—— 次要信息：浅底深字
   文字色走 `--ink-2`（暗色自动转浅灰）：硬编码 #494949 在暗色底上是黑字看不清 */
.pt-sub { font-size: var(--fs-13); line-height: 20px; padding: 1px 9px; white-space: nowrap;
  color: var(--ink-2); background: var(--bg-list); border: 1px solid var(--line); }

/* 三档色调（用户 2026-09-19 20:2x 给定完整对照表）——
     橙 = **进行中**（采购中 / 供应商生产 / 供应商发货 / 部分到货 / 待质检 / 质检中 / 入库审核中）
     绿 = **完成**  （已签收 / 已到货 / 合格 / 已入库 / 入库审核已通过）
     红 = **异常**  （异常 / 异常补货 / 入库审核已拒绝）
   · 主状态枚（`.pt-state`）= **实底白字**；子状态枚（`.pt-sub`）= **同色系浅底深字** */
.pt-state.pt-tone-warn { background: #F59E0B; border-color: #F59E0B; }
.pt-state.pt-tone-ok   { background: #16A34A; border-color: #16A34A; }
.pt-state.pt-tone-bad  { background: #DC2626; border-color: #DC2626; }
.pt-state.pt-tone-none { background: #94A3B8; border-color: #94A3B8; }
.pt-sub.pt-tone-warn { color: #B45309; background: #FEF3C7; border-color: #FCD34D; }
.pt-sub.pt-tone-ok   { color: #15803D; background: #DCFCE7; border-color: #86EFAC; }
.pt-sub.pt-tone-bad  { color: #B91C1C; background: #FEE2E2; border-color: #FCA5A5; }
.pt-sub.pt-tone-none { color: var(--ink-2); background: var(--bg-list); border-color: var(--line); }
/* 订单中心列表「采购状态」列 = **状态机胶囊**（用户 2026-09-21：「需要和采购管理中对应的状态一致」）——
   与「采购管理」列表的「状态」列同一份渲染（.pt-state / .pt-sub + .pt-tone-*），
   多张采购单时前面加「N 张 ·」小灰字（颜色走变量 → 暗色下自动转浅 ） */
.ps-cell { display: inline-flex; align-items: center; gap: 6px; }
.ps-count { color: var(--muted); font-size: var(--fs-12); white-space: nowrap; }
/* 更多操作下拉（用户 2026-09-19）：点开是绝对定位竖向菜单，不再把按钮摊成 block */
.more-menu { position: relative; display: inline-block; }
.more-menu > summary { list-style: none; cursor: pointer; }
.more-menu > summary::-webkit-details-marker { display: none; }
.more-menu > summary::marker { display: none; }
/* 面板宽度 = **内容宽**（原 `min-width:168px` 太宽，用户 2026-09-19「宽度过宽了」）：
   `width:max-content` 让面板刚好包住最宽的那一条（「重置草稿」4 字），其余按钮被 100% 拉成同宽 */
.more-list { position: absolute; right: 0; top: calc(100% + 6px); width: max-content; min-width: 0; margin: 0; padding: 6px; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(15,23,42,.14); display: none; flex-direction: column; gap: 12px; z-index: 60; }   /* 菜单项上下间隔 4 → **12px**（用户 2026-09-19 20:4x：「按钮上下间隔调整到12」） */
.more-menu[open] .more-list { display: flex; }
/* 下拉里的按钮**文字居中**（用户 2026-09-19：「下方的按钮，要居中对齐」）—— 原来是左对齐 */
.more-list .btn { width: 100%; justify-content: center; text-align: center; }
/* 「功能」下拉按钮 = **按 4 个字宽**（用户 2026-09-19 20:3x：「功能按按照4个字的宽度来处理这个按钮」）——
   它的文案只有 2 个字（+ 箭头），比旁边那排 4 字按钮（开始质检 / 标记供应商生产 / 确认入库…）窄一截、
   整排按钮看着不齐 → 补齐到「**4 字 + 左右内边距 + 边框**」，与 4 字按钮**等宽**
   （用 em 而非 px：字号一改也不会错位；`.btn-xs` 的 padding = `--btn-px-sm` = 8） */
.more-menu > summary.btn { min-width: 80px; }   /* 宽度定 **80px**（用户 2026-09-19 20:4x：「功能按钮宽度调整到80」；原按 4 字算 = 74） */
/* 「功能」按钮的箭头（内联 SVG，替代原来的 ▾ 字形）：展开时翻转 180° */
.more-caret { transition: transform .15s ease; }
.more-menu[open] > summary .more-caret { transform: rotate(180deg); }
/* 采购状态步骤条复用全局 `.steps` 组件（用户 2026-09-19）：子状态 + 作废态扩展 + 容器留白 */
/* 采购单进度条 = **独立卡片壳**（用户 2026-09-19：「进度一行卡片和下方转入带出卡片没有间隔」+
   「采购单进度这一行感觉不够直观」）→ 补左侧标题 + 下间距 **16px（全站卡片标准间距，用户 2026-09-19 19:5x 指正原为 12px）**，右下不再贴着卡片。
   圆角走 `--radius`（全站直角，用户明确要求不加圆角）*/
.pt-steps-box { display: flex; align-items: center; gap: 18px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 18px;
  margin: 0 0 16px; box-shadow: var(--shadow-lg); }
/* 🆕 订单详情页的整单进度条要**补一个上边距**（用户 2026-09-27：
   「订单中心，订单详情页，三张卡片之间的间距不对等」）——
   实测（`_probe_gaps.js`）：那一页「订单信息卡 → 进度条」的间距是 **0px**、
   而其余相邻块都是 16px ⇒ 只有这一处"贴"在一起、看着不等 ✓
   根因：上面那张卡 `margin-bottom: 0`、进度条自己 `margin-top: 0` ⇒ 谁都不给间距 ✗
   ⚠️ 只加 **`.ord-steps` 这一个修饰类**（订单详情页在用），
      **不动处理中心那边**（它的进度条上面是工具条、本来就是 `margin: 0 0 16px`）✗ */
.pt-steps-box.ord-steps { margin-top: 16px; }
.pt-steps-t { flex: 0 0 auto; font-size: var(--fs-13); font-weight: var(--fw-medium); color: var(--text); white-space: nowrap; }
#ptSteps { flex: 1; min-width: 0; padding: 0; margin: 0; border-top: 0; background: none; }
/* 已完成 / 当前 的步骤名与子状态**同色**（用户 2026-09-20 10:29：
   「都是同级别按钮，质检和采购不是同一个颜色？」截图里 采购=深色、质检=主色 ）
   ⇒ 两者一律用主色；「当前步」只靠 ① 实心圆点外环 ② 文字加粗 区分
   未走到的步骤仍是 `.steps .step-label` 的默认灰（--c-gray）⇒ 进度感不丢 */
#ptSteps .step.is-done .step-label { color: var(--primary); }
#ptSteps .step.is-active .step-dot { box-shadow: 0 0 0 3px var(--primary-soft-2); }
#ptSteps .step.is-active .step-label { color: var(--primary); }
/* ============================================================================
   每格状态色（**2026-09-27 定稿 = 回到第一版配色**）——
   用户原话：「你是不是亮和暗用反了啊，我感觉你的新的颜色，还不如我**第一版**的颜色。」
   🔴 我把「亮一点的蓝 / 暗蓝」按**字面**理解成 #4B87D9(浅) / #4F46E5(深) ⇒ 反了 ✗
      实际观感：**#4F46E5 = 饱满有存在感的蓝（"亮"）**，#4B87D9 偏灰（反而"暗"）
   ⇒ 采纳第一版：**进行中 = 主色 #4F46E5 色带 + 白心主色圆环**；
      **已完成 = #A29DED 浅色带 + #7F77DD 实心圆点**（圆环 vs 圆点这层区分本来就有 ✓）
   ⚠️ 橙色/绿色/红色（我 2026-09-27 自作主张加的）**一律不要**（用户：「不要给我加各种颜色」）✗
   ⚠️ 只有 `.is-bad`（到货质检有不合格）是我新增的状态类 ⇒ 这里给它**跟进行中同款**（不引入杂色）✓
   ============================================================================ */
#ptSteps .step.is-bad .step-dot { background: transparent; border: 2px solid var(--primary, #4F46E5); color: var(--primary, #4F46E5); }
#ptSteps .step.is-bad .step-label { color: var(--primary, #4F46E5); font-weight: 600; }
#ptSteps .step.is-todo .step-label { color: var(--c-gray); font-weight: var(--fw-regular); }
/* 子状态（供应商发货 / 合格 …）做成小胶囊，不和步骤名糊在一起；已完成 与 当前 同色 */
#ptSteps .step-sub { margin-top: 3px; padding: 1px 7px; border: 1px solid var(--line);
  border-radius: var(--btn-radius); background: var(--chip); color: var(--muted); font-weight: var(--fw-regular); }
#ptSteps .step.is-done .step-sub,
#ptSteps .step.is-active .step-sub { background: var(--primary-soft); border-color: var(--primary-soft-2); color: var(--primary); font-weight: var(--fw-medium); }
.steps .step-label { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.3; }
.steps .step-sub { font-size: 11px; color: var(--muted); font-weight: 400; }
.steps .step.is-void .step-dot { background: #EF4444; border-color: #EF4444; color: #fff; }
.steps .step.is-void .step-label { color: #EF4444; font-weight: 700; }
/* 非编辑（锁定）状态：字段**不要输入框底色/边框**，看起来就是普通文字（用户 2026-09-16）
   —— 原来给了 var(--bg-list) 灰底，视觉上像"还能编辑但变灰"，用户不要 */
.pt-locked .pt-ro,
.pt-locked .po-in,
.pt-locked .po-in.is-readonly,
.pt-locked .pi-in,
.pt-locked .pi-table .pi-in,
.pt-locked select.pt-ro,
.pt-locked input.pt-ro,
.pt-locked textarea.pt-ro {
  background: transparent !important;
  background-color: transparent !important;
  border-color: transparent !important;
  color: var(--text) !important;
  -webkit-text-fill-color: var(--text) !important;
  box-shadow: none !important;
}
/* 锁定态禁止交互（select 没有 readonly，只能靠 pointer-events 拦住） */
.pt-locked .pt-ro, .pt-locked .po-in, .pt-locked .pi-in { cursor: default; pointer-events: none; }
/* **例外：采购明细的「物流状态」列**（用户 2026-09-19 21:2x）—— 采购子状态 / 订单「采购状态」现在
   完全由它驱动（见 purchaseStage.recompute）→ 锁定态下也必须能改，否则单据一提交就再也推不动了
   （`_items.ejs` 给该列加 `.pi-unlock`、且不加 `.pt-ro`；这里用更高特异性把 pointer-events 放回来）*/
.pt-locked .pi-in.pi-unlock,
.pt-locked select.pi-unlock { pointer-events: auto !important; cursor: pointer; }
/* 「采购明细」面板豁免整单锁（用户 2026-09-22 20:4x「采购明细…依旧不支持编辑」）——
   `_orderCard.ejs` / `_items.ejs` 给这些控件加 `.pi-unlock`；这里把 pointer-events 放回来
   （特异性 0,4,0 高于 `.pt-locked .pi-in`，再加 !important 双保险）*/
.pt-locked .dt-panel[data-dt-tab="items"] .pi-unlock { pointer-events: auto !important; cursor: auto; }
/* 🆕 「生产加工」面板里的「生产计划」字段也要能解开（用户 2026-09-27 R24：
   已入库 ≥1 条 ⇒ 生产处理解锁 ⇒ 生产计划可编辑；豁免器见 `_items.ejs` 的 `unlockItemsPanel()`）——
   与采购下单面板同一套：只有打了 `.pi-unlock` 标记的控件把 pointer-events 放回来 ✓
   ⚠️ 「计划天数」是自动算字段，豁免器**不会**给它打 `pi-unlock` ⇒ 它保持只读 ✓ */
.pt-locked .dt-panel[data-dt-tab="prod"] .pi-unlock { pointer-events: auto !important; cursor: auto; }
/* 被豁免的「生产计划」行 ⇒ **日历入口（dp-trigger）也要放回来** ——
   上面 `.pt-locked .pi-dp .dp-trigger { display:none !important }` 会把日历图标藏掉，
   只解锁输入框的话用户会「能改但点不开日历」✗（用户 2026-09-27）
   ⚠️ 只对打了 `.pi-unlock` 的**行**生效 ⇒ 没豁免的行仍然保持只读观感 ✓ */
.pt-locked .dt-panel[data-dt-tab="prod"] .form-row.pi-unlock .dp-trigger { display: inline-flex !important; }
/* 🔴 被豁免的「生产记录」明细行里的**下拉（select）**也一样 ——
   原生 select 被 `csel.js` 换成 `.csel > select.csel-native + button.csel-trigger`，
   用户点的是**触发器**；触发器的类名是绑定时抄的 ⇒ 必须由 JS 单独给它打 `.pi-unlock`
   （见 `_items.ejs` 的 `_unlockBox`），这里再把 pointer-events 放回来 ✓
   否则用户侧表现＝「生产记录解锁了，但当前生产进度还是选不了」✗（2026-09-27） */
.pt-locked .dt-panel[data-dt-tab="prod"] .csel-trigger.pi-unlock { pointer-events: auto !important; cursor: pointer; }
.pt-locked .dt-panel[data-dt-tab="prod"] .csel.pi-unlock { pointer-events: auto !important; }
/* 🔴🔴 「生产加工」面板**整面板解锁**（用户 2026-09-27：「要对整个生产加工的**所有操作按钮**进行解锁」）——
   为什么必须改成给**面板**打类、而不是继续逐元素打 `.pi-unlock`：
     实测（`_probe_prodctl.js 43`）：点「添加明细」**新加的那一行**，
     「当前生产进度」的 select `pointer-events:none`、而它的 `class` 是**空的**（没有 `.pi-unlock`）✗✗
     ⇒ 逐元素的做法**天生盖不住后加的行**：解锁只在加载时跑（以及 MutationObserver 补跑），
       可新行的控件是异步渲染出来的，补跑那一趟要么早于渲染、要么被随后的重渲染冲掉
       ⇒ 用户侧表现＝「历史行能选、新增的行点不动」（2026-09-27 第二次踩）
   改法：由 JS 给面板本身加 `.pt-prod-on`，CSS 在这里一次性放行面板内**所有**控件
     ⇒ 任何行（含之后新增的）自动生效，不必再跟渲染时序赛跑 ✓
   特异性 0,4,0 + !important，稳过 `.pt-locked .pi-in`（0,2,0）✓
   ⚠️ 只解决 **pointer-events**；`readOnly` / `disabled` / `.dt-ro` 这类 CSS 管不到的，
     仍需 `_items.ejs` 的 `unlockItemsPanel()` 逐元素清（两者配合，缺一不可）✓ */
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on .pi-in,
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on select,
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on input:not([type="hidden"]),
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on textarea,
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on .csel,
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on .csel-trigger,
.pt-locked .dt-panel[data-dt-tab="prod"].pt-prod-on button {
  pointer-events: auto !important;
  cursor: auto;
}
/* 🆕 「出货记录」面板「已解锁」态（用户 2026-09-27：「解锁了出货记录，但是我一直不能编辑
   出货日期和备注字段」）——
   与生产面板**同一个坑**：服务端那边日期控件没加 `is-ro`、备注也没 `readonly`（渲染是对的），
   但 `.pt-locked` 那套锁定样式照样按着它：
     · 日期控件的**日历入口**（`.dp-trigger`）被 `.pt-locked .pi-dp .dp-trigger { display:none }` 藏掉
       ⇒ 输入框再"可编辑"也点不开日历，用户看着就是"不能编辑"✗
     · 面板里的控件还有 `pointer-events: none`
   ⇒ 与生产面板同款做法：由 JS 给**面板**打一个类（`.pt-prod-on`，判据用面板自己的 `data-open`），
      CSS 在这里统一放行 ⇒ 之后任何渲染变化都自动生效 ✓ */
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on .dp-trigger { display: inline-flex !important; }
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on .pi-in,
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on select,
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on input:not([type="hidden"]),
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on textarea,
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on .dp-display,
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on .csel,
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on .csel-trigger,
.pt-locked .dt-panel[data-dt-tab="ship"].pt-prod-on button {
  pointer-events: auto !important;
  cursor: auto;
}
/* 兜底：`#shipRec` 这颗卡片**不在** `.dt-panel` 里也能命中（它自己就在 ship 面板内，双保险）*/
.pt-locked #shipRec.pt-prod-on .dp-trigger { display: inline-flex !important; }
.pt-locked #shipRec.pt-prod-on input:not([type="hidden"]),
.pt-locked #shipRec.pt-prod-on textarea,
.pt-locked #shipRec.pt-prod-on button { pointer-events: auto !important; cursor: auto; }
/* 锁定（非编辑）状态：**连浏览器原生控件图标也去掉**（用户 2026-09-16：箭头/日历图标还显示着干嘛）
   —— select 的下拉箭头、input[type=date] 的日历图标、number 的上下箭头，全关 */
.pt-locked select,
.pt-locked input[type=date],
.pt-locked input[type=number],
.pt-locked input[type=month],
.po-in.is-readonly,
select.po-in.is-readonly,
select.pt-ro {
  appearance: none !important;
  -webkit-appearance: none !important;
  background-image: none !important;
}
.pt-locked select::-ms-expand { display: none !important; }
.pt-locked input[type=date]::-webkit-calendar-picker-indicator,
.pt-locked input[type=month]::-webkit-calendar-picker-indicator,
.po-in.is-readonly::-webkit-calendar-picker-indicator { display: none !important; opacity: 0 !important; width: 0 !important; }
.pt-locked input[type=number]::-webkit-inner-spin-button,
.pt-locked input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none !important; margin: 0; }
/* 锁定（非编辑）状态：多行文本框右下角的「缩放小三角」也去掉（用户 2026-09-16 截图反馈） */
.pt-locked textarea,
.pt-locked .po-in.is-readonly,
.pt-locked .pi-in,
textarea.po-in.is-readonly { resize: none !important; }
/* 明细表里所有多行文本框也不给拖拽缩放（表格里缩放会撑坏布局） */
.pi-table textarea { resize: vertical; }
.pt-locked .pi-table textarea { resize: none !important; }
.pt-locked select.pt-ro { appearance: none; -webkit-appearance: none; padding-left: 2px; }
/* 明细表的竖分割线与表头线保留（那是表格本身的结构线，不是输入框的） */
.pt-locked .pi-table th, .pt-locked .pi-table td { border-right-color: var(--line) !important; }
.pt-locked .pi-table tbody td { border-top-color: var(--line) !important; }
.pt-locked .pi-table th { border-bottom: 1px solid var(--line) !important; }
.pt-locked .pi-table select.pt-ro, .pt-locked .pi-table input.pt-ro { background: var(--bg-list); }
/* 明细表 = 真正的表格样式（用户 2026-09-16）：单元格之间不留额外间隙 + 竖分割线 */
.pi-table th, .pi-table td { border-right: 1px solid var(--line); padding: 3px 6px; }
/* 明细表**锁宽**（用户 2026-09-19 22:1x：「明明没有超屏，下方却出现了滑动杆」）——
   根因：`.data-table th/td { white-space: nowrap }` ⇒ 每列的**最小宽 = 内容全宽**，
   所有列 min-content 之和必然大于容器 → 即使 `.data-table { width:100% }` 也**被内容撑破**
   （实测：容器可用 1153px，表格被撑到 1168px，超 15px → 冒出一根横向滚动条 ）
   ⇒ `table-layout: fixed` 让 `width:100%` **严格生效**：列宽 = colgroup 指定的固定列 + 其余**平分剩余**，
      永不溢出（单元格已有 `overflow:hidden`，超出会截断；想调宽直接拖表头分隔线 ） */
.pi-table { table-layout: fixed; }
.pi-table th:last-child, .pi-table td:last-child { border-right: 0; }
.pi-table tbody td { border-top: 1px solid var(--line); }
/* 用户 2026-09-16 反馈「单元格文字比列名小不少」——根因：格子里显示的是 input/select，
   浏览器对表单控件默认用 13px（不继承 td 的 14px）→ 这里显式统一为 14px */
.pi-table th, .pi-table td,
.pi-table .pi-in, .pi-table select, .pi-table input, .pi-table textarea { font-size: var(--fs-14); }
.pi-table select.pi-in { line-height: 1.5; }
/* 卡片内衬统一（用户 2026-09-16 反馈间隙问题）：「货品添加」卡片原来 padding=0（内衬只有 1px），
   另两张卡片是 15px → 这里把表头条 / 总计行的左右内衬对齐到 14px （表格本体仍占满宽度 ） */
/* 用户 2026-09-16：「货品添加」要**完全左对齐**（不要左边那截间隔）→ 操作条左内衬归 0，
   表格首列只留单元格自身 6px；右侧保留 14px（合计 / +添加一行 不贴边 ） */
.pi-card .pi-bar { padding-left: 0; padding-right: 14px; }
.pi-card .pi-table th:first-child, .pi-card .pi-table td:first-child { padding-left: 6px; }
.pi-card .pi-table th:last-child, .pi-card .pi-table td:last-child { padding-right: 14px; }
.pi-card .pm-empty { padding-left: 0; padding-right: 14px; }
/* 「备注 + 创建人 / 创建时间」区（在货品明细卡片内、总计行下方，用户 2026-09-16 截图样式） */
.pi-foot { flex: 0 0 auto; padding: 8px 14px 10px 6px; border-top: 1px solid var(--line); }
.pi-foot-row { display: flex; align-items: flex-start; gap: 8px; }
.pi-foot-k { flex: 0 0 auto; color: var(--muted); font-size: var(--fs-13); line-height: 30px; }
.pi-foot-input { flex: 1 1 auto; min-height: 30px; }
.pi-foot-meta { display: flex; gap: 28px; margin-top: 6px; color: var(--muted); font-size: var(--fs-13); }
.pi-foot-meta b { color: var(--text); font-weight: 500; }
/* 「货品添加」卡片高度 = **CSS 变量 `--dt-card-h`**（用户 2026-09-18 20:31 定 450 → 23:20 改成按张数算：
   1 张明细卡 = 600、2 张 = 每张 300 …；变量由 `views/entity/form.ejs` 按页面上明细卡张数算好后写在表单容器上
   没这个变量的场景（详情页等）兜底 600）。不管多少行都不影响页面高度，行多了只在卡片里滚动 */
/* 高度**不再固定**（用户 2026-09-19 19:3x：「有 1 行数据就增加 1 行高度，最多 5 行后出现下拉滑动」）——
   改为「内容自然高 + 表格区上限 5 行」：`.pi-wrap` 的 max-height 由 public/js/cardCollapse.js
   的 fitDetailCards() 按**实际行数**算出（表头 + 33×行数 + 合计），超过就出滚动条
   `--dt-card-h` 已不再是高度（保留变量，别再用它设 height） */
.pi-card { height: auto; display: flex; flex-direction: column; overflow: hidden; }
.pi-card .pi-bar { flex: 0 0 auto; }
.pi-card .pi-wrap { flex: 1 1 auto; min-height: 0; overflow: auto; max-height: 252px; }   /* 252 = 表头36 + 36×5 + 合计36：JS 未跑时的兜底 */
/* 滚动时表头吸顶（表头要挡住下面的行，所以给不透明底色） */
.pi-card .pi-table thead th { position: sticky; top: 0; z-index: 3; background: var(--bg-list); color: var(--ink-2); }
   /* 「列名称那一行」= **表头行**，保持 `var(--bg-list)` **灰底** ——
      用户 2026-09-19 22:43 明确「列名称那一行颜色没有改回去啊，也是白色了」→ **改回灰底**。
      用户要变白的是「**采购明细 / 入库明细所在行**」= 操作栏 `.pi-bar`（见下面那条），**不是表头** */
.pi-card .pi-table tfoot td { position: sticky; bottom: 0; z-index: 3; }
.pi-card .pm-empty { flex: 0 0 auto; padding: 8px 14px; }
/* 操作列 / 物流到货处理列：**内容距单元格左右各 8px，两个按钮之间保留 8px 间隙**（用户 2026-09-16 指定）
   列宽 = 8 + 内容宽 + 8：操作列 8+22+8+22+8=68px；物流到货处理列 8+70+8+70+8=164px */
.pi-table .pi-op2 { white-space: nowrap; text-align: center; padding-left: 8px; padding-right: 8px; }
/* 「快捷录入」列（用户 2026-09-18）：**一个区块一个按钮**。
   按钮宽度 = **文字宽（不撑满整列）**，相邻间隔 8px、整列居中；区块多了自然换行（上下排）。
   别写 `display:block; width:100%` —— 那会把按钮从「约 70px」撑成「整列 176px − 16px = 160px」，
      并把原本并排的两个按钮压成上下两行（用户 2026-09-18 报「物流处理和到货处理的宽度变了」，就是它 ）。
   列宽口径仍是上面那条：8 + 70 + 8 + 70 + 8 = 164px（≈176）→ 正好并排放两个 4 字按钮 */
.pi-table .pi-grp-cell { white-space: normal; }
.pi-table .pi-grp-cell .pi-grp { display: inline-block; width: auto; margin: 0 4px; }   /* 4+4 = 中间 8px */
/* 「列设置」里标注「这个字段属于哪个按钮区块」的小灰标 */
.pi-col-grp { font-style: normal; font-size: var(--fs-12); color: var(--muted); margin-left: 6px; }
/* 只在「两个按钮之间」加间隙（首尾不加，否则会挤偏到边线的 8px ） */
.pi-table .pi-op2 .pi-ico + .pi-ico,
.pi-table .pi-op2 .btn + .btn { margin-left: 8px; }
/* 这几条旧的 outer margin 会把「距左右 8px / 间隙 8px」挤偏（+2px）→ 归零 */
.pi-table .pi-op2 .pi-ico, .pi-table .pi-op2 .btn { margin: 0; }
.pi-op2 .pi-ico { margin: 0 2px; vertical-align: middle; }
.pi-op2 .btn { margin: 0 1px; }
/* 前 3 列固定（用户 2026-09-16：操作列固定在序号列右侧、物流到货处理也固定）——横向滚动时钉住
   列宽（29 / 80 / 176px）改了就同步改这里的 left（0 / 29 / 109） */
/* 第 1 列 = **展开圆圈**（用户 2026-09-21 17:0x：展开按钮和 # 拆成两列）——
   冻结偏移跟着重算：展开 0(28 宽) → # 28(29 宽) → 操作 57(80 宽) */
.pi-table th:nth-child(1), .pi-table td:nth-child(1) { position: sticky; left: 0; z-index: 4; }
.pi-table th:nth-child(2), .pi-table td:nth-child(2) { position: sticky; left: 28px; z-index: 4; }
.pi-table th:nth-child(3), .pi-table td:nth-child(3) { position: sticky; left: 57px; z-index: 4; }
.pi-table tbody td:nth-child(-n+3) { background: var(--card); }
.pi-table thead th:nth-child(-n+3) { z-index: 6; background: var(--bg-list); }
   /* 前 3 列表头必须与其余表头**同色**（都灰），否则同一行会颜色割裂
      用户 2026-09-19 22:43：「列名称那一行…也是白色了」→ 跟着表头一起**改回灰底** */
.pi-table tfoot td:nth-child(-n+3) { background: var(--bg-list); }
.pi-op-h { text-align: center; }
.pi-op2 { white-space: nowrap; text-align: center; }
.pi-op2 .pi-ico { margin: 0 2px; vertical-align: middle; }
.pi-ico svg { display: block; margin: 0 auto; }
/* 弹窗里的分组小标题（物流信息 / 到货信息） */
.pi-sec { font-size: var(--fs-13); color: var(--muted); border-left: 3px solid var(--primary); padding-left: 8px; margin-bottom: 6px; }
/* 合计表（滚动区之外的那一行）：只露出与上方对齐的一行合计，横向溢出裁掉
   必须 border-collapse:collapse —— 否则默认的分离边框会给每列加 2px border-spacing（14 列 = 28px 偏移）→ 列全错位 */
/* 合计行的**代理滚动容器**（用户 2026-09-21 18:3x：「滚动条在总计的上方，不是下方」）——
   合计行待在滚动区外（横条才落在合计行上方），靠 JS 把主表 scrollLeft 同步进来 ⇒ 一起滚；
   `overflow: hidden` ⇒ 自己不显示横条，但**仍可被程序设 scrollLeft** ⇒ sticky 也相对它生效 */
.pi-total-clip { overflow: hidden; }

/* 这里**必须** `overflow: visible` —— 合计行的「总计」格靠 `position: sticky` 冻结（用户 2026-09-21 18:3x），
   而 `overflow: hidden` 会把 sticky 的包含块变成**这张表自己**（表本身不滚动）⇒ sticky 直接失效
   （实测：写 hidden 时「总计」横滚跟着走 left 401→201；改 visible 后钉住 ） */
.pi-total-table { margin: 0; overflow: visible; border-collapse: collapse; border-spacing: 0; }
.pi-total-table td { border-top: 1px solid var(--line); border-right: 1px solid var(--line); }
.pi-total-table td:last-child { border-right: 0; }
/* 「列设置」面板（用户 2026-09-16）：勾选显隐 + ↑↓ 调顺序 */
.pi-tools { position: relative; }
.pi-colset {
  position: absolute; right: 0; top: 100%; z-index: 30; display: none;
  width: 300px; max-height: 420px; overflow: auto; padding: 8px 10px;
  background: var(--card); border: 1px solid var(--line); box-shadow: 0 6px 20px rgba(15,23,42,.12);
}
.pi-colset.open { display: block; }
.pi-colset-head { display: flex; align-items: center; gap: 6px; font-size: var(--fs-13); color: var(--muted); padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.pi-colset-list { padding: 4px 0; }
.pi-colset-item { display: flex; align-items: center; gap: 4px; padding: 3px 0; font-size: var(--fs-13); }
.pi-colset-item label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.pi-colset-foot { padding-top: 6px; border-top: 1px solid var(--line); }
/* 明细「列设置」弹窗里的拖动落点提示（复用列表页 .fv-list ） */
#piColList .fv-item.is-drop { box-shadow: inset 0 2px 0 var(--primary); }
#piColList .fv-item[draggable="true"] { cursor: default; }
/* 自定义控件必须**显式写主题文字色**：不写就是浏览器默认的黑色，
   暗色模式下完全看不见（用户 2026-09-16 报「所有输入内容都还是黑色」）。所有单据/弹窗控件统一在这里兜住 */
.pi-in, .po-in, .pi-foot-input, .pi-colset input,
.pi-f input, .pi-f select, .pi-f textarea,
.pi-table input, .pi-table select, .pi-table textarea,
.to-panel input, .to-panel select, .to-panel textarea {
  color: var(--text);
  background: var(--field-bg);
}
.pi-in::placeholder, .po-in::placeholder, .pi-foot-input::placeholder { color: var(--muted); }
/* 下拉展开的选项列表底色也要跟上主题（否则暗色下白底黑字/看不清） */
select.pi-in option, select.po-in option, .pi-f select option, .to-panel select option { background: var(--card); color: var(--text); }
/* 日期控件：与系统一致（可点选、带日历图标 ），暗色下图标反色 */
input[type=date].pi-in, input[type=date].po-in, .pi-f input[type=date], .to-panel input[type=date] {
  color: var(--text);
  background: var(--field-bg);
  border: 1px solid var(--field-border);
}
[data-theme="dark"] input[type=date]::-webkit-calendar-picker-indicator { filter: invert(1) opacity(.75); }
/* 明细表里的控件：底色透明、靠单元格分割线（沿用表格式样 ） */
.pi-table .pi-in { background: transparent; }
/* 采购里的日期统一用系统标准日期选择器（.dp-field ）：这里只补宽度与表格内的适配 */
.pi-dp { width: 100%; }
.pi-dp .dp-display { width: 100%; color: var(--text); text-align: center; }
.pi-table .pi-dp .dp-display { background: transparent; border: 0; }
.pi-dp .dp-trigger { color: var(--muted); }
/* 非编辑（锁定）状态：日期选择器整体不可点
   并且**隐藏日历图标**（用户 2026-09-18 17:55 截图问「为什么在明细表中，这个日期格式，
   在非编辑状态，还要显示日期标识？」）——
   日历图标（`.dp-trigger`）的语义是「点这里选日期」，只读时留着一个点不动的图标纯属误导
   → 只读态只留纯文字（`2026年9月16日`）
   锁定类挂在 `body` 上（`pt-locked` 由 `_orderCard.ejs` 加、`pi-locked` 由 `_items.ejs` 加）→ 两个都写上，
   免得只改一个时另一种态还露着图标。 */
.pt-locked .pi-dp,
.pi-locked .pi-dp { pointer-events: none; }
.pt-locked .pi-dp .dp-trigger,
.pi-locked .pi-dp .dp-trigger { display: none !important; }
/* 同一个道理的连带项：只读态**空日期**不要显示 placeholder「请选择日期」——
   它与「空就是空值，不要占位符」（用户 2026-09-18 15:25 定的全站规范）相冲突，也会让人以为要填
   （`::placeholder` 变透明而不是改文字，值仍原样保留、编辑态一切照旧 ） */
.pt-locked .pi-dp .dp-display::placeholder,
.pi-locked .pi-dp .dp-display::placeholder { color: transparent; }
/* 可搜索的供应商选择器（用户 2026-09-16）：显示 简称 / 全称 / 评级，支持输入过滤 */
.sup-pick { position: relative; }
.sup-pick .sup-in { width: 100%; }
.sup-list {
  position: absolute; left: 0; top: 100%; z-index: 40; display: none;
  min-width: 260px; max-height: 260px; overflow: auto; text-align: left;
  background: var(--card); border: 1px solid var(--field-border); box-shadow: 0 8px 24px rgba(15,23,42,.16);
}
/* 面板「搬到 body」后就匹配不到 `.sup-pick.is-open .sup-list` 了 →
   所以列表自己带一个 `.open` 类（两条都留着，兼容两种状态 ） */
.sup-pick.is-open .sup-list, .sup-list.open { display: block; }
.sup-item { display: flex; align-items: center; gap: 8px; padding: 5px 10px; cursor: pointer; font-size: var(--fs-13); }
.sup-item:hover { background: var(--chip); }
.sup-item b { color: var(--text); font-weight: 500; }
.sup-item span { color: var(--muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sup-rate { font-style: normal; flex: none; padding: 0 6px; border: 1px solid var(--line); color: var(--muted); }
.sup-none { padding: 6px 10px; color: var(--muted); font-size: var(--fs-13); }
.pt-locked .sup-list { display: none !important; }
/* 列名与列内容**统一居中**（用户 2026-09-16）—— 表头、单元格、数字列、合计，以及框内文字都居中 */
.pi-table th, .pi-table td,
.pi-table .pi-num, .pi-table .pi-calc, .pi-table .pi-total-row td { text-align: center; }
.pi-table .pi-in, .pi-table input, .pi-table select, .pi-table textarea { text-align: center; }
.pi-table select.pi-in { text-align-last: center; }
/* 列宽可拖拽（用户 2026-09-16）：表头右侧一条可拖的竖线，拖动改列宽 */
/* 明细表里的控件不能有 min-width：列拖到最窄时它会溢出、压到隔壁列上（用户 2026-09-16 截图）
   → 控件宽度完全跟随列宽（100%），单元格 overflow:hidden 兜底，宁可裁掉也不重叠 */
.pi-table .pi-in,
.pi-table input.pi-in, .pi-table select.pi-in, .pi-table textarea.pi-in { min-width: 0 !important; }
/* 「产品规格」= **输入框 + 候选列表**（可点选、可搜索、**也可直接手打任意值** 用户 2026-09-16）
   «产品规格就是带出来了，也支持手动填写，不受带出来的值彻底约束» → 故意不用 `<select>`（会把人限死）
   候选列表和供应商下拉一样会被 `<td>{overflow:hidden}` 裁掉 → 打开时由 JS 搬到 body + fixed */
.pi-table .pi-spec { display: block; position: relative; }
.pi-table .pi-spec > input { width: 100%; }
.spec-list {
  position: absolute; left: 0; top: 100%; z-index: 40; display: none;
  min-width: 160px; max-height: 240px; overflow: auto; text-align: left;
  background: var(--card); border: 1px solid var(--field-border); box-shadow: 0 8px 24px rgba(15,23,42,.16);
}
.spec-pick.is-open .spec-list, .spec-list.open { display: block; }
.spec-item { padding: 5px 10px; cursor: pointer; font-size: var(--fs-13); color: var(--text); }
.spec-item:hover { background: var(--chip); }
.spec-none { padding: 6px 10px; color: var(--muted); font-size: var(--fs-12); line-height: 1.5; }
.pt-locked .spec-list { display: none !important; }
/* 「税率」是百分比 → 数字后面带 %（与系统列表里 percent 字段的 `5%` 一致 ） */
.pi-table .pi-pct { display: flex; align-items: center; justify-content: center; gap: 2px; }
.pi-table .pi-pct > input { flex: 1 1 auto; min-width: 0 !important; width: auto; text-align: right; }
.pi-table .pi-pct > i.pi-pct-sym { flex: none; font-style: normal; color: var(--muted); font-size: var(--fs-12); }
/* 金额列的**币别符号前缀**（用户 2026-09-20 09:19：明细金额列前面要有币别符号）——
   与 `.pi-pct`（百分号后缀）同一套做法：符号是单元格里的一个 `<i>`，跟着值走、锁定时也还在
   **字号 / 颜色必须与「总价 / 采购成本」里的符号一致**（用户 2026-09-20 09:24：
   「含税单价的货币符号大小和总价以及采购成本的货币符号大小不一致」）——
   所以这里**只留 `flex: none` + 去掉斜体**，字号与颜色都**继承单元格**
   （= 14px + 正文色，跟 `.pi-calc` 里那串 `¥4606.80` 完全同款）
   别再写 `font-size: var(--fs-12)` / `color: var(--muted)` —— 那就是"不一致"的根源 */
.pi-table .pi-cur { display: flex; align-items: center; justify-content: center; gap: 2px; }
.pi-table .pi-cur > input { flex: 1 1 auto; min-width: 0 !important; width: auto; text-align: center; }
.pi-table .pi-cur > i.pi-cur-sym { flex: none; font-style: normal; }
/* 锁定（非编辑）态：% 也跟着变灰、保持可见 */
.pi-locked .pi-card .pi-pct > i.pi-pct-sym { color: var(--muted); }
.pi-table th, .pi-table td { overflow: hidden; }
.pi-table .pi-wrap, .pi-table td { text-overflow: ellipsis; }
/* 表格总宽**锁 100%**（用户 2026-09-19 22:1x：「明明没有超屏，下方却出现了滑动杆」）——
   历史：曾为了让「拖列宽时其它列不跟着重排」而设 `width: auto`（总宽 = 各列宽之和），
   代价是**总宽会超出容器** → 冒出一根没必要的横向滚动条 （实测容器可用 1153px、表格被撑到 1168px）
   ⇒ 改回 `width: 100%`：配合 `table-layout: fixed`，总宽恒等于容器宽、**永不横向溢出**
      （副作用：拖某一列时其余列会被重排 —— 这是 fixed 布局的固有行为，但比多一根滚动条好） */
.pi-table { table-layout: fixed; width: 100%; max-width: 100%; min-width: 0; }
/* 数据行**固定 33px**（用户 2026-09-16）：表格不再用 height:100% 撑满（那样浏览器会把多余高度平摊给数据行 ），
   多余高度改由脚本算出来给「占位行」（.pi-fill-body）→ 总计行仍贴底、数据行恒 33px */
.pi-table #piBody tr, .pi-table #piBody td { height: 36px; }   /* 行高 36：用户 2026-09-21 18:4x 给的目标值 */
/* 采购明细表头 / 合计行也统一 36（用户 2026-09-21 18:4x）——
   表头原来靠通用的 `.data-table th{height:32px}`，别去改那条（订单列表等也在用）⇒ 这里作用域化 */
.pi-table thead th { height: 36px; }
.pi-total-table td { height: 36px; }
.pi-table #piBody td { padding-top: 0; padding-bottom: 0; }
.pi-table #piBody .pi-in { height: 24px; }
.pi-table .pi-fill-body td { padding: 0; border: 0; }
.pi-table th { position: relative; overflow: hidden; text-overflow: ellipsis; }
.pi-resizer {
  position: absolute; top: 0; right: -3px; width: 7px; height: 100%;
  cursor: col-resize; z-index: 2;
}
.pi-resizer:hover, .pi-col-dragging .pi-resizer { background: var(--primary); opacity: .45; }
body.pi-col-dragging, body.pi-col-dragging * { cursor: col-resize !important; user-select: none !important; }
/* 列名**加粗**（用户 2026-09-16：明确要求加粗；之前我理解反了去掉了 ）
   系统里 --fw-medium 一直是**未定义**的（之前写 var(--fw-medium) 是无效声明 ）→ 这里补上 600 */
.pi-table th { font-weight: var(--fw-medium, 600); font-weight: 600; }
.pi-table .pi-in { border: 0; background: transparent; padding: 3px 4px; border-radius: 0; }
.pi-table .pi-in:focus { outline: 2px solid var(--primary); outline-offset: -2px; }
.pi-locked .pi-card .pi-in { background: transparent; color: var(--muted); }
.pi-locked .pi-card select.pi-in { -webkit-text-fill-color: var(--muted); }

/* 明细表：字段之间不留额外间隙，用竖分割线区分（用户 2026-09-16） */
.pi-card .pi-wrap { overflow: auto; }
/* 采购表单里有工具条（#ptBar）时，表单自带的那排「取消 / 保存」不显示 —— 动作统一在顶部工具条
   用 :has() 在 CSS 里兜底（比脚本更早生效，不会闪一下；脚本仍会再隐藏一次 ） */
.edit-form:has(#ptBar) .form-actions { display: none !important; }
.form-flat { padding: 0; border: 0; background: transparent; }
/* 标题行最右侧的「系统编号」 */
.form-title-no { display: inline-flex; align-items: baseline; gap: 8px; font-size: var(--fs-14); }
.form-title-no .po-v { font-weight: var(--fw-medium); }
/* 首行色带里的「系统编号」**任何浏览器宽度下都不换行**（用户 2026-09-21 12:5x 截图：
   窄窗口下 `CGDD-20260920000001` 被折成两行 ）——
   · 原因：`.pt-bar` 是 `flex-wrap: wrap` 的弹性行，窗口变窄时**被压缩的是编号这一格**（flex 默认可收缩）
     —— 网格里的短横让浏览器有了断行点，于是折成「CGDD-」/「20260920000001」
   · 修法：编号那格 `flex: 0 0 auto`（不参与收缩）+ 自身 / 列名 / 值全部 `nowrap` ⇒ 宽度不够时
     由整行去换行（`.pt-bar` 本来就 wrap，按钮会整体落到下一行），编号永远是一整行
   订单中心详情首行的 `.ord-bar` 用的是同一套 `.form-title-no` 结构 → 一并受益 */
.form-title-no { flex: 0 0 auto; }
.form-title-no, .form-title-no .po-k, .form-title-no .po-v { white-space: nowrap; }
/* 工具条「采购编号 / 订单编号」的**标签与值之间只留 8px**（用户 2026-09-22：「采购编号与编号值间隔 8，
   订单编号与编号值间隔 8」）——`.form-title-no` 本身已是 `gap: 8px`，但基础 `.po-k` 带 `flex: 0 0 72px`
   （那是**卡片字段行列名**的列宽）⇒ 标签被撑成 72px 固定宽（4 个字才 56px，右侧白留 16px）
   ⇒ 量出来是 **24px**（16 空白 + 8 gap）⇒ 标题行里的标签改成**贴字宽**，8px 才是真间距 */
.form-title-no .po-k { flex: 0 0 auto; }
@media (max-width: 1100px) { .po-grid { gap: 6px 10px; } }

.pi-card { margin: 12px 0; }
.pi-card { margin: 12px 0; padding: 0; overflow: hidden; }
/* 顶部操作条：左边一个 tab（仿参考图「票据添加」）+ 右边工具区 */
.pi-bar { display: flex; align-items: center; gap: 10px; background: var(--card); border-bottom: 1px solid var(--line); padding: 0 10px; min-height: 34px; }
   /* 操作栏改**白底**（用户 2026-09-19 22:3x：「采购明细所在行调整为白色，入库明细所在行也调整为白色」）——
      就是写着「采购明细 / 入库明细」那条 = **唯一要变白的行** 原来是 `var(--bg-list)` 灰底
      用户 22:43 澄清：「列名称那一行…也是白色了」→ **列名称行（表头）要灰底**，别跟着一起改白 */
.pi-tab { background: var(--primary); color: #fff; font-size: var(--fs-13); padding: 6px 14px; border: 0; border-radius: var(--radius-sm) var(--radius-sm) 0 0; cursor: pointer; font-family: inherit; }
.pi-tab:hover { filter: brightness(1.06); }
/* 禁用的 `.pi-tab`（卡头「生产记录」按钮等）**要变灰**（用户 2026-09-22 20:3x：
   「你不支持点击，就把生产记录按钮变灰」）——
   原来**没有** `.pi-tab:disabled` ⇒ 禁用态仍是蓝底白字，完全看不出不能点 */
.pi-tab:disabled { background: var(--bg-list); color: var(--muted); cursor: not-allowed; }
.pi-tab:disabled:hover { filter: none; }
.pi-tools { display: flex; align-items: center; gap: 12px; margin-left: auto; font-size: var(--fs-13); color: var(--link); }
/* 「转入库」方式弹窗的单选选项（用户 2026-09-19）：标题 + 说明，两块竖排，点整行可选 */
.pi-tf-opt { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer; background: var(--field-bg); margin-bottom: 10px; }
.pi-tf-opt:last-of-type { margin-bottom: 0; }
.pi-tf-opt:hover { border-color: var(--primary, #4F46E5); }
.pi-tf-opt input[type=radio] { position: absolute; opacity: 0; pointer-events: none; }
.pi-tf-opt:has(input:checked) { border-color: var(--primary, #4F46E5); background: color-mix(in srgb, var(--primary, #4F46E5) 8%, var(--field-bg)); }
.pi-tf-t { font-size: var(--fs-14); font-weight: var(--fw-medium); color: var(--text); }
.pi-tf-d { font-size: var(--fs-12); color: var(--muted); line-height: 1.5; }
/* 总计行 */
.pi-total-row td { background: var(--bg-list); font-weight: var(--fw-medium); border-top: 1px solid var(--line); }
/* 行内操作：⊕ 加行 / 删行（仿参考图的操作列） */
/* 行内操作图标按钮（明细「操作」列的 ＋ / ）：**无边无底 = 两个裸图标**，
   图标色 = 用户参考图实测 **#4B87D9**（用户 2026-09-16：「按照这两个图标，完全一致包括颜色」；
   原来是「有边框 + 底色」的方块、且垃圾桶按 .danger 渲染成**红色** ） */
.pi-ico { width: 22px; height: 22px; line-height: 1; padding: 0; border: 0; border-radius: 0; background: transparent; color: #4B87D9; cursor: pointer; }
/* `.danger` 类**只保留语义**（JS 靠它区分删行按钮），颜色**跟随 .pi-ico**（参考图里删除也是蓝的）
   → 这里不再覆写 color，避免又变回红色 */
/* 禁用态：图标要看得出来不可点（原来是完全没写 → 已生效单据上看着还能点 ） */
.pi-ico:disabled { opacity: .45; cursor: not-allowed; }
.pi-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.pi-wrap { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: auto; }
.pi-table { width: 100%; }
.pi-table td, .pi-table th { vertical-align: middle; }
.pi-no { width: 34px; text-align: center; color: var(--muted); }
.pi-num { text-align: right; white-space: nowrap; }
.pi-op { width: 56px; text-align: center; }
.pi-table select, .pi-table input[type=text] { width: 100%; }
.pi-amount { color: var(--text); font-variant-numeric: tabular-nums; }
/* 明细行控件（用户 2026-09-16 的新字段表：仓位/供应商/产品名称/…/采购下单日期 + 物流·到货两个按钮） */
.pi-in { width: 100%; min-width: 78px; padding: 3px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--field-bg); font-size: var(--fs-13); }
.pi-narrow { min-width: 62px; width: 78px; }
.pi-op2 { white-space: nowrap; }
.pi-op2 .btn { margin-right: 4px; }
.pi-op2 .link-btn { font-size: var(--fs-13); }
/* 弹窗里的字段布局（两列） */
.pm-grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 16px; }
/* ============================================================
   到货台账（「到货处理」弹窗里的**多行表格**，用户 2026-09-20 08:47 / 09:02 定稿）
   列序：`# | 操作 | 实际到货日期 | 实际到货数量 | 剩余未到 | 单位 | 到货状态 | 质检状态 | 异常品处理方案`
   （「单位」在**剩余未到之后** —— 用户 2026-09-20 09:09 指定 ）
   · 左上角一个**有色标签按钮**「添加到货」（复用采购明细的 `.pi-tab` 样式；**不带 ＋ 号**）
   · 前两列（# / 操作）**冻结**（横滚时钉在左侧）
   · 所有单元格**上下左右居中**  · 总计行在**滚动区之外**、「总计」落在**操作列下方**
   ============================================================ */
.ar-table { width: 100%; table-layout: fixed; border-collapse: collapse; }
.ar-bar { display: flex; align-items: center; gap: 10px; min-height: 34px; }
.ar-wrap { border: 1px solid var(--line); border-top: 0; overflow: auto; max-height: min(46vh, 420px); }
.ar-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg-list); height: 36px; padding: 3px 6px;
  font-size: var(--fs-14); font-weight: 600;
  text-align: center; vertical-align: middle;
  border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); color: var(--ink-2);
}
.ar-table thead th:last-child { border-right: 0; }
.ar-table tbody td {
  height: 36px; padding: 0 6px;
  text-align: center; vertical-align: middle;
  border-right: 1px solid var(--line); border-top: 1px solid var(--line);
}
.ar-table tbody td:last-child { border-right: 0; }
.ar-table tbody tr:hover td { background: var(--bg-list); }
/* 前两列（# / 操作）**冻结**（用户 2026-09-20 09:02：「操作按钮在序号右侧，冻结」）——
   必须给**不透明底色**，否则横滚时内容会透出来 ；
   表头那两格 z-index 要更高（它们同时是 sticky top + sticky left）
   left 偏移按列宽写死：# = 0、操作 = 40px（改了 colgroup 宽度要同步改这里）*/
.ar-table th:nth-child(1), .ar-table td:nth-child(1) { position: sticky; left: 0; z-index: 3; }
.ar-table th:nth-child(2), .ar-table td:nth-child(2) { position: sticky; left: 40px; z-index: 3; }
.ar-table thead th:nth-child(1), .ar-table thead th:nth-child(2) { z-index: 5; background: var(--bg-list); }
.ar-table tbody td:nth-child(1), .ar-table tbody td:nth-child(2) { background: var(--card); }
.ar-table tbody tr:hover td:nth-child(1), .ar-table tbody tr:hover td:nth-child(2) { background: var(--bg-list); }
/* 操作列（＋ / ）= 采购明细「操作」列的标准：两个裸图标、间距 8px、左右各 8px */
.ar-table .ar-op { white-space: nowrap; text-align: center; padding-left: 8px; padding-right: 8px; }
.ar-table .ar-op .pi-ico { margin: 0; }
.ar-table .ar-op .pi-ico + .pi-ico { margin-left: 8px; }
/* 总计行：**滚动区之外**、列宽与上表逐列对齐；「总计」二字在**操作列下方** */
.ar-total { margin: 0; overflow: hidden; border-collapse: collapse; }
.ar-total td {
  height: 36px; padding: 3px 6px; font-size: var(--fs-14);
  text-align: center; vertical-align: middle;
  border-top: 1px solid var(--line); border-right: 1px solid var(--line);
}
.ar-total td:last-child { border-right: 0; }
.ar-total .ar-total-row td { background: var(--bg-list); font-weight: 500; }
.ar-table input:not([type=hidden]), .ar-table select {
  width: 100%; height: 24px; min-height: 24px; padding: 0 6px;
  border: 0; background: transparent; font-size: var(--fs-14); color: var(--text); font-family: inherit;
}
.ar-table input.is-readonly, .ar-table input[readonly] { color: var(--muted); cursor: default; }
/* 台账里**控件内的文字**也要居中（用户 2026-09-20 09:15：「实际到货数量、单位、剩余未到、到货状态、
   质检状态，未左右居中显示，依旧是左对齐」）——
   光给 `td { text-align: center }` 没用：`<input>/<select>` **默认左对齐**，
      居中要写在控件自己身上 ；采购明细靠的是 `.pi-table input/select { text-align:center }`（line 2168），
      而台账表**没有 `pi-table` 类** ⇒ 必须自己写一份（下拉还要 `text-align-last`）*/
.ar-table input, .ar-table select, .ar-table textarea { text-align: center; }
.ar-table select { text-align-last: center; }
.ar-table .ar-no { color: var(--muted); font-size: var(--fs-13); }
/* 日期控件在表格里要收窄：默认 36px 高 / 32px 图标会把行撑高 */
.ar-table .dp-field { max-width: none; }
.ar-table .dp-display { height: 26px; min-height: 26px; padding: 3px 26px 3px 6px; border: 0; background: transparent; font-size: var(--fs-14); }
.ar-table .dp-trigger { width: 24px; }
.ar-table .dp-trigger svg { width: 14px; height: 14px; }
.ar-table .ar-del { width: 22px; }
/* ⏹ 旧的底部「＋ 添加到货 + 提示文字」那行已删（用户 2026-09-20 09:02：按钮移到表格左上角、
   提示文字不要）——别再加回 `.ar-bar{margin-top:10px}` / `.ar-hint`（会盖掉上面那条 `.ar-bar`）*/

/* 台账底部「本条明细记录最终质检状态」那一行（用户 2026-09-20 09:53）—— 在**总计行上方**：
   · 标签靠左；下拉落在「质检状态」列正下方（与上面逐笔的下拉对齐）
   必须**复位 sticky / 背景**：`.ar-table tbody td:nth-child(1)/(2)` 的冻结规则会命中这一行的
      第 1 格（colspan=7）与第 2 格（下拉）⇒ 否则标签格会被当成冻结列、底色也变成 `--card`
      （选择器特异性与它相同 (0,2,2)，所以只要写在它**后面**就能压住 ） */
.ar-table tbody td.ar-final-lab { position: static; background: transparent; text-align: left; color: var(--dt-label); }
.ar-table tbody td.ar-final-val { position: static; background: transparent; }
.ar-final-val select { width: 100%; }

/* 台账里的「质检状态」下拉升级成**自定义下拉**（`/js/csel.js`，`data-csel="on"`）——
   用户 2026-09-20 09:58：「为什么这些选项值，不是左右居中呢」
   原因：展开的那一列选项是**浏览器自己画的原生列表**，网页 CSS 管不到它
      （实测：注入 `option{text-align:center}` 前后截图**完全一致** ）
   ⇒ 用项目已有的 csel 面板替代原生列表，并按台账的格子规格收一下外观：
      24px 高 / 无边框 / 透明底 / 文字居中（与上面 `.ar-table input,select{text-align:center}` 一致）*/
.ar-table .csel { display: block; width: 100%; }
.ar-table .csel-trigger,
.ar-table .csel-trigger.csel-plain {
  position: relative; width: 100%; height: 24px; min-height: 24px; padding: 0 18px 0 6px;
  border: 0; background: transparent; font-family: inherit; font-size: var(--fs-14); color: var(--text);
}
/* 文字真正居中：把箭头绝对定位到右侧，值那一段独占整宽 */
.ar-table .csel-val { justify-content: center; }
.ar-table .csel-caret { position: absolute; right: 3px; }
/* 展开面板：选项文字居中（对号也绝对定位，免得把文字挤偏）*/
.csel-panel.is-center .csel-opt { position: relative; justify-content: center; }
.csel-panel.is-center .csel-opt .csel-check { position: absolute; left: 8px; }
/* 锁定（已生效 / 已结案）态下，台账里的自定义下拉外壳也要能点 —— 与上面 `.ar-table select` 放行同一套 */
.pt-locked .ar-table .csel-trigger,
.pi-locked .ar-table .csel-trigger { pointer-events: auto !important; cursor: pointer; }
.pi-f { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-13); color: var(--muted); }
.pi-f input, .pi-f select, .pi-f textarea { width: 100%; }
.pi-f-wide { grid-column: 1 / -1; }
.to-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--line); }
@media (max-width: 700px) { .pm-grid2 { grid-template-columns: 1fr; } }

/* ---------- 采购明细的两个处理弹窗「物流处理 / 到货处理」（2026-09-16 统一到系统弹窗规格）----------
   用户反馈「弹窗小得不像样子、很难看」→ 根因：
     ① 只给了 560/680px 宽 + 控件没有高度（默认约 20px 的细条）→ 整屏看着又小又挤
     ② `.pi-f` 里的日期框被全局 `.dp-field{max-width:220px}` 压到 220px，跟同列其它控件不等宽
     ③ 底部按钮用了 `.btn-xs`，比系统其它弹窗（字段设置 / 字段显示）小一号
   → 这里统一：面板加宽 + 控件统一 34px 高 + 日期框放开限宽 + 按钮回常规尺寸 */
.pi-modal .to-head { padding: 14px 18px; }
.pi-modal .pi-modal-sub { margin-left: 12px; font-weight: 400; font-size: var(--fs-13); color: var(--muted); }
.pi-modal .to-body { padding: 18px 18px 8px; max-height: min(72vh, 620px); }
.pi-modal .pm-grid2 { gap: 14px 20px; }
.pi-modal .pi-f { gap: 6px; }
.pi-modal .pi-f input:not([type=hidden]), .pi-modal .pi-f select, .pi-modal .pi-f textarea {
  height: 34px; padding: 0 10px; font-size: var(--fs-13);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
}
.pi-modal .pi-f textarea { height: auto; min-height: 68px; padding: 7px 10px; line-height: 1.5; }
.pi-modal .pi-f select[multiple] { height: auto; min-height: 68px; padding: 3px; }
.pi-modal .pi-f .po-in.is-readonly { background: var(--bg-list); color: var(--muted); }
/* 日期选择器在弹窗里要跟同列控件等宽等高（放开 220px 限宽 ） */
.pi-modal .pi-f .dp-field { width: 100%; max-width: none; }
.pi-modal .pi-f .dp-display { height: 34px; font-size: var(--fs-13); }
.pi-modal .pm-hint { line-height: 1.6; }
.pi-modal .to-foot { padding: 12px 18px; }

/* ---------- 「转入」选择器（对齐库管王「转入单据」，整页 + 单选）----------
   用户 2026-09-16 给的参照：筛选条件 + 可勾选单据表 + 「已勾选/共 N 条」+ 分页 + 确定·取消。 */
.tp-filters { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin: 0 0 10px; }
.tp-f { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-13); color: var(--muted); }
/* 不要再给 `.tp-f input` 写裸样式 —— 原来那条 `min-width:150px` 把输入框变成了**浏览器默认外观**
   （用户 2026-09-16：「输入框也不是系统正常的样式…日期也不是系统的日期选择器样式」）
   → 文本走系统的 `.cs-input`、日期走系统的 `.dp-field`，这里只给宽度 */
/* 关键字框占满**剩余宽度**（`flex:1`）→ 搜索 / 重置 才能与它同处一行（用户 2026-09-18：
   「搜索和重置按钮需要和搜索在一行显示」）。同时能吸收不同视口下的宽度差，尽量不换行 */
.tp-f-kw { flex: 1 1 170px; min-width: 150px; max-width: 320px; }
.tp-f-kw .cs-input { flex: none; width: 100%; }
/* 日期 / 下拉收窄，保证「关键字 + 下单日期×2 + 业务员 + 团队 + 搜索 + 重置」在一行内放得下
   （弹窗内容宽 952 = 1000 − 内衬 24×2；实测 170+150+13+150+120+120+62+62+间隔 70 = 917 留 35 余量）*/
.tp-f .filter-select { flex: none; width: 120px; }   /* 业务员 / 团队 两个下拉与系统筛选栏同款 */
.tp-f .dp-field { width: 150px; }
/* 「至」与左右两个日期框**同处一行**时，文字要落在输入框的**垂直中线**上：
   输入框 32 高、文字行高 21 → 下内距 5（≈(32−21)/2）（用户 2026-09-18 起筛选行只占一行） */
.tp-sep { color: var(--muted); font-size: var(--fs-13); padding-bottom: 5px; }
/* 高度 = 表头 + **整整一页的行数**（`--tp-rows` 由 EJS 按 pageSize 注入）——
   用户 2026-09-16：「每页显示的数量，应该正好让你不进行翻页…内容框正好容纳 10 条数据」
   原为死值 420px → 比一页矮 73px，最后 1~2 行被挤进**内部纵向滚动条**
   实测（1440×900 / Edge）：表头行 41px、数据行 43px、上下边框 2px、横向滚动条预留 ~17px
   → 每页 10 行时需要 ≈495px；这里留 2px 余量（`--tp-chrome` 26px）避免出现 1~2px 的滚动条 */
.tp-table-wrap {
  --tp-row-h: 40px; --tp-head-h: 41px; --tp-chrome: 26px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: auto;
  max-height: calc(var(--tp-row-h) * var(--tp-rows, 10) + var(--tp-head-h) + var(--tp-chrome));
}
/* 转入候选弹窗（`.is-pick`）：内容本身就是「刚好一页」→ 不能再被 78vh/640px 卡出一条**外层**滚动条
   → 上限抬高，让整个弹窗自己撑开；只有屏幕实在太矮时，才由 `.to-body` 兜底滚动 */
.to-panel.is-pick .to-body { max-height: min(92vh, 820px); }
.tp-table { width: 100%; }
.tp-table thead th { position: sticky; top: 0; z-index: 1; background: var(--bg-list); }
/* 候选行高 **40**（用户 2026-09-18 19:31：「下方的选择行高，缩减到40」）——
   9 + 行高 21 + 9 + 1px 分割线 = 40
   根因：`.to-panel input { height:32px; padding:0 16px }`（全站输入框标准）把**单选框**也算进去了
   → 单选被撑成 32 高 + 上下内距，行高被顶到 **60**（实测），并且超出容器高度 → 弹窗里多出一条纵向滚动条
   → 这里把候选表里的单选恢复成**原生尺寸**（13×13 + UA 的 3px 外边距）*/
.tp-table tr.tp-row > td { padding: 9px 14px; line-height: 21px; }
.tp-table input[type=radio] { height: auto; min-height: 0; padding: 0; }
.tp-ck { width: 38px; text-align: center; }
.tp-row { cursor: pointer; }
.tp-row:hover td { background: var(--bg-list); }
/* 候选表里的彩色 / 人员标签**压到 20 高**（全站 `.tag-chip` 是 23.6 高）——
   否则标签会把行高从 40 顶到 ~46 （用户 2026-09-18 19:31 定的行高 40 优先）*/
.tp-table .tag-chip { height: 20px; line-height: 20px; padding: 0 8px; margin: 0; font-size: var(--fs-12); }
/* 长文本列（订单备注 / 客户公司 这类 text / textarea 字段）→ 限宽 + 省略号（鼠标悬停看全文）
   列是**动态**的（跟随「转入带出字段」配置）→ 不能再按 `td:nth-child(8)` 写死，改由服务端给 `clip` 标记 */
.tp-table td.tp-clip { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tp-foot { display: flex; align-items: center; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
/* 弹窗里的底部操作区 = **系统标准操作栏**（用户 2026-09-18 19:31：「弹窗外框标准不对」）：
   高 **52**、上方分割线**贯穿弹窗宽度**（负外边距吃掉 `.to-body` 内距 16 + 两侧滚动条槽 8 = 24）、
   `sticky bottom:0` 吸底 → 与弹窗底部**齐平**（原来只有 24 高、无分割线、下面还多 16px 空白 ）*/
.to-body .tp-foot {
  position: sticky; bottom: 0; z-index: 6;
  height: 52px; min-height: 52px;
  background: var(--card);
  border-top: 1px solid var(--line);
  margin: 14px -24px 0; padding: 0 24px;
  flex-wrap: nowrap;
}
.to-body:has(.tp-foot) { padding-bottom: 0; }
.tp-pager { display: inline-flex; align-items: center; gap: 4px; }
.pg-num.is-on { background: var(--primary); color: #fff; border-color: var(--primary); }

/* ---------- 列表页「状态页签」框架（库管王那套结构，本系统配色）----------
   用户 2026-09-16：「我想要的是这样一个框架，在采购管理里面」→ 靠 cfg.statusTabs 开。
   现在只有左状态页签（下划线在激活项，跟本系统导航一致）；
   右侧的「刷新 / 导出 / 打印」按钮已按用户 2026-09-18 去掉（`.list-tools > .btn` 那条规则留着，
   以后若再加工具按钮仍生效；当前没有一个模块开 statusTabs → 整条工具条不渲染）。 */
.list-tools { display: flex; align-items: center; gap: 10px; margin: 0 0 6px; border-bottom: 1px solid var(--line); }
.lt-tabs { display: flex; align-items: center; gap: 2px; }
.lt-tab { display: inline-block; padding: 7px 12px 9px; font-size: var(--fs-14); color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.lt-tab:hover { color: var(--link); }
.lt-tab.is-on { color: var(--primary); font-weight: var(--fw-medium); border-bottom-color: var(--primary); }
.list-tools > .btn { margin-bottom: 4px; }

/* ---------- 整页表单（非弹窗，如「+ 转入」流程）：字段区也要走网格 ----------
   弹窗里的 `.modal-grid` 规则只对 `body.embed-body .modal-form` 生效 → 整页打开时字段会是**单列**，
   配合 `card` 一起「窄窄一条」，用户会问「为什么里面还是一个有宽度的卡片？」（2026-09-16）
   用 `body:not(.embed-body)` 与弹窗那套完全隔开，互不干扰。 */
body:not(.embed-body) .edit-form > .modal-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--form-row-gap) 18px; align-items: start;   /* 字段与字段上下 间距随 --form-row-gap（表单布局标准）*/
}
body:not(.embed-body) .edit-form > .modal-grid > .form-row { margin-top: 0; }
@media (max-width: 1100px) { body:not(.embed-body) .edit-form > .modal-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px) { body:not(.embed-body) .edit-form > .modal-grid { grid-template-columns: 1fr; } }

/* ---------- 订单详情的「卡片内就地编辑」（?edit=1）----------
   用户 2026-09-15：「编辑按钮就不要弹窗了，直接就是当前的卡片字段处于可编辑状态」；
   用户 2026-09-18 追加：「**不改变当前卡片形态**，而是直接字段变成可编辑状态」→
   现在编辑态与只读态**共用同一套卡片 / .kv-grid**（见 views/orders/detail.ejs），
   只有「值那一格」（.kv-v）在编辑态多一个 `form-row` 类 —— 控件样式直接复用 .form-row 那套，
   于是不会与弹窗表单走偏；下面只补几处 `.kv-v` 场景特有的对齐 / 宽度。 */
/* 编辑态的内边距也归 0：上下间距与只读态一致 = 网格 gap 16（用户 2026-09-18 15:38） */
.kv-item.is-edit { align-items: center; padding-top: 0; padding-bottom: 0; }
/* `.kv-v` 自己就是 .form-row：flex 列 + gap 8（只有一格控件时 gap 无影响） */
.kv-item.is-edit .kv-v.form-row { min-width: 0; align-self: center; }
/* `.dp-field` / `.tp-field` 全局带 max-width:220px（筛选栏那套）→ 在 4 列网格里只占一半，
     看起来就是「宽度不够」（与弹窗里同样的坑，见上面 .embed-body 那条） */
.kv-item.is-edit .kv-v .dp-field,
.kv-item.is-edit .kv-v .tp-field { max-width: none; }
/* 只读展示用的输入框（下单月份 / 自动编号 / 公式 / 查找引用）：不抢镜，也不影响排版 */
.kv-item.is-edit .kv-v input[readonly] { opacity: .72; background: var(--chip); }
/* 标签块（标签 / 多选面板）在窄格里允许换行，别顶出去 */
.kv-item.is-edit .kv-v .tag-options { flex-wrap: wrap; }
/* **就地编辑里的多行文本默认也只占 1 行（32）** —— 用户 2026-09-18 16:26：
   「在订单详情页点击编辑，卡片就开始动」。根因之一：只读态这一格只有一行文字（行高 32），
   而 textarea 按弹窗规范是 **64**（2 倍）→ 点编辑时那一行 32→64，**同一格网里其余 3 个字段被拉伸**、
   卡片长高 32、下方所有卡片整体下移
   → 详情页就地编辑里压回 32（与输入框同高）；需要更多空间时用户可自行拖右下角拉高
     （拖高后卡片会随内容变高，那是用户主动行为，属预期）
   只作用于**就地编辑**（`.kv-item.is-edit`），弹窗里的「多行文本 = 2 倍」规范不变 */
.kv-item.is-edit .kv-v.form-row textarea { min-height: var(--inp-h-md); height: var(--inp-h-md); }
/* 订单详情**就地编辑的底部操作栏 = 悬浮在视口底部、完全不占文档流**
   （用户 2026-09-18 16:26：「在订单详情页点击编辑，卡片就开始动」）——
   它原来插在卡片栈中间（卡 2 之后、关联卡片之前），占位 32 + 上边距 14 = **把后面所有内容推下 46px**
   → 改成 `position: fixed`：卡片 / 关联卡片 / 底部创建信息的位置与只读态**逐像素一致**
   · 左侧让开侧栏（176；折叠态 64 —— 见 `body.side-collapsed`）
   · 用 `:has()` 给 body 补下内距，免得浮条压住页面最底部那行内容
   · 按钮仍靠右，与弹窗操作栏一致 */
.form-actions-float {
  position: fixed; left: 176px; right: 0; bottom: 0; z-index: 9;
  margin: 0; padding: 10px 22px;
  background: var(--card); border-top: 1px solid var(--line);
  display: flex; align-items: center;
}
body.side-collapsed .form-actions-float { left: 64px; }
body:has(.form-actions-float) { padding-bottom: 54px; }

/* **订单详情顶部的「订单编号 + 按钮」那一行不再是卡片**（用户 2026-09-18 17:13：
   「订单中心，订单详情页，订单编号所在行，不是 1 个卡片」）——
   去掉边框 / 底色 / 阴影 / 内距，只留一行（编号在左、按钮在右），
   与采购 / 出货 / 生产的顶部工具条（`.pt-bar`）同一形态
   下面的字段区仍是正常卡片，卡片间距由 `.app-main > .card` 那组规则给 */
/* 🆕 订单详情首行的背景改成**白色**（用户 2026-09-26：「放销售订单编号、编辑删除按钮那一行，背景颜色是白色」）
   —— 原来这里是 transparent（透出页面底色）✗ ⇒ 用卡片底色 var(--card)，深色主题自动跟随 ✓ */
.card.ord-bar { background: var(--card); border: 0; box-shadow: none; padding: 0; }
.card.ord-bar > h2 { margin: 0; flex: 1 1 auto; min-width: 0; }
/* 上面那句 `flex: 1 1 auto` 必须有（用户 2026-09-19 20:4x：「订单详情页，编辑，删除，返回列表按钮，
   需要放到右侧」）—— 根因：`.app-main > .card.ord-bar` 本身是 **flex 容器**，
   h2 作为 flex item **默认收缩到内容宽度**（实测 h2 = 396px / 窗口 1416px）⇒
   h2 里那个 `<span style="flex:1">` 撑杆宽度算出来是 **0** → 按钮只贴在**文字右边**、而不是卡片右边
   补上伸展后 h2 占满整行，撑杆才有空间把这三个按钮推到**最右** */
/* 这一行在 `<h2 class="rel-title">` 里，**别让标题的加粗传染**（用户 2026-09-18 17:17：
   「订单编号这几个字的大小格式，和采购的『系统编号 CGDD-…』要一致」）——
   采购工具条里：名 = 400 / 值 = 600、字号都 14、颜色 #767B9E / #130928；
   订单这边原来「名」被 h2 的 bold 带成了 700 → 这里把 h2 的字重归普通，`.po-v` 自带的 600 保留 */
.card.ord-bar > h2 { font-weight: normal; }

/* 订单详情页面最下方的「创建人 / 创建时间」（用户 2026-09-18）：**系统自动给的值，不是字段** ——
   复用采购 / 出货卡片底部那套 `.pi-foot-meta`（flex + gap 28 + muted 13px + b=正文色），
   只把外边距对齐卡片：上 14（= `.app-main > .card` 的卡间距）、左右 18（= `.card` 内距）*/
.ord-foot-meta { margin: 16px 0 0; padding: 0 18px; }
/* 采购 / 生产 / 出货详情：这一行在 `.form-flat` 里、紧跟 `<form class="edit-form">`（display:flex）之后 ——
   **flex 容器不折叠子项 margin** ⇒ 上面那张 `.pi-card` 的 `margin-bottom:16` 已经留在 form 内部，
   这里再给 margin-top 就是**两段相加**：原来 16 + 14 = **30px**（用户 2026-09-19 报「为什么不是 16」的真凶）
   → 本场景清零，总间距 = 卡片那 16
   （订单详情那边是普通 block 兄弟：上方 `.card` 的 margin-bottom 为 0，靠这里的 16 给满，折叠后正好 16 ） */
.form-flat > .ord-foot-meta { margin-top: 0; }

.inline-edit-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--form-row-gap) 18px; align-items: start;   /* 字段与字段上下 间距随 --form-row-gap（表单布局标准）*/
}
.inline-edit-grid > .form-row { margin-top: 0; }
@media (max-width: 1100px) { .inline-edit-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px) { .inline-edit-grid { grid-template-columns: 1fr; } }

/* 窄屏降列：与上方那组一致（基准 = 一行 4 个） */
@media (max-width: 1320px) { .kv-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1000px) { .kv-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px) { .kv-grid { grid-template-columns: 1fr; } }
@media (max-width: 700px) { .form-row-2 { grid-template-columns: 1fr; } }

/* ============================================================
   折叠态：桌面端侧栏收起（仅显示图标，宽度 200 → 64）
   body.side-collapsed 由 public/js/app.js 切换并记忆
   ============================================================ */
.app-sidebar, .app-content { transition: width .22s ease, margin-left .22s ease; }
body.side-collapsed .app-sidebar { width: 64px; padding: 20px 10px 14px; }
body.side-collapsed .app-content { margin-left: 64px; }
/* 隐藏文字，只留图标并居中 */
body.side-collapsed .nav-item,
body.side-collapsed .nav-group { justify-content: center; gap: 0; padding: 0; }
body.side-collapsed .nav-item > span,
body.side-collapsed .nav-group > span { display: none; }
body.side-collapsed .nav-caret { display: none; }
/* 二级菜单在折叠态隐藏（点分组会自动展开侧栏） */
body.side-collapsed .nav-sub { display: none; }
body.side-collapsed .nav-group { cursor: pointer; }
/* 收起按钮图标翻转 + 居中 */
body.side-collapsed .side-collapse svg { transform: rotate(180deg); }
.side-collapse svg { transition: transform .22s ease; }
body.side-collapsed .side-bottom { border-top-color: var(--nav-line); }
/* 折叠态下的选中态竖条对齐 */
body.side-collapsed .nav-item.active::before { left: 0; }

/* 折叠态：hover 时在图标右侧即时浮出导航名字（替代原生 title 的延迟提示）
   ① .side-nav 是滚动容器（overflow-y:auto 会把 overflow-x 也算成 auto）→ 折叠态放行 overflow，
      否则浮出的气泡会被裁掉；折叠态图标很小、条目少，不影响滚动。
   ② 气泡内容取 data-nav（header.ejs 里每个导航项都带）；只在折叠态出现，展开态文字本就可见。 */
body.side-collapsed .side-nav,
body.side-collapsed .nav-item,
body.side-collapsed .nav-group { overflow: visible; }
body.side-collapsed .nav-item,
body.side-collapsed .nav-group { position: relative; }
body.side-collapsed .nav-item::after,
body.side-collapsed .nav-group::after {
  content: attr(data-nav);
  position: absolute;
  left: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%) scale(.96);
  transform-origin: left center;
  background: var(--side-bg);              /* 与导航同色：跟随明暗主题（改导航色这里自动跟着变） */
  color: var(--nav-hover-text);
  border: 1px solid var(--nav-line);
  font-size: var(--fs-13);
  font-weight: var(--fw-regular);
  line-height: 1;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  box-shadow: 0 8px 20px rgba(15,23,42,.28);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .14s ease, transform .14s ease, visibility .14s;
  z-index: 60;
}
body.side-collapsed .nav-item:hover::after,
body.side-collapsed .nav-group:hover::after {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) scale(1);
}

/* 折叠态顶栏：显示 logo（隐藏系统名称），logo 按 60px 宽等比展示 —— 正好落在 64px 折叠导航上方；
   分割线贴到折叠后导航右缘（64px）→ 页面标题紧接其后。
   顶栏左内边距由 22px 缩到 2px，才能让 60px 宽的 logo 居中于 0~64px 区域（2 + 60 + 2）。 */
body.side-collapsed .app-topbar { padding-left: 2px; }
body.side-collapsed .tb-left { width: 60px; }
body.side-collapsed .tb-logo { display: block; width: 60px; height: auto; }   /* 等比 → 高约 15.4px */
body.side-collapsed .tb-brand { display: none; }
body.side-collapsed .tb-divider { left: 64px; }

/* 移动端（抽屉式）下折叠态不生效 */
@media (max-width: 860px) {
  body.side-collapsed .app-sidebar { width: 200px; padding: 74px 14px 14px; }
  body.side-collapsed .app-content { margin-left: 0; }
  body.side-collapsed .nav-item,
  body.side-collapsed .nav-group { justify-content: flex-start; gap: 10px; padding: 0 14px; }
  body.side-collapsed .nav-item > span,
  body.side-collapsed .nav-group > span { display: inline; }
  body.side-collapsed .nav-caret { display: block; }
  body.side-collapsed .nav-sub { display: flex; }
  body.side-collapsed .side-collapse { display: none; }
  /* 移动端折叠态不生效 → 顶栏复位成正常（logo 正常尺寸、系统名称显示、左内边距恢复） */
  body.side-collapsed .app-topbar { padding-left: 22px; }
  body.side-collapsed .tb-logo { display: block; width: auto; height: 24px; }
  body.side-collapsed .tb-left { width: auto; }
  body.side-collapsed .tb-brand { display: inline; }
}

/* 表单内联开关（是 / 否 类型字段 / 字段设置属性行）
   注意：.form-row label 的 align-items:baseline 特异性更高（0,1,1）会覆盖居中，
   故用 .form-row .sw-inline 提权（0,2,0）确保 checkbox 与文字真正垂直居中 */
.sw-inline, .form-row .sw-inline { display: inline-flex; align-items: center; gap: 8px; font-size: var(--fs-14); color: var(--text); cursor: pointer; }
.form-row .sw-inline input, .sw-inline input { width: 16px; height: 16px; margin: 0; accent-color: var(--primary); cursor: pointer; flex: none; }
/* 单选 / 多选**不要**焦点环（`.form-row input:focus` 的 `box-shadow: var(--ring)` 打在圆点外面就是一个**方框**
   —— 用户 2026-09-17：「点『年月日』前面的圆点，会有一个方框出现」）。
   选中态本来就由 accent-color 表达；键盘 Tab 走到时浏览器自带聚焦样式，别覆盖 outline */
.form-row input[type=radio]:focus,
.form-row input[type=checkbox]:focus,
.sw-inline input:focus,
.fd-check:focus,
.tag-check input:focus,
.ms-opt input:focus { box-shadow: none; }
.form-row .sw-inline span, .sw-inline span { line-height: 16px; }

/* 字段设置：日期显示方式（两层选择） */
.date-fmt { display: flex; flex-direction: column; gap: 10px; }
.df-layer1, .df-layer2 { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.df-layer2 { margin-top: 2px; }
.df-addons { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; padding-top: 8px; border-top: 1px dashed var(--line); }
/* 日期「显示方式」最底部：**当前展示结果**（用此时此刻的日期时间演示最终效果，用户 2026-09-17） */
#fdDateHint .fd-date-now { color: var(--link); font-weight: 600; }

/* 自定义日期/时间选择器（对齐 Ant Design 截图风格） */
.dp-field, .tp-field { position: relative; display: inline-flex; align-items: center; width: 100%; max-width: 220px; }
.dp-display, .tp-display { flex: 1; min-height: 36px; height: 36px; padding: 6px 32px 6px 10px; border: 1px solid var(--field-border); border-radius: var(--radius-sm); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-14); line-height: 22px; cursor: pointer; outline: none; width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dp-display:hover, .tp-display:hover { border-color: var(--primary); }
.dp-display:focus, .tp-display:focus { border-color: var(--primary); box-shadow: var(--ring); }
.dp-display::placeholder, .tp-display::placeholder { color: var(--muted); }
.dp-trigger, .tp-trigger { position: absolute; right: 0; top: 0; height: 100%; width: 32px; border: none; background: transparent; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; color: var(--muted); padding: 0; }
.dp-trigger:hover, .tp-trigger:hover { color: var(--primary); }
.dp-trigger svg, .tp-trigger svg { width: 16px; height: 16px; pointer-events: none; }
.dp-panel, .tp-panel { position: absolute; top: calc(100% + 6px); left: 0; z-index: 200; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 8px 28px rgba(15,23,42,.14); padding: 10px; min-width: 268px; display: none; }
.dp-panel.open, .tp-panel.open { display: block; }
/* ---------- DatePicker 日期选择框规范（用户 2026-09-17 给定）----------
   面板：宽 **280**、内边距 上 16 / 左右 14（7×36 = 252 = 280−28）；
         日期固定 **6 行**（42 格，见 daysGrid）→ 面板高恒 **314**，切换月份不跳动。
   日期按钮 **36×24**、字号 14、列宽 36、行距 8（6×24 + 5×8 = 184）。
   星期行 高 24、字号 12。底部「今天」= 通栏分隔线 + 高 **40**、居中、左右内边距 24、字号 14。
   月份 / 年份面板：3 / 4 列，按钮 46×30（年份 30 高），**行间距 30**。
   高度自洽：16 + 标题 24 + 12 + 星期 24 + 4 + 日期 184 + 8 + 今天 40 = 312，**加上下边框 2 = 314**（外观总高，见规范图左侧标注）
   `.dp-days` 必须用固定列宽 36（别用 1fr）+ gap 0 横向，否则 7 列凑不满 252 */
.dp-panel { width: 280px; min-width: 280px; padding: 16px 14px 0; box-sizing: border-box; }
.dp-header { display: flex; align-items: center; justify-content: space-between; height: 24px; margin-bottom: 12px; padding: 0; }
.dp-title { font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--text); cursor: pointer; user-select: none; }
.dp-title:hover { color: var(--primary); }
.dp-nav { display: flex; gap: 4px; }
.dp-nav button { width: 24px; height: 24px; border: none; background: transparent; border-radius: 0; cursor: pointer; color: var(--muted); display: inline-flex; align-items: center; justify-content: center; padding: 0; }
.dp-nav button:hover { background: var(--bg-list); color: var(--primary); }
.dp-nav button svg { width: 14px; height: 14px; pointer-events: none; }
.dp-weekdays { display: grid; grid-template-columns: repeat(7, 36px); height: 24px; align-items: center; text-align: center; font-size: var(--fs-12); color: var(--muted); padding: 0; border-bottom: none; margin-bottom: 4px; }
.dp-days, .dp-months, .dp-years { display: grid; }
.dp-days { grid-template-columns: repeat(7, 36px); gap: 8px 0; }
.dp-months { grid-template-columns: repeat(3, 1fr); gap: 30px 0; }
.dp-years { grid-template-columns: repeat(4, 1fr); gap: 30px 0; }
.dp-cell { width: 36px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-14); color: var(--text); border-radius: var(--radius-sm); cursor: pointer; border: none; background: transparent; }
.dp-cell:hover:not(.disabled) { background: var(--bg-list); }
.dp-cell.today { color: var(--primary); font-weight: var(--fw-bold); }
.dp-cell.selected { background: var(--primary); color: #fff; }
.dp-cell.selected:hover { background: var(--primary); }
.dp-cell.other-month { color: var(--c-gray); }
.dp-cell.disabled { color: var(--line-strong); cursor: not-allowed; }
/* 月份 46×30、年份 30 高（列内居中） */
.dp-months .dp-cell { width: 46px; height: 30px; margin: 0 auto; }
.dp-years .dp-cell { height: 30px; }
/* 底部「今天」：通栏分隔线 + 40 高、居中（负 margin 让分隔线拉到面板边缘） */
.dp-footer { height: 40px; margin: 8px -14px 0; padding: 0 24px; border-top: 1px solid var(--line); display: flex; align-items: center; justify-content: center; }
.dp-footer button { border: none; background: transparent; color: var(--primary); font-size: var(--fs-14); cursor: pointer; padding: 2px 8px; border-radius: var(--radius-sm); }
.dp-footer button:hover { background: var(--primary-soft); }

/* 时分组合 */
.dt-field { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.dt-field .dp-field { max-width: 170px; }
.dt-field .tp-field { max-width: 110px; }
/* 日期 / 时间控件 = Medium 档高度（32）——与 input / 按钮标准一致（用户 2026-09-17：
   筛选行里搜索框 32、下拉 34、日期 36 三种高度混在一起，搜索框看着就「变矮了」）。
   只改高度与上下内距，**左右内距保持原值**：筛选行的日期框只有 120px 宽，
      按标准右内距 48 会把「2026-09-01」挤掉 */
.dp-field .dp-display, .tp-field .tp-display {
  height: var(--inp-h-md); min-height: var(--inp-h-md);
  padding-top: 0; padding-bottom: 0;
}
.filter-bar .filter-select,
.filter-bar .csel-trigger {
  height: var(--inp-h-md); min-height: var(--inp-h-md);
}
/* DatePicker 三种尺寸（用户 2026-09-17 规范）：**默认为中尺寸**（上面的 --inp-h-md = 32）；
   需要小 / 大时给 .dp-field / .tp-field 加 .is-sm / .is-lg（配合页面其它 Input 尺寸使用） */
.dp-field.is-sm .dp-display, .tp-field.is-sm .tp-display { height: var(--inp-h-sm); min-height: var(--inp-h-sm); font-size: var(--fs-14); }
.dp-field.is-lg .dp-display, .tp-field.is-lg .tp-display { height: var(--inp-h-lg); min-height: var(--inp-h-lg); font-size: var(--fs-16); }
/* 禁用态（规范图第三例）：灰底灰字、不可点 */
.dp-field.is-disabled .dp-display, .tp-field.is-disabled .tp-display { background: var(--bg-list); color: var(--muted); border-color: var(--line); cursor: not-allowed; }
.dp-field.is-disabled .dp-trigger, .tp-field.is-disabled .tp-trigger { color: var(--line-strong); cursor: not-allowed; }

/* 时间下拉 */
.tp-panel { min-width: 120px; padding: 4px; }
.tp-columns { display: flex; gap: 4px; max-height: 210px; }
.tp-col { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
.tp-item { height: 28px; display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-13); color: var(--text); border-radius: var(--radius-sm); cursor: pointer; flex: none; }
.tp-item:hover { background: var(--bg-list); }
.tp-item.selected { background: var(--primary); color: #fff; }
.tp-col::-webkit-scrollbar { width: 4px; }
.tp-col::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 0; }

/* 字段设置页 */
.fd-wrap { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 12px; align-items: start; --fd-top: 82px; --fd-topbar: 60px; }
/* ---------- 字段设置页滚动吸顶（用户 2026-09-17）----------
   页面用**窗口滚动**（没有内部滚动容器）→ 全部用 position:sticky：
     · 左侧「选择模块」向上滚到顶栏下方就**停住**（模块多时自身可滚）
     · 右侧「模块 · 字段」标题行 + 表格表头行**冻住**，并且**不许有缝**：
       标题行往上一直顶到**顶栏紧下方（60px）**，用自身背景盖住
       「主区内边距 22 + 卡片内边距 16 = 38px」那段空隙 —— 否则表格内容会从这条缝里露出来
       （用户 2026-09-17 第 2 次反馈：截图里红框那条缝）
     · 正常流里的位置不变：margin-top -38 抵消 22 + 16，padding-top 38 补回来
   表头的 top = 顶栏 60 + 标题行实测高度（--fd-head-h 由页面 JS 写入，含上下 padding） */
.fd-side {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
  position: sticky; top: var(--fd-top);
  max-height: calc(100vh - var(--fd-top) - 16px); overflow-y: auto; overflow-x: hidden;
}
/* 右侧主卡片（订单中心 · 字段 + 表格）：
   · **没有上内边距** → 标题行「订单中心 · 字段」直接贴在卡片上边线（用户 2026-09-17 晚：原来它上方
     多出一段 22px 页面留白 + 16px 卡片内边距，看着就像凭空多了一块，卡片顶边线还被吸顶标题的底色盖没了） */
.fd-maincard { padding: 0 14px 14px; }
/* 「遮挡条」：滚动时表格内容会从「顶栏下方 → 卡片上边线」这 16px 里露出来。
   原来靠给标题行加 22/38px 上内边距去盖，底色是白的 → 看着就像标题上方多了一段白条
   现在改用一个**高 16px、负下边距 16px（不占版面）**的 sticky 条，底色 = 页面底色（var(--bg)）
   → 滚动前后这一条看起来完全一样（就是页面本身的留白），表格内容也不会漏出来 */
.fd-bleed-cover { position: sticky; top: var(--fd-topbar); z-index: 4; height: 16px; margin-bottom: -16px; background: var(--bg); }
.fd-card-head {
  position: sticky; top: var(--fd-top); z-index: 6; background: var(--card);
  padding: 12px 0;
}
.fd-card-head::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; background: var(--line); }
/* 表头吸顶：.table-wrap 的 overflow-x:auto 会把它变成 sticky 的滚动容器 → 表头就不粘了，
   所以本页表格放开 overflow（表格是 table-layout:fixed + width:100%，本来也不会横向溢出） */
.fd-wrap .table-wrap { overflow: visible; }
.fd-table thead th {
  position: sticky; top: calc(var(--fd-top) + var(--fd-head-h, 60px)); z-index: 5;
  /* 底色必须与上面 `.fd-table th` 一致（`.fd-table thead th` 优先级更高，会盖掉它） */
  background: var(--tint-primary);
  box-shadow: inset 0 -1px 0 var(--line-strong);   /* collapse 下 sticky 单元格的边框常丢 → 用内阴影画底线 */
}
.fd-side-head { padding: 9px 12px; border-bottom: 1px solid var(--line); font-size: var(--fs-13); color: var(--muted); background: var(--card-2); }
.fd-side-item { display: flex; align-items: center; gap: 8px; padding: 9px 12px; font-size: var(--fs-14); color: var(--text); border-bottom: 1px solid var(--line); }
.fd-side-item:last-child { border-bottom: none; }
.fd-side-item:hover { background: var(--bg-list); }
.fd-side-item.on { background: var(--primary); color: #fff; font-weight: var(--fw-bold); }
.fd-side-item .n { margin-left: auto; font-size: 11px; border-radius: 0; padding: 0 7px; line-height: 16px; background: var(--chip); color: var(--muted); }
.fd-side-item.on .n { background: rgba(255,255,255,.22); color: #fff; }
/* 明细子模块（用户 2026-09-18）：缩进一格 + 名字前面一条细竖线，表示「挂在上面那个模块下」 */
.fd-side-item.is-detail { padding-left: 28px; font-size: var(--fs-13); color: var(--muted); }
.fd-side-item.is-detail span { position: relative; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fd-side-item.is-detail span::before {
  content: ''; position: absolute; left: -8px; top: 50%; width: 1px; height: 14px;
  margin-top: -7px; background: var(--line-strong);
}
.fd-side-item.is-detail.on { color: #fff; }
.fd-side-item.is-detail.on span::before { background: rgba(255,255,255,.5); }
/* **fixed 布局**（用户 2026-09-17）：列宽完全按 thead 里的 width 分配 ——
   原先 auto 布局下「字段名称」会吃掉全部剩余宽度（其它列挤在右边），现在把它压住，
   多出来的空间分给「类型 / 来源 / 必填 / 列表显示 / 操作」→ 这几列的间隙明显变大 */
.fd-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: var(--fs-14); }
.fd-table td { overflow-wrap: anywhere; }   /* 窄列里的长字段名换行显示，而不是把表格撑破 */
.fd-table th, .fd-table td { padding: 9px 8px; border-bottom: 1px solid var(--line); text-align: left; }
/* 表头（顺序 / 字段名称 / 类型…）：**黑字 · 14px · 加粗**，底色用品牌浅靛蓝
   —— 原来 12px 灰字 + `--card-2`（#F1F5F9）跟「区块行」的底色**一模一样**，分不清哪条是表头（用户 2026-09-17 晚） */
/* 不加 `white-space:nowrap`：字号从 12 加到 14 后，窗口一窄「列表显示」会溢出到相邻列
   （截图里出现过「列表显示操作」挤在一起 ）→ 让它自己折行更安全 */
.fd-table th { background: var(--tint-primary); color: var(--text); font-size: var(--fs-14); font-weight: var(--fw-bold); }
.fd-table .fd-key { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; color: var(--muted); }
.fd-chip { display: inline-block; font-size: 11px; border-radius: 0; padding: 1px 8px; background: var(--chip); color: var(--muted); }
.fd-chip.sys { background: rgba(79,70,229,.12); color: var(--primary); }
.fd-chip.custom { background: rgba(16,185,129,.12); color: var(--c-success); }
/* 编号主键：系统自动生成，不可删除 */
.fd-chip.keyfield { margin-left: 6px; background: rgba(245,158,11,.16); color: #B45309; }
/* 编号主键说明条（字段设置弹框内） */
.fd-key-hint {
  display: flex; align-items: flex-start; gap: 8px; margin-bottom: 14px;
  padding: 9px 12px; border-radius: var(--radius-sm);
  background: rgba(245,158,11,.10); border: 1px solid rgba(245,158,11,.30);
  color: #B45309; font-size: var(--fs-12); line-height: 1.7;
}
.fd-key-hint[hidden] { display: none; }
.fd-key-hint svg { flex-shrink: 0; margin-top: 1px; }

/* 标签（多选）字段：录入勾选 + 列表/详情彩色小标签 */
.tag-options { display: flex; flex-wrap: wrap; gap: 8px; }
.tag-check { display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border: 1px solid var(--line); border-radius: 0; cursor: pointer; font-size: 13px; line-height: 1.6; user-select: none; transition: border-color .15s, background .15s, color .15s; }
.tag-check input { margin: 0; }
.tag-check.is-on { border-color: var(--primary); background: var(--primary-soft); color: var(--primary); }
.tag-chip { display: inline-block; padding: 1px 9px; margin: 2px 5px 2px 0; border-radius: 0; background: var(--primary-soft); color: var(--primary); font-size: 12px; line-height: 1.8; white-space: nowrap; }

/* 下拉多选 / 标签 / 人员多选 / 关联多选（共用一套「假下拉」控件）
   2026-09-18 用户问「产品品类是下拉多选，为什么样式不遵循其他的输入框样式？」
   → 根因：这里是最早期写的**独立样式**，没跟上下拉控件标准（写死了 min-height:38 / padding:6px 11px /
     border:--line / background:--card），而系统下拉是 **高 32 / 左距 16 / border:--field-border / bg:--field-bg**
     → 所以多选控件看着比别的输入框「高一截、边框更浅」。现已按「控件标准 · 下拉 Medium 档」改齐
   caret 是 flex 子元素（不是原生 select 那种背景图箭头）→ 右内距用 **12px**（= 箭头↔右边缘标准），
      不用原生 select 的 padding-right:32px（那是给背景图箭头留位的） */
.ms-field { position: relative; }
.ms-trigger {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
  height: var(--sel-h-md); min-height: var(--sel-h-md);            /* 与下拉标准一致：32 */
  padding: 0 12px 0 var(--sel-px-md);                              /* 左距 16、右边 12（caret 之后） */
  font-size: var(--sel-fs-md); font-family: inherit; line-height: 1;
  color: var(--text); background: var(--field-bg);
  border: 1px solid var(--field-border); border-radius: var(--radius-sm);
  cursor: pointer; text-align: left; transition: border-color .15s, box-shadow .15s;
}
.ms-trigger:hover { border-color: var(--primary); }
.ms-trigger[aria-expanded="true"] { border-color: var(--primary); box-shadow: var(--ring); }
.ms-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ms-text.is-placeholder { color: var(--muted); }
.ms-caret { flex: none; color: var(--muted); transition: transform .15s; }
.ms-trigger[aria-expanded="true"] .ms-caret { transform: rotate(180deg); }
.ms-panel {
  /* 宽度自适应内容（用户 2026-09-26）：原来 left:0 + right:0 会把宽度锁成容器宽，多列时被裁 ✗
     ⇒ 改成 left:0 + right:auto + width:max-content + min-width:100% ✓ */
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; right: auto;
  min-width: 100%; width: max-content; max-width: min(760px, 92vw);
  max-height: 220px; overflow: auto; padding: 6px; background: var(--card);
  border: 1px solid var(--field-border); border-radius: var(--radius-sm); box-shadow: var(--shadow);
}
.ms-opt { display: flex; align-items: center; gap: 8px; padding: 7px 9px; border-radius: var(--radius-sm); font-size: 14px; cursor: pointer; user-select: none; }
.ms-opt:hover { background: var(--bg-list); }
.ms-opt input { width: 16px; height: 16px; margin: 0; accent-color: var(--primary); cursor: pointer; }

/* 明细卡片（用户 2026-09-18）：**一个明细字段 = 一张卡片**（主表有 2 个明细字段 → 2 张卡）
   下间距 12 → **16**（用户 2026-09-19 20:0x「要统一改」）—— 原来 12 是全页卡片栈里唯一的例外 */
.dt-card { margin: 0 0 16px; }
.dt-card .rel-title { margin-bottom: 10px; }

/* 明细字段（用户 2026-09-18）：挂在表单里的**子表** —— 列 = 明细子模块的字段
   小表样式：表头浅底、单元格内控件铺满、行首序号、末列删除按钮 */
.dt-field { display: flex; flex-direction: column; gap: 8px; }
.dt-table-wrap { border: 1px solid var(--line); overflow-x: auto; }
.dt-table { width: 100%; border-collapse: collapse; font-size: var(--fs-14); }
.dt-table thead th {
  background: var(--tint-primary); color: var(--text); font-weight: var(--fw-bold);
  font-size: var(--fs-13); padding: 8px; text-align: left; white-space: nowrap;
}
.dt-table td { padding: 6px 8px; border-top: 1px solid var(--line); vertical-align: middle; }
.dt-table td.dt-no { color: var(--muted); font-size: var(--fs-13); text-align: center; }
.dt-table input, .dt-table select { width: 100%; min-width: 70px; height: var(--inp-h-sm, 28px); min-height: 28px; padding: 0 8px; font-size: var(--fs-13); }
.dt-actions { display: flex; align-items: center; gap: 10px; }
/* 明细卡片顶部 / 底部的公共字段（整张明细共用一个值，不占表格列） */
.dt-band { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; padding: 8px 10px; background: var(--bg-list); border: 1px solid var(--line); }
.dt-band-top { margin-bottom: 0; }
.dt-band-bottom { margin-top: 0; }
.dt-band .dt-cell { display: flex; align-items: center; gap: 8px; }
.dt-band .dt-lab { font-size: var(--fs-13); color: var(--muted); white-space: nowrap; }
.dt-band input, .dt-band select { min-width: 120px; height: var(--inp-h-sm, 28px); min-height: 28px; padding: 0 8px; font-size: var(--fs-13); }
/* 汇总行（勾了「明细汇总」的数字 / 金额列） */
.dt-foot td { background: var(--tint-primary); font-weight: var(--fw-bold); color: var(--text); }
.dt-foot .dt-sum { text-align: right; font-variant-numeric: tabular-nums; }
.dt-foot .dt-sum-lab { font-size: var(--fs-13); }
/* 公式列（用户 2026-09-18）：只读格 —— 浅底 + 等宽数字，一眼看出「这是算出来的，不能填」 */
.dt-table input.dt-fx { background: var(--tint-primary); color: var(--text); cursor: default; text-align: right; font-variant-numeric: tabular-nums; }

/* 公式字段（用户 2026-09-18）：主表里的只读结果框（fx 角标 + 浅底输入框） */
.fx-wrap { display: flex; align-items: center; gap: 6px; width: 100%; }
.fx-badge {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 2px;
  font-size: 11px; font-weight: 700; font-style: italic; line-height: 1;
  color: var(--primary); background: var(--tint-primary); border: 1px solid var(--tint-primary);
}
.fx-wrap .fx-input {
  flex: 1 1 auto; min-width: 0;
  background: var(--tint-primary); color: var(--text); cursor: default;
  font-variant-numeric: tabular-nums;
}
.fx-wrap .fx-input:focus { outline: none; border-color: var(--tint-primary); }
.form-row.is-h .fx-wrap { justify-content: flex-start; }
/* 供货「新建」整页里的公式格 */
input.sup-fx { background: var(--tint-primary); color: var(--text); cursor: default; text-align: right; font-variant-numeric: tabular-nums; }

/* 字段设置弹窗里的「公式」配置区（用户 2026-09-18）：表达式 + 可用字段 / 函数 + 小数位 */
.fx-cfg { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.fx-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.fx-line input[name="formula_expr"] {
  flex: 1 1 auto; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.fx-cfg .fx-lbl { flex: none; width: 56px; font-size: var(--fs-13); color: var(--muted); }
.fx-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
.fx-chip {
  height: 24px; min-height: 24px; padding: 0 8px; border: 1px solid var(--line);
  border-radius: 2px; background: var(--card); color: var(--text);
  font-size: var(--fs-13); line-height: 1; cursor: pointer;
}
.fx-chip:hover { border-color: var(--primary); color: var(--primary); }
.pm-hint.is-err { color: var(--danger); }

/* 查找引用（用户 2026-09-18）：表单里的只读结果框（「查」角标 + 浅底），值与公式字段同一套观感 */
.lk-wrap { display: flex; align-items: center; gap: 6px; width: 100%; }
.lk-badge {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 2px;
  font-size: 11px; font-weight: 700; line-height: 1;
  color: var(--primary); background: var(--tint-primary); border: 1px solid var(--tint-primary);
}
.lk-wrap .lk-input { flex: 1 1 auto; min-width: 0; background: var(--tint-primary); color: var(--text); cursor: default; }
.lk-wrap .lk-input:focus { outline: none; border-color: var(--tint-primary); }

/* 字段设置弹窗里的「查找引用」配置区：引用表 / 引用字段 / 条件组 / 统计方式 */
.lk-cfg { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.lk-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.lk-line select { min-width: 150px; }
.lk-conds {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--line); background: var(--bg-list); padding: 10px 12px;
}
.lk-conds-h { display: flex; align-items: center; justify-content: space-between; font-size: var(--fs-13); color: var(--muted); }
.lk-cond { display: flex; align-items: center; gap: 8px; }
.lk-cond select { flex: 1 1 auto; min-width: 0; }
.lk-cond .lk-c-op { flex: 0 0 96px; min-width: 96px; }
.lk-c-del {
  flex: none; width: 26px; height: 26px; line-height: 1; border: 1px solid var(--line);
  background: var(--card); color: var(--muted); font-size: 16px; cursor: pointer; border-radius: 2px;
}
.lk-c-del:hover { color: var(--danger); border-color: var(--danger); }
.lk-addcond { align-self: flex-start; border: 0; background: none; color: var(--primary); font-size: var(--fs-13); cursor: pointer; padding: 2px 0; }
.lk-addcond:hover { text-decoration: underline; }

/* 历史编号处理（用户 2026-09-18）：编号规则下方的一块单选（只在**编辑已有编号字段**时出现） */
.auto-history { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.auto-h-t { font-size: var(--fs-13); color: var(--muted); margin-bottom: 6px; }
.auto-h-opts { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.auto-h-warn { color: var(--danger); font-weight: var(--fw-bold); }

/* 「允许填写时新增选项」（用户 2026-09-17 深夜）：下拉多选的面板底部 / 标签块末尾各放一个输入框，
   输入新值回车（或点「添加」）→ 就地加一个选项并勾上；保存时后端把它补进该字段的选项里 */
.ms-new-row { display: flex; align-items: center; gap: 6px; padding: 6px 4px 2px; border-top: 1px solid var(--line); margin-top: 4px; }
.ms-new-row .ms-new-inp {
  flex: 1; min-width: 0; height: 28px; min-height: 28px; padding: 0 8px;
  font-size: var(--fs-13); color: var(--text); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.tag-new-inp {
  height: 28px; min-height: 28px; padding: 0 8px; font-size: var(--fs-13);
  color: var(--text); background: var(--card); border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
}

/* 关联字段（用户 2026-09-17 深夜）：从另一张表挑记录
   · 面板顶部可带**搜索框**（字段配置里开了「支持搜索」才有）；
   · 每一行 = 主字段值 + 一行灰色小字（配置的辅助字段，用来辨识，如「简称 · 级别」） */
.rel-search { padding: 4px 4px 6px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.rel-search .rel-kw {
  width: 100%; height: 32px; padding: 0 8px; font-size: 14px; color: var(--text);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
}
.rel-opt { align-items: flex-start; }
.rel-opt .rel-main { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rel-opt .rel-sub { flex: none; font-size: 12px; color: var(--muted); white-space: nowrap; }
.rel-empty { padding: 10px 9px; font-size: 13px; color: var(--muted); }

/* 字段设置 · 关联字段的**配置区**（关联表 + 「选择主字段 / 辅助字段」按钮 + 多选 / 支持搜索）
   主/辅字段改成**弹窗里勾选**（用户 2026-09-18）：行内只有按钮 + 已选摘要 */
.rel-cfg { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.rel-line { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.rel-line select { min-width: 170px; }
/* 「左侧品类」引用字段：关联表被系统钉死 ⇒ 灰掉 + 鼠标不可点
   （用类而不是 disabled —— disabled 的表单项不随表单提交，后端会因「未选关联表」拒存）
   注意：原生 select 的文字色在 Chromium 下要**同时**给 -webkit-text-fill-color，只写 color 不生效 */
.rel-line select.is-locked {
  background: var(--field-bg); color: var(--muted); -webkit-text-fill-color: var(--muted);
  cursor: not-allowed;
}
.rel-sum { font-size: var(--fs-13); color: var(--muted); }
.rel-sum.is-err { color: var(--danger); }
.rel-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; background: var(--bg-list); border: 1px solid var(--line); padding: 10px 12px; }
.rel-col { min-width: 0; }
.rel-col-t { font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.rel-list { max-height: 168px; overflow: auto; display: flex; flex-direction: column; gap: 2px; }
.rel-pick { display: flex; align-items: center; gap: 6px; font-size: 14px; padding: 3px 2px; cursor: pointer; }
.rel-pick input { width: 14px; height: 14px; margin: 0; accent-color: var(--primary); }
@media (max-width: 720px) { .rel-cols { grid-template-columns: 1fr; } }

/* 链接 / 邮箱（自定义字段类型）：列表与详情里的可点击锚点 */
.cell-link { color: var(--primary); text-decoration: none; border-bottom: 1px solid var(--primary-soft); }
.cell-link:hover { border-bottom-color: var(--primary); }

/* 进度（自定义字段类型）：表单滑块 + 列表/详情进度条 */
.range-field { display: flex; align-items: center; gap: 12px; }
.prog-range { flex: 1; accent-color: var(--primary); cursor: pointer; height: 6px; }
.range-val { flex: none; min-width: 44px; text-align: right; font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.prog-cell { display: flex; align-items: center; gap: 10px; }
.prog-bar { flex: 1; height: 8px; border-radius: 0; background: var(--bg-list); overflow: hidden; min-width: 90px; }
.prog-fill { display: block; height: 100%; border-radius: 0; background: linear-gradient(90deg, #6366F1, var(--primary)); }
/* 进度条颜色（字段设置的「数字 → 进度」可指定：蓝 / 绿 / 橙 / 黄 / 红） */
.prog-fill.blue { background: var(--primary); }
.prog-fill.green { background: #16a34a; }
.prog-fill.orange { background: #f97316; }
.prog-fill.yellow { background: #eab308; }
.prog-fill.red { background: #ef4444; }
.prog-pct { flex: none; min-width: 38px; text-align: right; font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.num-prog-preview { flex: 1 1 220px; min-width: 200px; }
/* 锁定不可改的输入（编号主键的日期段 / 序号位数） */
.form-row .auto-grid input[disabled], .form-row .auto-grid select[disabled] {
  background: var(--bg-list); color: var(--muted); cursor: not-allowed;
}
/* 字段排序：拖动手柄（与顶栏「字段显示」弹窗同一套交互） */
.fd-move { display: inline-flex; align-items: center; color: var(--muted); cursor: grab; }
.fd-move:hover { color: var(--primary); }
.fd-move svg { pointer-events: none; }
/* 区块排序 = **上下箭头**（用户 2026-09-17：区块之间不用拖拽了） */
.fd-arrows { display: inline-flex; align-items: center; gap: 4px; }
.fd-arrow {
  width: 20px; height: 20px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); background: var(--card); color: var(--text); cursor: pointer; font: inherit;
}
.fd-arrow svg { pointer-events: none; }
.fd-arrow:hover:not(:disabled) { border-color: var(--primary); color: var(--primary); }
.fd-arrow:disabled { opacity: .3; cursor: not-allowed; }
/* 只有「顺序」列（第 1 格）与「字段名称」列（第 2 格）显示可拖光标、能起拖（用户 2026-09-17）：
   整行都能拖会误触（在类型 / 必填 / 操作列想选文字就把行拖走了）→ 其它列保持默认光标。
   区块行已改成箭头排序 → 不给可拖光标（改由 .fd-arrow 表达） */
.fd-table tbody tr[data-id]:not(.fd-group-row) > td:nth-child(1),
.fd-table tbody tr[data-id]:not(.fd-group-row) > td:nth-child(2) { cursor: grab; }
.fd-table tbody tr[data-id]:not(.fd-group-row) > td:nth-child(1):active,
.fd-table tbody tr[data-id]:not(.fd-group-row) > td:nth-child(2):active { cursor: grabbing; }
body.fd-dragging, body.fd-dragging * { cursor: grabbing !important; user-select: none !important; }

/* 字段列表 · 区块分组（用户 2026-09-17 参考图）：
   区块单独一行（浅底 + 分区图标 + 名称），区块内的字段缩进并带一个**小圆点**（CSS 画的点，不是字符）；
   组与组之间留一条**空隙行**（.fd-gap-row），让「一组 = 区块 + 它的字段」在视觉上自成一块 */
.fd-table .fd-group-row { background: var(--card-2); }
.fd-table .fd-group-row b { font-weight: 600; }
.fd-table .fd-group-row td { border-bottom: 1px solid var(--line-strong); }   /* 组头下沿用略深的线 */
.fd-table .fd-gap-row td { height: 10px; padding: 0; background: transparent; border-bottom: none; }
/* ① 区块 = **带边框的盒子**（用户 2026-09-17 参考页做法）：
   组内每行的首 / 末列画竖线，区块行画顶边、组末行画底边 → 视觉上是一个独立外框，
   「组与组之间有明显间隔」不再只靠留白；悬浮在组上时整行变色 */
.fd-table tbody tr.in-group > td:first-child { border-left: 1px solid var(--line-strong); }
.fd-table tbody tr.in-group > td:last-child { border-right: 1px solid var(--line-strong); }
.fd-table tbody tr.fd-box-top > td { border-top: 1px solid var(--line-strong); }
.fd-table tbody tr.fd-box-bottom > td { border-bottom: 1px solid var(--line-strong); }
.fd-table tbody tr[data-id]:hover > td { background: var(--bg-list); }
/* ② 「必填 / 列表显示」= 勾选框（用户 2026-09-17）：直接点即保存；编号主键灰掉不可点 */
.fd-check {
  width: 14px; height: 14px; margin: 0; vertical-align: middle;
  cursor: pointer; accent-color: var(--primary);
}
.fd-check:disabled { cursor: not-allowed; opacity: .45; accent-color: var(--line-strong); }
/* 🆕 字段设置 ·「显示到 Tab」列（用户 2026-09-27）：
   一排可点的小标签，**多选** —— 点一下切换「这个字段要不要显示到该 Tab」，AJAX 立即保存 ✓
   ⚠️ 全站 border-radius:0 ⇒ 不要圆角；配色跟 --primary 走 ✓ */
.fd-tabs-col { vertical-align: middle; }
.fd-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
.fd-tab-chip {
  font: inherit; font-size: 11px; line-height: 1;
  padding: 3px 7px; cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: 0;
  background: transparent; color: var(--muted);
}
.fd-tab-chip:hover { border-color: var(--primary); color: var(--primary); }
.fd-tab-chip.is-on { border-color: var(--primary); background: rgba(79, 70, 229, .10); color: var(--primary); font-weight: 600; }
.fd-group-ico { display: inline-flex; align-items: center; vertical-align: -2px; margin-right: 6px; color: var(--muted); }
.fd-table .fd-sub-row td { color: var(--text); }
.fd-sub-name { position: relative; padding-left: 20px; }
.fd-sub-name::before {
  content: ''; position: absolute; left: 8px; top: 50%; margin-top: -2px;
  width: 4px; height: 4px; background: var(--muted); border-radius: 50%;
}
/* 字段排序：指针拖拽（touch-action:none 让触摸屏也能拖）
   但这**不代表整行都显示可拖光标**：cursor:grab 只给「顺序 / 字段名称」两列（见上方规则，用户 2026-09-17） */
.fd-table tbody tr[data-id] { touch-action: none; }
.fd-table tbody tr.dragging { opacity: .5; background: var(--bg-list); cursor: grabbing; }
body.fd-dragging { user-select: none; -webkit-user-select: none; }
body.fd-dragging * { cursor: grabbing !important; }
.fd-actions { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
@media (max-width: 900px) { .fd-wrap { grid-template-columns: 1fr; } }

/* 左侧分类栏标题 + 右侧标题面包屑 */
.cat-side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 9px 12px; border-bottom: 1px solid var(--line); background: var(--card-2);
  font-size: var(--fs-13); color: var(--muted); }
.ph-crumb { margin-left: 10px; font-size: var(--fs-13); font-weight: var(--fw-regular); color: var(--primary);
  background: var(--primary-soft); border-radius: 0; padding: 1px 12px; }

/* 自动编号等只读字段 */
input.is-readonly { background: var(--bg-list); color: var(--muted); cursor: not-allowed; }

/* 字段设置 · 选项编辑（用户 2026-09-17）：每行 = 选项名 + 颜色选择器 + 删除；下面一个「+ 添加选项」 */
/* 「选项」这一行走**水平布局**（与「所属区块」「默认值」同款，标签右对齐）→ 内容裹一层
   `.fd-opts-wrap`（选项列表 + 添加按钮 + 说明，纵向排列），否则 is-h 会把它们排成一行 */
.fd-opts-wrap { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-width: 0; }
/* 「+ 添加选项 / + 批量添加」两个按钮并排（用户 2026-09-17） */
.fd-opts-btns { display: flex; align-items: center; gap: 8px; }
/* 「批量添加选项」弹窗里的多行输入
   选择器必须带 `.to-panel` 前缀：`.to-panel textarea`（0,1,1）会盖掉纯类名的 height */
.to-panel .fd-bulk-ta {
  width: 100%; height: 260px; min-height: 260px; padding: 10px var(--inp-px-md);
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--inp-fs-md); line-height: 22px; resize: none; outline: none;
}
.to-panel .fd-bulk-ta:focus { border-color: var(--primary); }
/* 选项列表**铺满内容区**（= 上方「字段名称」输入框的宽度），每行再按 flex 分宽
   （`.fd-opts` 用 align-self:stretch 而不是给 wrap 加 align-items:stretch ——
     否则「+ 添加选项」按钮和说明文字也会被拉满 ） */
.fd-opts { align-self: stretch; display: flex; flex-direction: column; gap: 6px; }
.fd-opt-row { display: flex; align-items: center; gap: 8px; }
/* 选项名输入框 = **吃掉剩余宽度**：整行 = 输入框 + 8 + 颜色 + 8 + 默认 + 8 + 删除按钮
   = 上方输入框的宽度（用户 2026-09-17：要的就是这个等式，别把行缩成内容宽 ）
   选择器必须带 `.form-row` 前缀：选项行本身就在 `.form-row`（#fdOptionsRow）里，
      会被 `.form-row input[type="text"]`（0,2,1）的 `width:100%` 盖掉 → 写了宽度也不生效
      （`.form-row .fd-opt-row .fd-opt-name` = 0,3,0 才压得住） */
.form-row .fd-opt-row .fd-opt-name {
  flex: 1 1 auto; min-width: 0; width: auto; height: var(--sel-h-md); padding: 0 10px;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-14); outline: none;
}
.fd-opt-row .fd-opt-name:focus { border-color: var(--primary); box-shadow: var(--ring); }
.fd-opt-row .fd-opt-color {
  flex: none; width: 44px; height: var(--sel-h-md); padding: 2px 3px;
  border: 1px solid var(--field-border); background: var(--field-bg); cursor: pointer;
}
/* 颜色色块（用户 2026-09-17 晚：原来 44×32 太大 → 收到 28×28，与行内其它控件居中对齐） */
.fd-opt-row .fd-swatch {
  flex: none; width: 28px; height: 28px; padding: 0; cursor: pointer;
  border: 1px solid var(--line-strong); background: #FFFFFF;
}
.fd-opt-row .fd-swatch.is-empty { border-style: dashed; }   /* 白 = 不指定颜色（系统自动配色） */
/* 「选项值锁定」档（如货品「售卖状态」，用户 2026-09-26）：值看得见、改不动，只剩「默认」可勾
   （⚠️ 只加 is-lock / disabled，**不改 input[readonly]** 这类通配 —— 会把别的只读控件一起抹平）*/
.form-row .fd-opt-row .fd-opt-name.is-lock {
  background: var(--bg-list); color: var(--muted); cursor: default;
  border-color: transparent;
}
.fd-opt-row .fd-swatch:disabled { cursor: not-allowed; opacity: .6; }
.fd-opt-row .fd-opt-offbox:disabled + .slider { opacity: .5; cursor: not-allowed; }
.fd-opt-row .btn { flex: none; }
/* 选项行里的「默认」圆点（用户 2026-09-17）：勾上 = 新建记录时该选项默认选中（同字段只留一个） */
.fd-opt-row .fd-opt-def {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-13); color: var(--muted); cursor: pointer; white-space: nowrap;
}
.fd-opt-row .fd-opt-def input { width: 14px; height: 14px; margin: 0; accent-color: var(--primary); cursor: pointer; }
.fd-opt-row .fd-opt-def:has(input:checked) { color: var(--primary); }
/* 选项行的「启用 / 关闭」开关（用户 2026-09-17 晚）：关闭 = 新建单据时选不到、老数据仍保留。
   开关**不重复写「启用」二字**（每行都写太吵）→ 只给 title 提示 */
.fd-opt-row .fd-opt-cell { display: flex; align-items: center; gap: 6px; flex: none; }
.fd-opt-row.is-off .fd-opt-name { color: var(--muted); background: var(--card-2); }

/* 选项配色的色板浮层（27 色调色板；挂 body + fixed）
   打开时必须给 display:flex（JS 里设，别用 block）—— 否则 .fd-pal 的 gap 失效、行与行贴在一起（用户 2026-09-17 反馈） */
.fd-pal {
  position: fixed; z-index: 300; padding: 8px; background: var(--card);
  border: 1px solid var(--line); box-shadow: var(--shadow-hover);
  display: flex; flex-direction: column; gap: 8px;
}
.fd-pal-row { display: flex; gap: 8px; }
.fd-pal-cell { width: 22px; height: 22px; padding: 0; cursor: pointer; border: 1px solid var(--line-strong); }
.fd-pal-cell:hover { outline: 2px solid var(--primary); outline-offset: 1px; }
/* 色板最后一项「不使用颜色」（用户 2026-09-17）：带斜线的方块图标 + 文字，
   与第一行的**纯白色块**语义区分开 —— 点它 = 不指定颜色，回落到系统自动配色 */
.fd-pal-none {
  display: flex; align-items: center; gap: 6px; width: 100%;
  height: 26px; padding: 0 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  font-family: inherit; font-size: var(--fs-12); line-height: 1; text-align: left;
}
.fd-pal-none:hover { border-color: var(--primary); color: var(--primary); }
.fd-pal-none svg { flex: none; }

/* 字段设置 · 自动编号规则配置 */
/* 编号规则网格（用户 2026-09-17）：**左列吃掉剩余宽度**（前缀 / 日期段 / 序号位数 / 后缀），
   右列只给「连接符」留 80px（连接符就一两个字符，不该跟内容一样宽）；
   最后一行「后缀」跨两列，免得被连接符那一列切窄 */
.auto-grid { display: grid; grid-template-columns: minmax(0, 1fr) 80px; gap: 10px; }
.auto-grid > label:last-child { grid-column: 1 / -1; }
.auto-grid label { display: flex; flex-direction: column; gap: 5px; font-size: var(--fs-12); color: var(--muted); }
.auto-grid input, .auto-grid select { min-height: 32px; padding: 0 9px; border-radius: var(--radius-sm); border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--fs-13); outline: none; }
.auto-preview { margin-top: 10px; padding: 9px 12px; border-radius: var(--radius-sm); background: var(--primary-soft); color: var(--primary); font-size: var(--fs-13); }
.auto-preview b { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: var(--fs-14); }

/* global-square-corners */
input, select, textarea, button, .btn, .card, .to-panel, .to-modal, .badge, .chip, .icon-btn, .pg-num {
  border-radius: 0 !important;
}

/* ============================================================
   自定义下拉（色块选项）—— 用户 2026-09-17
   展开下拉时，每个选项应是**紧贴文字的色块**（.tag-chip），
   而不是原生 <option> 那种「整个选项宽度全铺背景色」。选中项左侧带勾选对号。
   结构：.csel(定位) > select.csel-native(隐藏但仍可聚焦) + button.csel-trigger(可见外壳)
   面板 .csel-panel 挂到 body + position:fixed → 不受弹窗 / 表格 overflow 裁剪。
   注：原生 select 保留在表单里 → 提交值、required 校验、onchange 全部照旧。
   ============================================================ */
.csel { position: relative; display: inline-flex; max-width: 100%; vertical-align: middle; }
.form-row .csel, .modal-grid .csel { display: flex; width: 100%; }
.csel > select.csel-native {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; pointer-events: none;   /* 完全不可见/不可点，但仍在表单里（提交 + 必填校验） */
}
.csel-trigger {
  flex: 1 1 auto; min-width: 0; margin: 0; appearance: none; -webkit-appearance: none;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  box-sizing: border-box; text-align: left; cursor: pointer;
}
/* 无自带样式的下拉（表单字段等）→ 触发器套用系统控件外观 = 「控件标准 · 下拉」Medium 档
   文字左距 16 · 文字↔箭头 8（=.csel-trigger 的 gap）· 箭头 12px · 箭头↔右边 12 */
.csel-trigger.csel-plain {
  height: var(--sel-h-md); min-height: var(--sel-h-md);
  padding: 0 var(--sel-pr-md) 0 var(--sel-px-md);
  border: 1px solid var(--field-border);
  background: var(--field-bg); color: var(--text); font-family: inherit; font-size: var(--sel-fs-md); outline: none;
}
/* 箭头统一 12px（csel.js 生成的 SVG 是 14px，按标准压到 12；Large 场景由页面覆写为 16px） */
.csel-caret { width: var(--sel-caret-md); height: var(--sel-caret-md); }
.csel-trigger:hover { border-color: var(--primary); }
.csel-trigger[aria-expanded="true"] { border-color: var(--primary); box-shadow: var(--ring); }
.csel-trigger:disabled { opacity: .6; cursor: not-allowed; }
.csel-trigger.is-invalid { border-color: var(--danger) !important; box-shadow: 0 0 0 3px rgba(239,68,68,.12) !important; }
.csel-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; }
.csel-val.is-placeholder { color: var(--c-gray); }
.csel-val .tag-chip { margin: 0; }
.csel-caret { flex: none; color: var(--muted); transition: transform .15s; }
.csel-trigger[aria-expanded="true"] .csel-caret { transform: rotate(180deg); }

.csel-panel {
  /* z-index 260 → **6000**（用户 2026-09-23：弹窗里的下拉「压根儿不出值」）——
     面板挂 body + position:fixed，而 `.pi-add-modal` 弹窗是 z-index **5000** ⇒ 260 会被弹窗整块盖住，
     点开触发器看不到任何选项 。取 6000 = 高于弹窗(5000)/抽屉(4000)、低于确认框(9000) */
  position: fixed; z-index: 6000; min-width: 120px; max-height: 280px; overflow: auto;
  padding: 5px; background: var(--card); border: 1px solid var(--line);
  border-radius: 0; box-shadow: var(--shadow);
}
/* 选项行（用户 2026-09-17）：内容高度 32 · 文字上下居中 · 左对齐；
   已选中的那一项左侧显示对号（.csel-check）。
   **选项行不加背景色**（用户 2026-09-17 纠正）：颜色只体现在紧贴文字的色块（.tag-chip）上，
      那是「选项值本身的颜色」；整行铺底是错的。 */
.csel-opt {
  display: flex; align-items: center; justify-content: flex-start; gap: 8px;
  height: 32px; padding: 0 10px; border-radius: 0;
  cursor: pointer; user-select: none; font-size: var(--fs-14); color: var(--text);
  text-align: left; white-space: nowrap;
  background: transparent;
}
.csel-opt:hover { background: var(--bg-list); }
.csel-opt .csel-check { flex: none; width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; color: var(--primary); visibility: hidden; }
.csel-opt.is-sel .csel-check { visibility: visible; }
.csel-opt .tag-chip { margin: 0; }

/* ============================================================
   下拉标准 · 全站铺开（用户 2026-09-17：「铺」）
   ------------------------------------------------------------
   三档标准定义在文件上方（.sel / .sel-sm / .sel-lg + --sel-* 变量），但历史控件是
   「每个容器各写一套」。这里按「位置 → 档位」一次性覆盖到位。
   必须放在文件**末尾**（同特异性时后者优先），否则会被下面各处的历史规则反盖。
     · Medium（32 高 / 文字左 16 / 箭头 12 / 箭头距右 12）：
       转入面板、明细行弹窗、权限页、企微设置、字段设置自动编号、供货「货品信息带出区」、
       表单内的辅助下拉（颜色 / 单位换算 / 人员 / 小数位）
     · Small（24 高 / 文字左 8 / 箭头 12 / 箭头距右 8）：
       分页「每页 N 条」、采购·出货·生产明细表、订单明细表
     · **保持不动**：列表页筛选行 `.filter-select`（用户为「一行放得下」压到 102px 宽，
       套标准会截断「全部发货状态」这类提示文字）、只读锁定态 `select.pt-ro`
       （那是文字展示，不是可交互控件）
   ============================================================ */
.to-panel select:not([multiple]),
.pi-modal .pi-f select:not([multiple]),
.pm-field select, .font-pick select, .auto-grid select,
.pg-size select,                                        /* 分页「每页 N 条」= Medium 档（高 32） */
.form-row .gp-line select, .form-row select.gp-fill,
.cat-sub-inline select, .num-line > select, .num-dec select, .person-line > select {
  height: var(--sel-h-md); min-height: var(--sel-h-md);
  padding: 0 32px 0 var(--sel-px-md);
  font-size: var(--sel-fs-md); line-height: 1;
  background-color: var(--field-bg);
  background-image: var(--sel-arrow); background-repeat: no-repeat;
  background-position: right var(--sel-pr-md) center;
  background-size: var(--sel-caret-md) var(--sel-caret-md);
  appearance: none; -webkit-appearance: none;
  color: var(--text); font-family: inherit;
}
.pi-table select:not(.pt-ro),
.oi-table select {
  height: var(--sel-h-sm); min-height: var(--sel-h-sm);
  padding: 0 28px 0 var(--sel-px-sm);
  font-size: var(--sel-fs-sm); line-height: 1;
  background-color: var(--field-bg);
  background-image: var(--sel-arrow); background-repeat: no-repeat;
  background-position: right var(--sel-pr-sm) center;
  background-size: var(--sel-caret-sm) var(--sel-caret-sm);
  appearance: none; -webkit-appearance: none;
  color: var(--text); font-family: inherit;
}

/* ============================================================
   输入框标准 · 全站套用（用户 2026-09-17 给定 input 规范）
   ------------------------------------------------------------
   三档定义在文件上方（.inp / .inp-sm / .inp-lg + --inp-* 变量），
   这里把各容器「各写一套」（历史高度 28/30/32/34/36、内距 8/9/10 混用）统一覆盖。
   必须放在文件**末尾**（同特异性后者优先）。
     · Medium（高 32 / 文字距边界 12 / 字 14）：
       表单与弹窗表单、列表筛选行搜索框、权限页搜索、企业微信设置字段、
       字段设置自动编号、明细行字段弹窗、分页「跳至」
     · Small（高 24 / 文字距边界 8 / 字 14）：
       分类搜索 / 分类内联编辑、采购·出货·生产明细表内、订单明细表内
     · **保持不动**：明细/表单里的只读锁定态（文字展示，不是可交互控件）
   ============================================================ */
.form-row input[type=text], .form-row input[type=number], .form-row input[type=password],
.form-row input[type=date],
.cs-input,
.perm-search input,
.pm-field input,
.interop-extra input,
.pi-modal .pi-f input:not([type=hidden]),
.pg-input,
.to-panel input,
.to-modal input,
.auto-grid input {
  height: var(--inp-h-md); min-height: var(--inp-h-md);
  padding: 0 var(--inp-px-md);
  font-size: var(--inp-fs-md); line-height: 1;
}
/* 多行文本（textarea）套 Medium 的**字号与内距**，高度自适应 */
.form-row textarea,
.pi-modal .pi-f textarea,
.to-panel textarea {
  padding: 6px var(--inp-px-md);
  font-size: var(--inp-fs-md);
  /* 多行文本默认高度 = **普通输入框高度的 2 倍**（用户 2026-09-18 14:42）：
     32 × 2 = 64。这条在文件靠后、会盖掉前面 `.form-row textarea` 的同名值，改高度只改这里就够 */
  min-height: calc(var(--inp-h-md) * 2);
}
/* Small 档：表格 / 分类里的紧凑输入 */
.cat-search input,
.cat-inline input,
.pi-table input:not([type=checkbox]):not([type=radio]),
.oi-table input:not([type=checkbox]):not([type=radio]) {
  height: var(--inp-h-sm); min-height: var(--inp-h-sm);
  padding: 0 var(--inp-px-sm);
  font-size: var(--inp-fs-sm); line-height: 1;
}

/* ---------- 搜索框 = Medium 档   图标 16px · 距右边界 12px · 图标↔文字 8px → 右侧文字内距 36px（= --inp-px-ico）
   覆盖的搜索框：通用列表页（name=q）、订单中心 / 操作日志（name=kw）、
   转入选单弹窗、字段设置的分类搜索、权限设置搜索、明细行的供应商搜索
   用 CSS 背景图而非一个一个改 HTML：类名/属性选择器自动覆盖全部，新加列表页也自动生效 */
.cs-input,
.cat-search input,
.perm-search input,
.tp-search input,
.sup-in,
input[name="kw"],
input[name="q"],
input[type="search"] {
  padding-right: var(--inp-px-ico);
  background-image: var(--inp-search-ico);
  background-repeat: no-repeat;
  background-position: right var(--inp-ico-in) center;
  background-size: var(--inp-ico) var(--inp-ico);
}

/* ============================================================
   卡片与卡片之间统一 **16**（用户 2026-09-18 16:10：「全部调整为16」）
   ------------------------------------------------------------
   改前实测（同一张页面里会出现 3 种间距）：
     · 订单详情 4 处全是 **14** ← `.app-main > .card { margin-top: 14px }`
     · 货品/供货详情的关联卡片之间 **14** ← `.dash-grid` 的 `gap: 14px` + `.dash-grid.rel-cards` 的上下 14
     · 出货/生产详情第 2 张之后 **12** ← `.pf-card` / `.pi-card` 的上下外边距
   这里统一用**下外边距堆叠**（与表单里那套 `.form-flat` 一致）：
     · `.app-main > .card` 上 16（首张 0）—— 覆盖订单详情 / 各设置页的卡片栈
     · `.dash-grid.rel-cards` 上下 16 + `gap: 16px` —— 覆盖关联卡片栈
     · `.po-card / .pf-card / .pi-card / .dt-card` 上 0、下 16 —— 覆盖采购/出货/生产的详情卡片栈 + 明细字段卡
       （`.dt-card` 于 2026-09-19 20:0x 补入：它原来定义处写 12，是这条「统一 16」里唯一的漏网之鱼 ）
   必须放文件末尾（同特异性后者优先）；表单里 `.form-flat .po-card/.pi-card/.pf-card`（特异度更高）
      仍是 16（`.pf-card` 在表单里刻意保留 `margin-bottom: 0`）
   ============================================================ */
.app-main > .card,
.app-main > form > .card { margin-top: 16px; }        /* form 包裹（订单详情就地编辑）也要算 —— 否则点编辑后卡片间距变 0 */
.app-main > .card:first-of-type,
.app-main > form > .card:first-of-type { margin-top: 0; }
/* 首页：`.dash-grid` 紧跟在「今日汇率」卡片后面时原来是 **0**（卡片 mb 0 + 网格 mt 0）→ 补 16 */
.app-main > .dash-grid { margin-top: 16px; }
.dash-grid.rel-cards { margin-top: 16px; margin-bottom: 16px; gap: 16px; }
.related-card { margin-top: 16px; }
.detail-card { margin-bottom: 16px; }
.po-card, .pf-card, .pi-card, .dt-card { margin-top: 0; margin-bottom: 16px; }
/* 详情页卡片**阴影统一**（用户 2026-09-19 20:0x：「之前卡片要加阴影的，貌似你只是在采购单进度卡片添加了，
   下方的转入详情卡片，采购明细卡片，入库明细卡片，都没有添加阴影」）——
   实测（headless 算 computedStyle）：`.pt-steps-box`（进度卡）= `--shadow-lg`（.08 / .10，**看得见**），
   而 `.po-card / .pi-card / .dt-card` 只有全局 `.card` 的 `--shadow`（.04 / .05，**几乎看不出**）
   → 这里统一成进度卡同款 `--shadow-lg` */
.po-card, .pf-card, .pi-card, .dt-card, .df-card { box-shadow: var(--shadow-lg); }
/* 生产的「生产计划 / 生产结果」两张半宽卡也要走标准卡片样式（用户 2026-09-20 15:27：
   「卡片阴影不对，字段名称距离卡片边界是 14」）——
   新卡**必须进这两条名单**，否则阴影只有 .04、间距变 32 */
.card.prod-half { box-shadow: var(--shadow-lg); margin-top: 0; margin-bottom: 16px; }
   /* `.df-card`（单据动态卡）也一起压成 `--shadow-lg`（用户 2026-09-19 21:5x：
      「这种卡片的阴影也不对」）—— 它原来走 `.card` 的 `--shadow`（.04/.05，明显更淡）*/

/* 2026-09-18 15:56 试过一版「详情页字段上下模式」（列名在上、内容在下），
   用户 15:58「算了，还是左右吧」→ **已撤回**，详情页字段保持**左右模式**（列名右对齐 + 16，见 `.di-label` / `.kv-k` 那几处）*/

/* ============================================================================
   明细卡片（内置「货品明细」`.pi-card` + 明细字段 `.dt-card`）= **同一套布局**
   （用户 2026-09-18 23:13：「新的明细卡片和老的明细卡片，布局需要是一样的」）
   · 结构统一：`.pi-bar` 操作栏（左 = 加行 tab、右 = 条数 + 折叠箭头）+ `.pi-wrap` 表格区（弹性撑满）
     + 占位行补足剩余高度 + 合计行 `tfoot` 贴底 + 表头吸顶 —— 明细字段卡**直接复用**内置卡那套类名
   · 折叠各自独立（状态按 `data-collapse-key` 记 localStorage，见 public/js/cardCollapse.js）
   · 必须放文件**末尾** —— 要盖掉 `.pi-card{height:450px}` / `.dt-table` 那几条旧规则
   ============================================================================ */
.card-toggle {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border: 0; background: none;
  color: var(--muted); cursor: pointer;
}
.card-toggle:hover { color: var(--text); }
.card-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.card-toggle svg { display: block; transition: transform .16s ease; }
.is-collapsed .card-toggle svg { transform: rotate(-90deg); }   /* 折叠时箭头朝右（展开 → 朝下）*/

/* 操作栏里的**明细名**（内置货品明细卡没有它，明细字段卡用来说明这是哪张表） */
.pi-bar-tit { font-size: var(--fs-14); color: var(--text); }

/* —— 明细字段卡：整体套用内置卡的外壳（内衬 0 + 与内置卡同一高度变量 + 竖向 flex） —— */
.dt-card { padding: 0; height: auto; display: flex; flex-direction: column; overflow: hidden; margin: 0 0 16px; }
/* 必须显式 `align-items: stretch`：`.dt-field` 这个类名**详情页就地编辑**也在用
   （line 2498 那条 `.dt-field{display:flex;align-items:center}`），不重置的话在竖向 flex 里会把
   表格区按内容宽度**居中缩成一小条**（用户会看到明细卡里表格只占中间一小块）*/
.dt-card > .dt-field,
.dt-card .dt-host { flex: 1 1 auto; display: flex; flex-direction: column; align-items: stretch; min-height: 0; min-width: 0; flex-wrap: nowrap; }
   /* `flex-wrap: nowrap` 和 `min-width: 0` 都不能省（用户 2026-09-19 22:5x：
      「整个系统横向竟然都超屏了」/「你是没给入库明细增加超卡片的横向滚动条是吗？」）——
      · `.dt-field` 在详情页就地编辑里还带着 `flex-wrap: wrap`（line 2648）→
        **column + wrap** 时 `align-items: stretch` 不再把子项拉满容器宽，子项按**内容宽**排到第二列 →
        `.dt-host` / `.dt-table-wrap` 会跟着表格一起变宽，卡片内部反而不横滚、内容被裁掉
      · flex 子项默认 `min-width: auto`，表格被拖宽后会把 `.dt-host` / `.dt-field` 一起撑大 →
        卡片溢出 → 表单溢出 → **整页冒出横向滚动条**
      两处都修好之后：表格变宽只让 `.dt-table-wrap` **内部横滚**（超卡片就有卡内横向滚动条 ），
      卡片宽度纹丝不动、整页永不溢出 */
.dt-card .dt-table-wrap { flex: 1 1 auto; min-height: 0; min-width: 0; overflow: auto; }

/* —— 表头 / 数据行 / 输入框：与内置 `.pi-table` 完全同款（表头吸顶、行高 33、输入 24、竖线、居中） —— */
/* `table-layout: fixed`（用户 2026-09-19 22:45）：原来 `auto` 会按内容分配列宽 →
   列宽忽宽忽窄、新加的字段列**落不到整齐的列边界上**（看着像没分割线）
   改 fixed + `<colgroup>` 后：前两列固定 44/64、其余**平分剩余宽度**，每条分割线都在真实列边界上 */
.dt-card .dt-table { width: 100%; table-layout: fixed; }
.dt-card .dt-table thead th {
  position: sticky; top: 0; z-index: 3; background: var(--bg-list);
  height: 29px; padding: 3px 6px; font-size: var(--fs-14); font-weight: 600; text-align: center;
  border-right: 1px solid var(--line);
}
   /* 「入库明细」的**列名称行**同样保持灰底（用户 2026-09-19 22:43）——
      与 `.pi-card .pi-table thead th` 同口径；要变白的是「入库明细所在行」= 操作栏 `.pi-bar` */
.dt-card .dt-table tbody td { height: 33px; padding: 0 6px; text-align: center; border-right: 1px solid var(--line); border-top: 1px solid var(--line); }
.dt-card .dt-table input, .dt-card .dt-table select, .dt-card .dt-table textarea {
  width: 100%; height: 24px; min-height: 24px; padding: 0 6px; border: 0; background: transparent; font-size: var(--fs-14);
}
/* 占位行：把剩余高度补给最后一行 → 合计行贴卡片底（选择器要比 `tbody td` 更具体才盖得住） */
.dt-card .dt-table tbody.dt-fill td { height: 0; padding: 0; border: 0; }
/* 明细表只有**前两列**（# / 操作）横滚时钉住 —— 内置 `.pi-table` 那条 `:nth-child(3)` 是给它自己的
   「快捷录入」列写的（left:109px），明细表第 3 列是普通数据列，必须把偏移复位
   复位时**只能用 `position: relative`，绝不能用 `position: static`** ——
   `th` 一旦变成 static，表头里那个**绝对定位的拖拽把手 `.pi-resizer`** 就会以
   「初始包含块（页面）」为参照 → 全部跑到**视口右上角**去（7px 宽、整屏高、看不见），
   还会多出 3px → **整页冒横向滚动条** （2026-09-19 22:5x 用户报「整个系统横向竟然都超屏了」，实测就是这个）
   `relative` 同样是定位元素（能当把手参照），但不会粘在左侧 */
/* 展开列去掉后，第 1 列就是「#」⇒ 操作列贴到 **28px**（用户 2026-09-21 17:5x；
   28 是**实测值**（freeze() 量出来的 # 列真实宽），不是 CSS 里写的 34px ）*/
.dt-card .pi-table th:nth-child(2), .dt-card .pi-table td:nth-child(2) { left: 28px; }
.dt-card .pi-table th:nth-child(3), .dt-card .pi-table td:nth-child(3) { position: relative; left: auto; z-index: auto; }
/* **合计行**（`.pi-total-table` 的 tfoot）前 3 格**冻结**（用户 2026-09-21 18:3x：
   「上方的采购明细总计那一行也需要和主表一起滚动。然后你滚动的时候，不要把总计两个字滚动走了。
     它的左侧包括它是冻结状态」）——
   「总计」二字落在**第 3 格**（= 主表「操作」列位置），而上一行那条复位规则
      （`.dt-card .pi-table td:nth-child(3){position:relative}`）会把它也一并复位 ⇒ 横滚时总计跟着走
   ⇒ 这里用更具体的选择器（多一个 `tfoot`）夺回来；前 2 格原本就是 sticky，一并写明 */
/* 选择器必须是 `.pi-card`（采购明细卡的类）—— 之前写成 `.dt-card`（那是**另一张卡**）⇒ 整条规则没匹配
   而且 z-index 必须**高于同一 tfoot 里的其它格**：`.pi-card .pi-table tfoot td` 会把**所有** tfoot td
      都设成 `position: sticky; z-index: 3` ⇒ 冻结格与右侧格**同层** ⇒ DOM 靠后的右侧格盖住左侧冻结格
      （用户 2026-09-21 18:3x：「拖动滚动条的时候，右侧内容会把左侧内容覆盖」）*/
.pi-card .pi-total-table tfoot td:nth-child(1) { position: sticky; left: 0; z-index: 6; }
.pi-card .pi-total-table tfoot td:nth-child(2) { position: sticky; left: 28px; z-index: 6; }
.pi-card .pi-total-table tfoot td:nth-child(3) { position: sticky; left: 56px; z-index: 6; }

/* **合计行放在滚动区之外**（独立表 `.dt-total`，与内置「货品明细」的 `.pi-total-table` 同款）——
   用户 2026-09-19 22:45：「总计行位置不对」：原来它是主表里的 `<tfoot>`（在滚动区内），
   被 cardCollapse 算进卡片高度 → 「总计」恒贴卡片底、与数据行之间空出 1 行
   挪出来之后：表格区 = 表头 + N 行（含占位行），合计行永远紧跟在卡片底部、列宽靠 colgroup 镜像对齐 */
.dt-card .dt-total { flex: 0 0 auto; width: 100%; max-width: 100%; min-width: 0; }
/* 下面这几条**不带 `.dt-card` 前缀** —— 供货「新建」整页那种非卡片模式用的也是同一套骨架，
   带上前缀那边就没有合计行的底色/高度了 */
.dt-total tfoot td {
  background: var(--bg-list);
  height: 29px; padding: 3px 6px; font-size: var(--fs-14); text-align: center;
  border-top: 1px solid var(--line);
}
   /* 竖分割线**不要在这里写** —— `.pi-total-table td { border-right }` 已经有了，
      再加一条会与 `td:last-child{border-right:0}` 冲突出双线 */
/* 合计行**水平 + 垂直都居中**（用户 2026-09-19 19:4x：「总计对应的内容，是表格左右上下居中，不是靠右对齐」）——
   与内置「货品明细」卡的总计行（`.pi-total-row td{text-align:center}` + `.pi-table td{vertical-align:middle}`）完全一致
   原来是 `.dt-sum{text-align:right}` / `.dt-sum-lab{text-align:left}` —— 已改成 center，别改回去 */
.dt-total tfoot td.dt-sum { text-align: center; }        /* 数值居中（不再右对齐） */
.dt-total tfoot td.dt-sum-lab { text-align: center; }    /* 「总计」二字居中 —— 它落在**操作列**下方 */
/* 加行按钮已挪到顶部操作栏 → 表下那行收掉（供货「新建」整页不是卡片，照旧保留） */
.dt-card .dt-actions { display: none; }
/* 曾经给「一列都没有的明细」做过高度自适应 —— 用户 2026-09-18 23:20 明确「2 个明细卡片时每张 300」，
   为了**两张卡一样高**已取消：所有明细卡都按 `--dt-card-h` 走，空卡也是这个高度 */
.dt-card .dt-empty-hint { padding: 12px 14px; margin: 0; }

/* —— 折叠：只留操作栏（`display:none` 里的隐藏域**照样提交** ） —— */
.pi-card.is-collapsed { height: auto; }
/* 折叠**不再把表格整块藏起来**（用户 2026-09-19 19:3x：「折叠的时候最少显示 1 行」）——
   表格区（`.pi-wrap` / `.dt-field`）因此**不进这条 display:none 名单**，只留「表头 + 1 行」，
   高度同样由 public/js/cardCollapse.js 的 fitDetailCards() 按 1 行算
   别再把 `.pi-wrap` / `.dt-field` 加回来 */
/* 折叠时**「总计」那一行不跟着折叠**（用户 2026-09-19 21:0x：「采购明细，当我折叠的时候，
   总计行是不需要折叠的」）—— `.pi-total-table` 已从下面的 `display:none` 名单里**移出**
   （它本来就在 `.pi-wrap` **外面**：横向滚动条在表格与「总计」之间，所以折叠表格区不影响它）*/
.pi-card.is-collapsed > .pi-foot,
.pi-card.is-collapsed > .pm-empty { display: none; }

/* ============================================================
 * 直角白名单（用户 2026-09-19）：「取消全局直角，除了 table 和卡片必须直角」
 *   → 只有 **卡片 / 表格（含列表、明细单）** 保持 0；其余（按钮 / 输入框 / 下拉 / 弹窗 /
 *     标签 / 徽标 / 日期面板 / 下拉菜单…）走 `--radius*` 令牌的圆角，按设计规范来
 *   放在**文件末尾**：同特异度下靠「后出现」覆盖前面的圆角声明
 * ============================================================ */
.card, .card .table-wrap, .table-wrap, .data-table, .entity-table,
.stat, .related-card, .detail-card, .pm-card, .po-card, .pt-steps-box,
.perm-side, .perm-wrap, .perm-table, .rates-grid-wrap, .rates-grid,
.info-table-shell, .pi-card, .pi-wrap, .oi-table-wrap, .dt-card, .login-card,
.filter-bar, .list-topbar {
  border-radius: 0;
}

/* ═══ 首行「通栏色带」（用户 2026-09-19：「全局，详情页面，首行需要贴顶部，背景颜色主色，不透明度40%」）═══
   · 采购 / 生产 / 出货 的 **新增 / 编辑 / 详情** 三种形态首行都 = #ptBar → 模板**一律**加 .pt-bar-ro
     （原先是「只有只读详情态才加」，用户 2026-09-19 19:3x：「新增页面的首行和详情页首行逻辑一样」→ 已统一）
   · 订单中心详情首行 = .card.ord-bar（只读 / 就地编辑共用）；
   · 贴顶 + 通栏：负 margin 抵消 .app-main 的 16px padding；底色 = 主色 40%（color-mix 对透明混合，深浅主题都成立）。 */
.form-flat .pt-bar-ro {
  /* 左侧多盖 1px（用户 2026-09-21 14:5x：色带与左侧导航之间有 1 像素缝）——
     缩放 / 小数像素下的亚像素接缝：margin-left -17 + padding-left 17 ⇒ 内容位置不变、缝被盖住
     右侧不动（padding-right 16）避免溢出 */
  margin: -16px -16px 16px -17px;
  padding: 0 17px;
  padding-right: 16px;
  background: #fff;   /* 用户 2026-09-22 17:2x 改为**白色**（2026-09-21 曾指定淡紫 #aca7ff）——
                          白底 → 首行深色文字清晰
                          暗色主题下由下面那条 `[data-theme="dark"]` 规则改回深色卡片底，
                             否则白底 + 暗色的浅字会直接看不清 */
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-lg);   /* 跟随主题（亮色 .08/.10、暗色 .45/.5）—— 原来硬编码 rgba(15,23,42,.14)，暗色下看不见 */
  /* 高 36 保持，但改成 **min-height**（用户 2026-09-21 12:5x）——
     窄窗口下工具条按钮会换行（`.pt-bar` 本身 `flex-wrap: wrap`），写死 `height: 36` 会把换行出来的
     按钮**压出色带之外**（实测 900px 就有 2 个、520px 有 5 个元素溢出 ）⇒ 允许它按内容长高
     （配合上面那条：编号格 `flex:0 0 auto` + nowrap ⇒ 编号永远单行，装不下时整行换行 ） */
  min-height: 36px;
  padding-top: 2px;
  padding-bottom: 2px;
  display: flex;
  align-items: center;
  position: sticky;
  /* 必须让开固定顶栏：`.app-topbar` 是 `fixed; top:0; height:60px; z-index:50` ——
     这里若写 `top: 0`，首行滚到顶后**被顶栏整个盖住**（z-index 10 < 50）→ 看起来就是「没固定」
     所以粘附位置 = 顶栏高度 60px，正好贴在顶栏下沿 */
  top: 60px;
  z-index: 10;
}
/* 顶部出现操作提示条（wc-status，在 .form-flat 直属位置）时不再上提，避免色带盖住提示 */
.form-flat:has(> .wc-status) .pt-bar-ro { margin-top: 0; }
/* 首行已有工具条 #ptBar → 基线的 `.form-title-row`（entity/form.ejs 那个只有标题/系统编号的行）
   **直接用 CSS 隐掉**（同一手法见 `.edit-form:has(#ptBar) .form-actions`）——
   原先只靠 JS 的 hideDup()，CSS 没生效前会先渲染一帧 → 首行色带上提后**与它叠在一起闪一下**
   `> ` 是必须的：#ptBar 本身也在 form 里、带着 .form-title-row 类，不加会把自己也隐藏 */
.form-flat:has(#ptBar) > .form-title-row:not(#ptBar) { display: none; }
.app-main > .card.ord-bar,
.app-main > form > .card.ord-bar {
  /* 左侧多盖 1px（用户 2026-09-21 14:5x：色带与左侧导航之间有 1 像素缝）——
     缩放 / 小数像素下的亚像素接缝：margin-left -17 + padding-left 17 ⇒ 内容位置不变、缝被盖住
     右侧不动（padding-right 16）避免溢出 */
  margin: -16px -16px 16px -17px;
  padding: 0 17px;
  padding-right: 16px;
  background: #fff;      /* 🆕 用户 2026-09-26：「订单中心详情页，上面的固定条，背景颜色要改为白色」
                           —— 原来这里是淡紫 #aca7ff（2026-09-21 指定）；
                             现在与采购 / 生产 / 出货首行的 `.pt-bar-ro` **完全一致**
                             （那边 2026-09-22 已按用户要求改白，订单这条当时漏了）✓
                           暗色主题下由下面那条 `[data-theme="dark"]` 规则改回深色卡片底，
                              否则白底 + 暗色的浅字会直接看不清 ✓ */
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-lg);   /* 跟随主题（亮色 .08/.10、暗色 .45/.5）—— 原来硬编码 rgba(15,23,42,.14)，暗色下看不见 */
  /* 高 36 保持，但改成 **min-height**（用户 2026-09-21 12:5x）——
     窄窗口下工具条按钮会换行（`.pt-bar` 本身 `flex-wrap: wrap`），写死 `height: 36` 会把换行出来的
     按钮**压出色带之外**（实测 900px 就有 2 个、520px 有 5 个元素溢出 ）⇒ 允许它按内容长高
     （配合上面那条：编号格 `flex:0 0 auto` + nowrap ⇒ 编号永远单行，装不下时整行换行 ） */
  min-height: 36px;
  padding-top: 2px;
  padding-bottom: 2px;
  display: flex;
  align-items: center;
  position: sticky;
  /* 同上：粘附位置 = 顶栏高度，否则会被 fixed 顶栏（z-index 50）盖住 */
  top: 60px;
  z-index: 10;
}
/* 顶部有 toast / wc-status 的详情页（订单详情的错误横幅是**文档流内**的话）同理不上提 */
.app-main:has(> .wc-status) > .card.ord-bar,
.app-main:has(> form > .wc-status) > form > .card.ord-bar { margin-top: 0; }


/* ============================================================
   单据动态（用户 2026-09-19）—— 详情页最底部的大卡
   「谁修改了什么，1 行 1 条」+ 筛选（操作人 / 操作类型）+ 评论 @提醒
   卡片本身直角、阴影走 --shadow-lg（与详情页其它卡片一致，见文件末尾的统一规则）
   ============================================================ */
/* 上间距**不自己加**（用户 2026-09-19 21:5x 实测反馈「两处的间距不对」）——
   实测：上方卡 `.dt-card` 有 `margin-bottom:16`，本卡再 `margin-top:16` → 实际间隙 **32px**
   改成 `margin-top:0`，与全站其它卡片同一口径（全靠**上方卡片的 margin-bottom** 撑开 = 16px）
   下间距必须**自己给 16**（同上，用户 21:5x 追问「你与下方的创建人行间距没有调整啊」）——
   实测原来是 **0px**（`.df-card` mb=0、下方 `.pi-foot-meta` mt=0 → 直接贴在一起）*/
/* 采购处理：整张卡宽 **360**（用户 2026-09-22 20:2x：「把单据动态宽度调整为 360」）*/
.df-card { margin-top: 0; margin-bottom: 16px; width: 360px; max-width: 100%; }
.df-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.df-head h2 { margin: 0; }
.df-head .df-sel { height: var(--sel-h-sm, 24px); font-size: var(--fs-13); }
/* 两个筛选（全部人员 / 全部操作）**贴左**：紧跟「单据动态 · 共 N 条」之后（用户 2026-09-19 23:0x 定稿）——
   别加 `margin-left: auto`：那会把它们顶到卡片最右（23:0x 试过一版，用户随即要求「两个筛选按钮贴左」）*/
.df-filters { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
/* 左右两列（用户 2026-09-19 21:4x）：左 = 动态列表（弹性撑满），右 = 发表评论（窄，固定 300） */
/* 分隔线从 `.df-list` 挪到 `.df-body`（用户 2026-09-20 08:2x：「让发表评论的输入框…与第一条
   单据动态顶部对齐」）—— 原来 `border-top` 挂在左列 `.df-list` 上，只有左列被下推 1px，
   两列起点差 1px；挂到 `.df-body` 后**两列内容框起点完全相同**，输入框上边缘与第一条动态上边缘**严丝合缝** */
/* 改成**上下排列**（用户 2026-09-22 20:2x：「填写评论在上方，动态在下方」）——
   原为左右两列（左=动态列表、右=评论 300px）⇒ 现在：第一块 = 发表评论、第二块 = 动态列表 */
.df-body { display: flex; flex-direction: column; align-items: stretch; gap: 16px; border-top: 1px solid var(--line); }
.df-main { flex: 0 0 auto; width: 100%; min-width: 0; }
.df-side { flex: 0 0 auto; width: 100%; display: flex; flex-direction: column; gap: 8px; }
/* `.df-side-tit`（「发表评论」小标题）已删（2026-09-20 08:2x）—— 它会把输入框往下推约 26px，
   与第一条动态顶部对不齐；文案已并入 textarea 的 placeholder */
.df-side .df-mention { position: relative; }
.df-side .btn#dfSend { align-self: flex-end; }
/* 原「≤900px 才竖排」的媒体查询已删 —— 现在**本来**就是上下排列 */
.df-list { list-style: none; margin: 0; padding: 0; max-height: 340px; overflow: auto; }
   /* 这里**不再写 `border-top`** —— 分隔线已上提到 `.df-body`（见上），
      挂在列表上会把左列单独下推 1px，导致输入框与第一条动态差 1px */
.df-row { display: flex; align-items: flex-start; gap: 10px; padding: 7px 2px; border-bottom: 1px solid var(--line); font-size: var(--fs-13); }
.df-row.is-cmt { background: var(--primary-soft); }
.df-time { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.df-who { flex: 0 0 auto; color: var(--text); min-width: 72px; }
.df-act { flex: 0 0 auto; color: var(--ink-2); }
.df-act.is-cmt { color: var(--primary); }
.df-text { flex: 1 1 auto; min-width: 0; color: var(--text); word-break: break-word; }
.df-at { font-style: normal; color: var(--primary); font-weight: 600; }
.df-empty { padding: 18px 2px; color: var(--muted); font-size: var(--fs-13); text-align: center; }
.df-cmt { margin-top: 10px; }
.df-in { width: 100%; box-sizing: border-box; min-height: 48px; padding: 6px 8px; font-family: inherit;
  font-size: var(--fs-13); color: var(--text); background: var(--bg-list); border: 1px solid var(--field-border, var(--line));
  resize: vertical; }
.df-cmt-bar { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
/* 「@ 提醒」按钮 + **已选人员名单同行**（用户 2026-09-19 23:0x：
   「@提醒人的时候，选择的提醒人员并没有出现在提醒按钮后面」）——
   原来是纯 block → 名单只能另起一行；改 flex 后按钮后面紧跟「张三、李四」
   （选人下拉框是 absolute，不受父级 flex 影响 ） */
.df-mention { position: relative; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.df-picked {
  font-size: var(--fs-12); color: var(--primary); line-height: 1.4;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.df-picked:empty { display: none; }
.df-mention-box { position: absolute; left: 0; bottom: calc(100% + 6px); z-index: 30; display: none;
  flex-direction: column; gap: 4px; max-height: 220px; overflow: auto; min-width: 160px; padding: 8px;
  background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow-lg); }
.df-mention-box.open { display: flex; }
.df-mk { display: flex; align-items: center; gap: 6px; font-size: var(--fs-13); cursor: pointer; white-space: nowrap; }

/* ============================================================
   悬浮提示条（用户 2026-09-19）—— 详情页的错误 / 提示**浮在页面之上**，
   不再挤在卡片里把内容往下推；**3 秒自动消失**，也能点 × 手动关掉。
   加 `.is-float` 才生效（原来的 `.wc-status` 文档流形态保留给企业微信那类「状态说明」用）
   ============================================================ */
.wc-status.is-float {
  position: fixed; z-index: 80;
  /* 位置 = **屏幕正中**（用户 2026-09-19 21:4x 明确：「我需要的是屏幕中间」）——
     原来是顶部居中（top:76px），已按用户要求改成垂直 + 水平都居中 */
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: max-content; max-width: min(720px, 92vw);
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0; box-shadow: var(--shadow-lg);
  transition: opacity .3s ease;
}
.wc-status.is-float.is-out { opacity: 0; }
.wc-status.is-float.is-gone { display: none; }
.wc-status.is-float .wc-x {
  flex: 0 0 auto; margin: 0; padding: 0 2px; border: 0; background: transparent;
  color: inherit; font-size: 16px; line-height: 1; cursor: pointer; opacity: .7;
}
.wc-status.is-float .wc-x:hover { opacity: 1; }

/* 暗色主题：详情页首行回到**深色卡片底**（用户 2026-09-19 22:0x 指定的 `#d4e3f0` 是浅色 ——
   暗色下会刺眼，且「浅底 + 暗色主题的浅字」直接看不清）*/
[data-theme="dark"] .form-flat .pt-bar-ro,
[data-theme="dark"] .app-main > .card.ord-bar { background: var(--card); }

/* ============================================================
   消息标识（用户 2026-09-19）—— 顶栏右上角铃铛 + 未读角标 + 下拉面板
   消息 = @我的评论 + 我经手单据的变动（后端 src/msgCenter.js，接口 /api/msg）
   面板 HTML 只有一份：`views/partials/msgPanel.ejs`（由 /api/msg 返回后前端塞进来）
   ============================================================ */
.tb-msg-wrap { position: relative; }
.tb-msg { position: relative; }
.tb-msg-badge {
  position: absolute; top: -5px; right: -5px;
  min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  background: #f56c6c; color: #fff; font-size: 11px; font-weight: 600; line-height: 1;
  border-radius: 8px; border: 1px solid var(--card);
}
.msg-panel {
  display: none; position: absolute; right: 0; top: calc(100% + 8px);
  width: min(420px, 92vw); max-height: min(70vh, 520px); overflow: hidden auto;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); z-index: 60; text-align: left;
}
.msg-panel.open { display: block; }
.msg-loading, .msg-empty { padding: 18px 14px; text-align: center; color: var(--muted); font-size: var(--fs-13); }
.msg-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; gap: 8px; padding: 9px 12px;
  background: var(--card); border-bottom: 1px solid var(--line);
}
.msg-tit { font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--text); }
.msg-cnt {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: #f56c6c; color: #fff; font-size: 11px; font-weight: 600;
}
.msg-readall { border: 0; background: none; color: var(--link); font-family: inherit; font-size: var(--fs-12); padding: 2px 4px; cursor: pointer; }
.msg-readall:hover { color: var(--primary); text-decoration: underline; }
.msg-item { display: flex; gap: 8px; padding: 9px 12px; text-decoration: none; color: inherit; border-bottom: 1px solid var(--line); }
.msg-item:last-child { border-bottom: 0; }
.msg-item:hover { background: var(--primary-soft); }
.msg-dot { flex: 0 0 auto; width: 7px; height: 7px; margin-top: 6px; border-radius: 50%; background: transparent; }
.msg-item.is-unread .msg-dot { background: #f56c6c; }
.msg-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.msg-line1 { display: flex; align-items: center; gap: 6px; font-size: var(--fs-12); color: var(--muted); }
.msg-who { color: var(--text); font-weight: var(--fw-medium); }
.msg-act { padding: 0 5px; height: 17px; line-height: 17px; border-radius: var(--radius-sm); background: var(--bg-list); color: var(--muted); font-size: 11px; }
.msg-act.is-cmt { background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--primary); }
.msg-atme { padding: 0 5px; height: 17px; line-height: 17px; border-radius: var(--radius-sm); background: color-mix(in srgb, #f56c6c 14%, transparent); color: #e45656; font-size: 11px; }
.msg-time { flex: 0 0 auto; }
.msg-line2 { font-size: var(--fs-13); color: var(--text); }
.msg-line3 { font-size: var(--fs-12); color: var(--muted); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.msg-foot { padding: 7px 12px; font-size: 11px; color: var(--muted); border-top: 1px solid var(--line); text-align: center; }

/* ============================================================
   到货台账在**锁定态**（已生效 / 已结案）也必须能录入
   （用户 2026-09-20 09:05：「日期一直可改…你这个不生效」—— 真凶就是下面这几条 ）
   ------------------------------------------------------------
   `.pt-locked` / `.pi-locked` 那一套是给**货品明细主行**做的：整页的日期控件被
   `pointer-events: none`、`.dp-trigger`（日历图标）被 `display: none !important` 收掉，
   而弹窗里的「到货台账」也在 `.pi-card` 里 → **被一起禁掉**
   但「到货」本来就是单据生效之后才发生的事（台账存在的意义）⇒ 必须放行
   这里的 `!important` 是用来压过上面那条 `display: none !important` 的，删不得
   必须放在 `.pt-locked` 那几条**之后**（文件靠后）才生效
   ============================================================ */
.pt-locked .ar-table .pi-dp,
.pi-locked .ar-table .pi-dp { pointer-events: auto; }
.pt-locked .ar-table .pi-dp .dp-trigger,
.pt-locked .ar-table .dp-trigger,
.pi-locked .ar-table .pi-dp .dp-trigger,
.pi-locked .ar-table .dp-trigger { display: inline-flex !important; }
.pt-locked .ar-table .pi-dp .dp-display::placeholder,
.pi-locked .ar-table .pi-dp .dp-display::placeholder { color: var(--muted) !important; }
/* 台账里的控件一律可点（数量 / 异常品方案 / 质检下拉 / ＋）*/
.pt-locked .ar-table .po-in,
.pt-locked .ar-table .pi-in,
.pt-locked .ar-table input,
.pt-locked .ar-table select,
.pi-locked .ar-table .po-in,
.pi-locked .ar-table .pi-in,
.pi-locked .ar-table input,
.pi-locked .ar-table select { pointer-events: auto !important; cursor: auto; }
/* 锁定态把原生控件外观去掉了 → 台账里要给回来（质检下拉要看得见是下拉）*/
.pt-locked .ar-table select,
.pi-locked .ar-table select { appearance: auto !important; -webkit-appearance: auto !important; }
.pt-locked .ar-table input[type=number],
.pi-locked .ar-table input[type=number] { appearance: auto !important; -webkit-appearance: auto !important; }

/* ============================================================================
   明细卡（`.dt-card`）表格里的**日期列**改用系统日期选择器（用户 2026-09-20 11:49：
   「我在入库明细，新增的入库时间，无法编辑填写」）——
   原来 `detailField.js` 渲染的是原生 `<input type="date">`：得先点进浏览器的 年/月/日 子域才能打字，
   实测（CDP 真鼠标 + insertText）**根本打不进去** ⇒ 换成 `.dp-field.dt-dp`（与全站一致，点开日历选）
   `.dp-display` 全局 36px 高带边框 → 会把表格行撑高 ⇒ 收成单元格规格（24px / 无边框 / 文字居中）
      （口径与 `.ar-table .dp-*`、`.pi-dp` 两处一致；`.dp-field` 全局 max-width:220px 也要放开）
   这三条必须放在**文件末尾**：`style.css` 有大量末尾覆盖块，写前面会被压回 */
.dt-card .dt-dp { width: 100%; max-width: none; }
.dt-card .dt-dp .dp-display { height: 24px; min-height: 24px; padding: 0 18px 0 4px; border: 0;
  background: transparent; color: var(--text); text-align: center; font-size: var(--fs-14); box-shadow: none; }
.dt-card .dt-dp .dp-display:focus { box-shadow: none; border: 0; }
.dt-card .dt-dp .dp-display::placeholder { color: var(--muted); }
.dt-card .dt-dp .dp-trigger { width: 16px; color: var(--muted); }
.dt-card .dt-dp .dp-trigger:hover { color: var(--primary); }
.dt-card .dt-dp .dp-trigger svg { width: 12px; height: 12px; }

/* ============================================================================
   审批设置：**按模块分组的开关**（用户 2026-09-20 13:48：「采购管理中，采购明细，和入库明细，
   两个都需要支持单独开启审批。然后采购管理，生产管理，出货管理没有区分出来」）——
   采购管理一块里放两个开关（采购明细审批 / 入库明细审批），生产、出货各一块
   和上面的日期列一样，必须放**文件末尾**（前面有覆盖块）*/
.apv-mods { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 14px; }
.apv-mod { flex: 1 1 280px; min-width: 260px; border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 14px 12px; background: var(--bg-list); }
.apv-mod-t { font-size: var(--fs-13); font-weight: var(--fw-bold); color: var(--text); margin-bottom: 8px; }
.apv-mod-sw { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; cursor: pointer; font-size: var(--fs-13); color: var(--text); }
.apv-mod-sw + .apv-mod-sw { border-top: 1px dashed var(--line); }
.apv-mod-sw input { flex: 0 0 auto; margin-top: 3px; }
.apv-mod-tip { flex: 1; font-style: normal; font-size: var(--fs-12); color: var(--muted); line-height: 1.5; }
.apv-mod-sw > span { flex: 0 0 auto; font-weight: var(--fw-medium); }

/* 审批设置：**每个模块各一套流程**（点模块块 / 「配置本模块审批流程」切换）*/
.apv-flow { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.apv-flow-t { font-size: var(--fs-13); font-weight: var(--fw-bold); color: var(--text); margin-bottom: 10px; }
.apv-mod.is-on { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-soft-2); }
.apv-mod-go { margin-top: 6px; }
/* 通道级选项（用户 2026-09-24）——
   · `.apv-mod-opt`：附加勾选框（如「允许审批未完成状态添加新的审批」）
   · `.apv-mod-plans`：「不合格处理方式审批」勾选需要审批的方式 */
.apv-mod-opt { border-top: 1px dashed var(--line); }
.apv-mod-plans { padding: 6px 0 2px; border-top: 1px dashed var(--line); }
.apv-mod-plans-t { font-size: var(--fs-12); color: var(--muted); margin-bottom: 4px; }
.apv-mod-plans-l { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.apv-pl { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-13); color: var(--text); cursor: pointer; }
.apv-pl input { flex: 0 0 auto; margin: 0; }

/* ============================================================================
   区块卡片里的字段区也要走**整页表单的 3 列网格**（用户 2026-09-20 14:4x：
   「卡片里面的字段布局是不是不是按照卡片标准来的？1行几个字段呢？」）
   ----------------------------------------------------------------------------
   2507 行那条只匹配 `body:not(.embed-body) .edit-form > .modal-grid`（**直接子元素**），
      而采购 / 生产 / 出货 的字段是在**区块卡片**里（`.card > .modal-grid`）⇒ 没吃到网格，
      computed `display` 一直是 `block` = **一行一个字段**
      （实测 /productions/9：「生产接收计划日期 / 生产计划结束日期 / 备注」排成 3 行 ）
   ⇒ 这里按同一套口径补齐：**3 列**（≤1100px → 2 列，≤700px → 1 列），
      行距 / 上下间距与整页表单一致（`--form-row-gap`）
   · 多行文本 / 下拉多选**整行跨列** —— 与弹窗那套同口径（用户 2026-09-18 14:45 的既定要求：
     「默认多行文本字段独占 1 行、下拉多选字段独占 1 行」），否则「备注」会被压成 1/3 宽
   必须放**文件末尾**（前面有覆盖块）；用 `body:not(.embed-body)` 与弹窗那套（2 列）完全隔开
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--form-row-gap) 18px; align-items: start;
}
/* 默认一个字段占 4/12 = **一行 3 个**（与改造前完全一致，别的卡片零变化）*/
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row { grid-column: span 4; margin-top: 0; }
/* 「整行」默认名单（多行文本 / 下拉多选 / 带 .gp-full 的）—— 必须排在**显式跨度规则之前**，
   否则同特异性下后写的 span 会被它盖掉；反过来，[data-span] 的规则放后面就能压过它 */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.gp-full,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-ftype="textarea"],
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-ftype="multi_select"] { grid-column: 1 / -1; }
/* 字段级跨度（用户 2026-09-20 15:00 / 15:0x 两次指定生产计划卡片的排布）——
   模板按 `f.span` 输出 `data-span`（entity/form.ejs），跨度表在 src/productionStage.js 的 PLAN_SPAN
   （按标签认列）⇒ 生产计划卡整张卡都是 span 3 = **一行 4 个**：
     第 1 行 接收计划日期 / 计划结束日期 / 计划总天数 / 备注
     第 2 行 实际生产完成日期 / 实际总天数 / 生产状态 / 超期·剩余天数 */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span="3"] { grid-column: span 3; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span="4"] { grid-column: span 4; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span="6"] { grid-column: span 6; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span="12"] { grid-column: 1 / -1; }
/* 「备注」默认**单行**显示（用户 2026-09-20 15:0x）——
   它 type=textarea，全局 `min-height: calc(--sel-h-md * 2)` 会给两行高 ⇒ 高度按单行输入框来，
   仍保留 `resize: vertical`（想拉高自己拖）*/
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span][data-ftype="textarea"] textarea {
  height: var(--inp-h-md); min-height: var(--inp-h-md); padding-top: 5px; padding-bottom: 5px;
}
/* 窄屏必须把**显式跨度一起取消**（`[data-span]` 与 `[data-span="3"]` 同特异性、靠后者生效）*/
@media (max-width: 1100px) {
  body:not(.embed-body) .edit-form .card > .modal-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  body:not(.embed-body) .edit-form .card > .modal-grid > .form-row,
  body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span] { grid-column: auto; }
}
@media (max-width: 700px) {
  body:not(.embed-body) .edit-form .card > .modal-grid { grid-template-columns: 1fr; }
  body:not(.embed-body) .edit-form .card > .modal-grid > .form-row,
  body:not(.embed-body) .edit-form .card > .modal-grid > .form-row[data-span] { grid-column: auto; }
}

/* ============================================================================
   生产「生产记录」明细：已提交的记录行 = 只读（用户 2026-09-20 14:38）
   ----------------------------------------------------------------------------
   禁用手段只能用 **readOnly / pointer-events**（见 _items.ejs 的 applyProdLocks）——
      表单控件一旦设成 disabled 就**不随表单提交** ⇒ 保存会把这一行的值清空
      （隐藏域 it_* / it_submitted 更不能碰）
   这里只补「看得出被锁住」的视觉：下拉染灰 + 去掉下拉箭头，输入框染灰
   ============================================================================ */
.pi-table select.pi-locked-ctl { background: var(--chip); color: var(--muted); }
.pi-table select.pi-locked-ctl { appearance: none; -webkit-appearance: none; }
.pi-table input.is-readonly, .pi-table textarea.is-readonly { background: var(--chip); color: var(--muted); }

/* ============================================================================
   只读的「日期 / 时间」字段（f.readonly，如生产「生产计划」在非未排期时）——
   日期控件的可编辑入口是那个 **.dp-trigger 按钮**（显示框本来就是 readonly），
   所以只读时必须把触发器与面板一起收掉，否则看着像能点
   （判据：entity/form.ejs 给只读日期字段加了 .dp-field.is-ro ）
   ============================================================================ */
.dp-field.is-ro .dp-trigger,
.dp-field.is-ro .tp-trigger,
.dp-field.is-ro .dp-panel,
.dp-field.is-ro .tp-panel { display: none !important; }
.dp-field.is-ro .dp-display,
.dp-field.is-ro .tp-display { background: var(--chip); color: var(--muted); cursor: default; pointer-events: none; }

/* ============================================================================
   生产计划卡片：**标准详情页卡片布局**（用户 2026-09-20 15:07）
   「列名称列内容左右布局，非编辑状态无输入框背景色块」
   ----------------------------------------------------------------------------
   · 左右布局 = 模板给的 `.form-row.is-h`（label 居左、控件居右，样式本就有）
   · 「非编辑状态」= 行上有 `.is-ro`（entity/form.ejs 按 f.readonly 打）
     ⇒ 把控件背景 / 边框 / 内衬一起去掉，看着就是**一段普通文字**，不再是输入框色块
   只作用于「生产计划卡片」（带 data-span 的行），别的一律不动
   日期控件的显示框（.dp-display）本来就是 readonly ⇒ **不能只看 readonly 这个属性**，
      必须靠行上的 .is-ro 判断，否则可编辑的日期也会被抹掉底色
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] { align-items: center; }
/* 名称列：**固定宽度 + 右对齐 + 自动补全角冒号**（用户 2026-09-20 15:14：
   「间隔不对，你的：也没有出现」）——
   固定宽度才能让 8 个字段的「值」左边界对齐（不固定时标签长短不一 ⇒ 看着就是"间隔不对"）×；
   冒号用 CSS 补，不改字段名本身（字段设置里仍是干净的名字）*/
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > label {
  /* **左对齐**（用户 2026-09-20 15:28：「你看不出来这两个间隙不一致吗」）——
     右对齐时短名称（「计划天数：」）会被推到列右端 ⇒ 每个名称距卡片左边的距离都不一样
     左对齐 + 固定列宽 ⇒ **每个名称都从卡片内衬 14px 处起**、值列照样对齐 */
  flex: 0 0 114px; text-align: left; white-space: nowrap; color: var(--dt-label);
}
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > label::after { content: '：'; }
/* 统一「名称→值」间隙（用户 2026-09-20 15:28：两张卡的间隙看着不一致）——
   原来靠 .form-row.is-h 的 flex gap，而行的 gap 受各控件类型影响 ⇒ 8 个字段间隙不完全一样
   现在：label 定宽 + 行 gap 归零 + 控件统一 margin-left，**与控件类型无关** */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] { gap: 0; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > label { flex: 0 0 114px; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > *:not(label) { margin-left: 10px; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > input,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > select,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .csel,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field { flex: 1 1 auto; min-width: 0; }
/* 非编辑态：没有输入框底色 / 边框（纯文字观感） */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] > input,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] > textarea,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] > select,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] .dp-display,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] .tp-display,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] .csel-trigger {
  background: transparent !important; border-color: transparent !important;
  box-shadow: none !important; padding-left: 0 !important; padding-right: 0 !important;
}
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-ro[data-span] select { appearance: none; -webkit-appearance: none; }

/* ============================================================================
   生产：**左右两张并排卡**（用户 2026-09-20 15:21）——
   左「生产计划」（接收计划日期 / 结束日期 / 计划天数 / 备注）
   右「生产结果」（实际生产完成日期 / 生产状态 / 实际总天数 / 超期·剩余天数）
   ----------------------------------------------------------------------------
   · 模板给这两张卡加了 `.prod-half`（entity/form.ejs，名单来自 productionStage.HALF_GROUPS）
   · 用 :has() 只在「有半宽卡」的表单上把 .edit-form 变成 2 列网格；**其余卡片一律跨两列**
     （明细卡 / 单据动态卡等绝不能被切成半宽 ）
   · 每张卡内部仍是 12 列网格，字段 span 6 ⇒ **一行 2 个**（PLAN_SPAN）
   ============================================================================ */
body:not(.embed-body) .edit-form:has(> .card.prod-half) {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px; align-items: start;
}
body:not(.embed-body) .edit-form:has(> .card.prod-half) > .card.prod-half { grid-column: span 1; }
body:not(.embed-body) .edit-form:has(> .card.prod-half) > *:not(.card.prod-half) { grid-column: 1 / -1; }
@media (max-width: 1100px) {
  body:not(.embed-body) .edit-form:has(> .card.prod-half) { grid-template-columns: 1fr; }
  body:not(.embed-body) .edit-form:has(> .card.prod-half) > .card.prod-half { grid-column: 1 / -1; }
}

/* ============================================================================
   让「日期控件」的可见输入框与普通输入框**一样宽**（用户 2026-09-20 15:42：
   「这四个字段的输入宽度还是不一致啊」）
   ----------------------------------------------------------------------------
   原因：日期控件是 `.dp-field`（flex）= 「显示框 + 选择器按钮」并排 ⇒
        显示框只拿到 (单元格宽 − 按钮宽)，而普通输入框是整个单元格宽 ⇒ 两者差 ~32px
   修法：`.dp-field` 改成相对定位、选择器按钮**绝对定位浮在右侧**（图标叠在框内）⇒
        显示框吃满整格 = 与普通输入框**完全同宽**
   作用域：只限「生产计划 / 生产结果」这两张半宽卡（带 data-span 的水平行）不动其它页面
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field { position: relative; display: block; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field > .dp-display { width: 100%; padding-right: 26px; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field > .dp-trigger {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%); margin: 0;
}
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field > .tp-field { position: relative; }

/* ============================================================================
   日期控件被一条老的 max-width 卡住 ⇒ 宽屏下比普通输入框窄（用户 2026-09-20 16:03：
   「能看出来长短不一致吗？刷新很多次了」）
   ----------------------------------------------------------------------------
   实测（1920 宽窗口）：日期显示框 220px vs 普通输入框 267px （差 47 像素）
   实测（1440 宽窗口）：两者都是 147 —— 所以**窄屏量不出来**，必须按用户的实际窗口宽度量
   真凶：日期控件身上有一条 max-width: 220px 的老限制（来自订单详情那套 KV 样式），
        格子宽度不超过 220 时看不出问题，宽屏一超过 220 就被截住
   修法：这两张卡里的日期控件解除 max-width，与普通输入框一样吃满整格
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field .dp-display,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field .tp-field,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field .tp-display {
  max-width: none;
}

/* ============================================================================
   名称**左对齐**、冒号**右对齐**（用户 2026-09-20 16:06：
   「：是右对齐，字段名称是左对齐。生产计划卡片和生产结果卡片都需要这样」）
   ----------------------------------------------------------------------------
   · 名称靠左：贴在标签列的左边界（= 卡片内衬 14px）
   · 冒号靠右：贴在标签列的右边界 ⇒ **同一列的冒号全部对齐**，值也从同一条竖线开始
   实现：label 变成 flex 容器 + justify-content: space-between
        （文字节点当第一项贴左、::after 的「：」当第二项贴右）
   只作用于这两张半宽卡（data-span 的水平行），其它页面不动
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > label {
  display: flex; justify-content: space-between; align-items: baseline;
}

/* ============================================================================
   名称与内容**上下居中**（用户 2026-09-20 16:08：「字段名称和字段内容都是上下居中」）
   ----------------------------------------------------------------------------
   实测（修前）：名称文字中心比输入框中心 **高 6px**
   原因：行用 align-items:center 时，label 盒子只有文字那么高，而 label 内部又用 baseline 对齐 ⇒
        文字在 label 盒内的位置 ≠ 输入框的中心
   修法：① 行改成 align-items: **stretch**（label 盒子撑满行高 = 与输入框同高）
        ② label 内部 align-items: **center**（文字在这个等高盒子里垂直居中）
   ⇒ 名称文字中心 = 输入框中心 （输入框本身高度不变）
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] { align-items: stretch; }
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > label {
  display: flex; justify-content: space-between; align-items: center;
}

/* ============================================================================
   字段名称 / 字段内容 用**标准详情页的字号与颜色**（用户 2026-09-20 16:10：
   「字段内容是 14、#130928 的颜色，你也不按照这个来」）
   ----------------------------------------------------------------------------
   口径 = 订单详情那套 KV（style.css 里 --dt-fs / --dt-label / --dt-text）：
     · 名称：--dt-fs + --dt-label（列名色）
     · 内容：--dt-fs + --dt-text（#130928）
   之前这两张卡的名称是全局 `.form-row label` 的 13px + --muted、控件是默认 14px + --text
      ⇒ 与标准不一致
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > label {
  display: flex; justify-content: space-between; align-items: center;
  font-size: var(--dt-fs); color: var(--dt-label);
}
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > input,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > textarea,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > select,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field > .dp-display,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field > .tp-display,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] > .dp-field > .dt-value {
  font-size: var(--dt-fs); color: var(--dt-text);
}

/* ============================================================================
   生产记录：**已提交（锁定）的行**统一成「纯白 + 纯文字 + 不可交互」
   （用户 2026-09-20 16:11：「你锁定了，为什么生产记录日期还可以编辑？锁定了，
     当前生产进度和记录内容的内容项背景色为什么不是白色呢」）
   ----------------------------------------------------------------------------
   · 三个格子一律纯白底（原来锁定项被染成灰色 var(--chip)，用户要白色）
   · 内容看上去就是文字：控件去掉背景与边框（日期显示框 / 下拉 / 文本框一视同仁）
   · 不可交互：日期显示框 pointer-events:none（光藏掉日历按钮不够，点显示框也会开日历）
   ============================================================================ */
.pi-table tbody tr.pi-prod-locked > td { background: var(--card); }
.pi-table tbody tr.pi-prod-locked input,
.pi-table tbody tr.pi-prod-locked textarea,
.pi-table tbody tr.pi-prod-locked select,
.pi-table tbody tr.pi-prod-locked .dp-display,
.pi-table tbody tr.pi-prod-locked .tp-display,
.pi-table tbody tr.pi-prod-locked .csel-trigger {
  background: transparent !important; border-color: transparent !important; box-shadow: none !important;
  color: var(--dt-text); padding-left: 0 !important;
}
.pi-table tbody tr.pi-prod-locked select { appearance: none; -webkit-appearance: none; text-align: center; text-align-last: center; }

/* ============================================================================
   修正「只读控件的观感」（用户 2026-09-20 16:17：「备注的内容是 #130928，其他的都不是」）
   ----------------------------------------------------------------------------
   真凶：`.form-row input[readonly] { opacity: .72 }` **只命中 <input>、不命中 <textarea>** ⇒
        备注（textarea）是正常的 #130928，其它只读输入框被打到 72% 透明 ⇒ 看上去更浅
   （computed color 全都是 #130928，所以只量 color 查不出来 —— 必须一起量 opacity ）
   修法：这两张卡的只读控件 opacity 恢复 1
   ============================================================================ */
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] input[readonly],
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] textarea[readonly],
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] .dp-display,
body:not(.embed-body) .edit-form .card > .modal-grid > .form-row.is-h[data-span] select[disabled] {
  opacity: 1;
}

/* ============================================================================
   生产记录明细表：让控件里的文字在**格子里**真正左右居中
   （用户 2026-09-20 16:20：「这个字段内容是左右居中的吗？」）
   ----------------------------------------------------------------------------
   实测：下拉 `text-align:center` + `text-align-last:center` 都对，但它的
        `padding: 0 28px`（右侧 28px 留给原生箭头）⇒ 内容盒**左窄右宽**，
        文字按内容盒居中 ⇒ 在格子里实际**偏左约 14px**
   同理文本框 `padding-left:0 / padding-right:8` ⇒ 偏左 4px
   修法：把左右内衬补成对称 ⇒ 内容盒居中 = 文字在格子里居中
   ============================================================================ */
.pi-table tbody td select { padding-left: 28px !important; }
.pi-table tbody td input:not([type=hidden]) { padding-left: 8px !important; }
.pi-table tbody td textarea { padding-left: 8px !important; }

/* ============================================================================
   出货明细「物流进度」跟踪（用户 2026-09-20）：每行下方一条 已出货→运送中→已签收
   ----------------------------------------------------------------------------
   · 仅出货模块渲染（_items.ejs 的 IS_SHIP 控制）；子行与数据行成对出现
   · 手动开关：开启后才可点选当前进度；状态存 `it_ship_progress` 随表单提交
   · 颜色全部走主题变量（亮/暗两边自适应）
   ============================================================================ */
.ship-prog-row td { padding: 0 !important; border-bottom: 1px solid var(--line); background: var(--card); }
.ship-prog {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 8px 14px 8px 35px;            /* 左缩进与「序号 + 操作」两列对齐 */
  background: var(--card-2);
}
.ship-prog-toggle { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.ship-prog-lab { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; }
.ship-prog:not(.is-on) .ship-prog-lab::after { content: ' · 未开启'; font-weight: 400; color: var(--muted); }
.ship-prog.is-on .ship-prog-lab::after { content: ' · 跟踪中'; font-weight: 400; color: var(--primary); }
/* 开关（toggle） */
.ship-sw { position: relative; display: inline-block; width: 36px; height: 20px; flex: none; }
.ship-prog-sw { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; z-index: 2; }
.ship-sw-knob {
  position: absolute; inset: 0; border-radius: 999px;
  background: var(--line); transition: background .18s ease;
}
.ship-sw-knob::before {
  content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: 50%; background: #fff; transition: transform .18s ease; box-shadow: 0 1px 2px rgba(15,23,42,.25);
}
.ship-prog-sw:checked + .ship-sw-knob { background: var(--primary); }
.ship-prog-sw:checked + .ship-sw-knob::before { transform: translateX(16px); }
.ship-prog-sw:focus-visible + .ship-sw-knob { outline: 2px solid var(--primary); outline-offset: 2px; }
/* 阶段步进条 */
.ship-steps { display: inline-flex; align-items: center; gap: 0; }
.ship-step {
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  font-size: 13px; line-height: 1; padding: 5px 14px; border-radius: 999px; cursor: pointer;
  transition: background .15s, color .15s, border-color .15s; white-space: nowrap;
}
.ship-prog:not(.is-on) .ship-step { cursor: not-allowed; opacity: .55; }
.ship-step.done { color: var(--primary); border-color: var(--primary); }
.ship-step.current { color: #fff; background: var(--primary); border-color: var(--primary); font-weight: 600; }
.ship-step.pending { color: var(--muted); }
.ship-line { width: 30px; height: 2px; background: var(--line); flex: none; }
.ship-line.done { background: var(--primary); }
/* 暗色主题：关闭态的开关滑块用浅色，保持可见 */
[data-theme="dark"] .ship-sw-knob::before { background: #E8ECFF; }

/* ============================================================
   列表「勾选列 + 导入 / 导出」（用户 2026-09-21 14:2x）
   —— 「列表页系统编号前面，每一行都需要加上 1 个选择框。支持全选，然后导出的时候就可以按照这个导出」
   —— 「导入导出按钮放到列表页新增单据旁边」「导出的时候，是有选择导出列表，还是导出含明细」
   ============================================================ */
/* 勾选列：窄、居中、不参与换行；勾中的行给个浅底（一眼看出导出范围）*/
.data-table .ck-col { width: 36px; padding: 0 8px; text-align: center; }
/* 🆕 序号列（用户 2026-09-26 货品管理）：紧跟在勾选列右边、系统编号左边 ——
   宽度取 52（与「批量录入」页的序号列同一档 ✓），数字居中、灰字 ✓ */
.data-table .no-col { width: 52px; padding: 0 8px; text-align: center; }
.data-table .ck-col input[type=checkbox] { width: 14px; height: 14px; margin: 0; vertical-align: middle; cursor: pointer; accent-color: var(--primary); }
.data-table tbody tr.is-sel td { background: var(--tint-primary); }
/* 冻结列：勾选列被一起钉住（JS 从第 0 列算前缀），底色要**不透明**否则横滚会透字 */
.data-table .ck-col.is-fz { background: var(--card); }
.data-table thead .ck-col.is-fz { background: var(--card-2); }
.data-table tfoot .ck-col.is-fz { background: var(--bg-list); }
.data-table tbody tr.is-sel .ck-col.is-fz { background: var(--tint-primary); }

/* 导入 / 导出 按钮组（和新按钮同一行、间距 8）*/
.io-wrap { position: relative; display: inline-flex; align-items: center; gap: 8px; }
.io-drop { position: relative; display: inline-block; }
.io-menu {
  display: none; position: absolute; right: 0; top: calc(100% + 6px); z-index: 70;
  width: max-content; min-width: 220px; padding: 10px 12px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-hover); text-align: left;
}
.io-menu.open { display: block; }
.io-grp { display: flex; flex-direction: column; gap: 6px; padding: 2px 0 8px; }
.io-grp + .io-grp { border-top: 1px solid var(--line); padding-top: 8px; }
.io-grp-t { font-size: 12px; color: var(--muted); }
/* 「按当前筛选，跨所有页」这类补充说明 */
.io-grp label .io-n { font-size: 12px; color: var(--muted); }
/* 「已选 N 条 ｜ 选择全部 M 条」勾选条（用户 2026-09-26 货品管理）——
   紧贴表格上方；只在勾选了行（或点了「选择全部」）时出现，不占地方 ✓ */
.sel-bar {
  display: flex; align-items: center; gap: 10px;
  height: 34px; padding: 0 10px; margin-bottom: 6px;
  background: var(--tint-primary); border: 1px solid var(--line);
  font-size: var(--fs-13); color: var(--text);
}
.sel-bar[hidden] { display: none !important; }
.sel-bar svg { flex: none; color: var(--primary); }
.sel-bar .sel-txt b { font-weight: 600; }
.sel-bar .link-btn { padding: 0 2px; }
.sel-bar.is-all { background: var(--tint-primary); border-color: var(--primary); }
.sel-all-txt { color: var(--primary); }
.sel-all-txt b { font-weight: 600; }
.io-grp label { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text); cursor: pointer; white-space: nowrap; }
.io-grp label input[type=radio] { margin: 0; accent-color: var(--primary); }
.io-grp label input[type=radio]:disabled + span, .io-grp label input[type=radio]:disabled { cursor: not-allowed; opacity: .5; }
.io-go { width: 100%; justify-content: center; }

/* 导入弹窗：规则说明 + 模板列 + 文件选择 + 结果表 */
.io-rule { background: var(--bg-list); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px 14px; margin-bottom: 14px; }
.io-rule-t { font-size: 13px; font-weight: var(--fw-bold); color: var(--text); margin-bottom: 6px; }
.io-rule ul { margin: 0; padding-left: 18px; }
.io-rule li { font-size: 13px; line-height: 1.9; color: var(--text); }
.io-cols { display: flex; flex-wrap: wrap; gap: 6px; }
.io-col { font-size: 12px; padding: 2px 8px; border: 1px solid var(--line); background: var(--card); color: var(--text); }
.io-col.is-req { border-color: var(--primary); color: var(--primary); }
.io-pick { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.io-file { font-size: 13px; color: var(--muted); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.io-result { font-size: 13px; }
.io-sum { margin-bottom: 8px; color: var(--text); }
.io-ok-t { color: #15803D; font-weight: var(--fw-bold); }
.io-bad-t { color: #B91C1C; font-weight: var(--fw-bold); }
.io-fail-all { color: #B91C1C; font-size: 13px; }
.io-tb-wrap { max-height: 320px; overflow: auto; border: 1px solid var(--line); }
.io-tb { width: 100%; }
.io-tb th, .io-tb td { height: 30px; font-size: 13px; }
.io-tb tr.io-ok td:nth-child(2) { color: #15803D; }
.io-tb tr.io-bad td:nth-child(2) { color: #B91C1C; font-weight: var(--fw-bold); }

/* ============================================================
   列表页顶部「Tab 切换条」（用户 2026-09-21 14:4x）
   —— 「列表页的顶部需要加 1 个 36 高度 #fff 的固定条，这个固定条是用来切换 tab 页面的
        （比如订单中心的，就是全部，未完成，已完成）。位置和你详情页里面那个位置一致」
   · 位置 / 尺寸 / 贴顶冻结 = **与详情页首行色带一模一样**（负 margin 抵消 app-main 的 22 padding、
     min-height 36、sticky top 60px 让开固定顶栏）
   · 暗色主题跟详情页色带同一处理：浅紫在深底上刺眼 ⇒ 换回卡片底
   ============================================================ */
.list-topbar {
  /* 左侧**多盖 1px**（用户 2026-09-21 14:5x：「这个固定条…距离左侧导航之间有个 1 像素的宽，
     我不需要这个 1 像素的间隙」）——
     根因：浏览器缩放 / 小数像素（Windows 125% 缩放、侧栏 200px 落在小数边界）时，
     色带左边界与侧栏右边界之间会露出 **1 个设备像素**的页面底色（亚像素接缝）
     ⇒ margin-left 用 -17、padding-left 用 17：**内容位置一个像素都不动**，
       背景向左多铺 1px 把缝盖住 （右边界不动，不会溢出/出横向滚动条 ） */
  /* 底边距 **0**（用户 2026-09-21 15:3x：「全员页面筛选查找行卡片，距离上方卡片间隔 12」）——
     页面主体 `.info-list` 本身是 `display:flex; flex-direction:column; gap:12px`，
     这条原来又挂 `margin-bottom:16px` ⇒ 筛选行与它上方那条的实际间距 = 16+12 = **28**
     去掉这 16 之后 = 12，与页面里其它块之间的间距**完全一致** */
  margin: -16px -16px 0 -17px;
  padding: 0 17px;
  padding-right: 16px;
  background: #fff;
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  min-height: 36px;
  padding-top: 2px;
  padding-bottom: 2px;
  display: flex;
  align-items: center;
  position: sticky;
  top: 60px;
  z-index: 10;
}
[data-theme=dark] .list-topbar { background: var(--card); }
.list-topbar .lt-tabs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; position: relative; }
/* Tab = 直角（列表页整体走直角规范）：未选中 = 透明底深字 */
.list-topbar .lt-tab {
  display: inline-block; padding: 3px 12px; font-size: var(--fs-14); line-height: 1.5;
  color: #2E2A5C; background: transparent; border: 1px solid transparent; border-radius: 0;
  white-space: nowrap; text-decoration: none;
}
.list-topbar .lt-tab:hover { background: rgba(0,0,0,.05); color: #1E1B4B; }
/* 选中 Tab：**文字变主色** + 文字下方一条**主色横线**（用户 2026-09-22 参照截图）——
   横线 = 一个**独立元素** `.lt-ink`（放在 `.lt-tabs` 里绝对定位、宽度 = 标签文字宽度），
   切 Tab 时靠 left/width 的 CSS 过渡**左右滑动**（定位脚本在 partials/listTopbar.ejs 里）
   不要再用「每个 tab 各挂一条 ::after」的写法 —— 那是**各自出现/消失**，滑不起来 */
.list-topbar .lt-tab.is-on {
  background: transparent; color: var(--primary); font-weight: var(--fw-bold);
  border-bottom-color: transparent;
}
.list-topbar .lt-ink {
  position: absolute; left: 0; bottom: 0; width: 0; height: 3px;
  background: var(--primary); border-radius: 0; pointer-events: none;
  transition: left .2s ease, width .2s ease;
}
[data-theme=dark] .list-topbar .lt-tab { color: var(--text); }
[data-theme=dark] .list-topbar .lt-tab:hover { background: var(--nav-hover-bg); color: var(--nav-hover-text); }
[data-theme=dark] .list-topbar .lt-tab.is-on { background: transparent; color: var(--primary); }
.list-topbar .lt-sp { flex: 1 1 auto; }
.list-topbar .lt-hint { font-size: 12px; color: #4A4478; }
[data-theme=dark] .list-topbar .lt-hint { color: var(--muted); }

/* ============================================================================
   采购处理详情 · 明细区**左右布局**（用户 2026-09-21 15:2x）
   「详情页分为左右布局，左侧是一个窄条，按顺序放几个按钮：采购明细 / 质检明细 / 入库明细 / 生产明细。
     不要动上方采购编号那个固定的条」
   · 左侧窄条 = 明细导航（竖排按钮，点哪个右侧就显示哪张明细卡）
   · 上方色带（#ptBar「采购编号」那条）/ 采购单进度 / 订单卡片 / 字段卡 / 单据动态**一律通栏不动**
   · .is-flat（新增 / 编辑**弹窗**）= 退回原来的「明细卡依次堆叠、不出窄条」形态
   ============================================================================ */
.dt-split { display: flex; align-items: flex-start; gap: 16px; margin: 12px 0; }
/* 中间区域 = **横向导航（上）+ 明细面板（下）**（用户 2026-09-22 21:0x：「把库存检查，采购下单，
   合格入库，生产加工，到货记录变成横向的按钮，放到中间卡片的上方」）——
   原来左侧 104px 竖排窄条 ⇒ 现在导航**横排**、整块放在中间卡片**上方**；右侧「单据动态」不动 */
.dt-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.dt-main > .dt-rail { flex: none; width: 100%; flex-direction: row; flex-wrap: wrap; gap: 8px; }
.dt-main > .dt-rail > .dt-rail-btn,
.dt-main > .dt-rail > .dt-rail-sub { width: auto; flex: 0 0 auto; padding: 6px 16px; min-height: 32px; }
/* 紧跟在「关联销售订单」卡片后面的明细区，间距 = **16**（用户 2026-09-22：「这个间距现在不是 16，调整到 16」）——
   `.po-card` 自身已带 `margin-bottom: 16`，而 `.dt-split` 又自带 `margin-top: 12` ⇒ 在 flex 父容器里**不折叠**、叠成 **28**
   ⇒ 把「订单卡之后」的 dt-split 上边距归零（其它位置的 dt-split 行为不变）
   必须用 `~`（后续兄弟）**不能**用 `+`（紧邻）—— 卡片和 dt-split 之间还夹着一个高度为 0 的
      隐藏容器 `DIV.modal-grid`（选单弹窗）⇒ `+` 永远匹配不上，改了等于没改 （2026-09-22 实测踩过）*/
.po-card ~ .dt-split { margin-top: 0; }
.dt-rail { flex: 0 0 104px; width: 104px; display: flex; flex-direction: column; gap: 8px; }
.dt-rail-btn {
  width: 100%; min-height: 34px; padding: 6px 8px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; text-align: center;
  font-family: inherit; font-size: var(--fs-13); line-height: 1.3;
  background: var(--card); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.dt-rail-btn:hover { border-color: var(--primary); color: var(--primary); }
.dt-rail-btn.is-on { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; }
[data-theme="dark"] .dt-rail-btn { background: var(--card-2); color: var(--text); }
.dt-panels { flex: 1 1 auto; min-width: 0; }
.dt-panel { display: none; }
.dt-panel.is-on { display: block; }
/* 弹窗 / embed：不出窄条，明细卡照老样子依次堆叠 */
.dt-split.is-flat { display: block; margin: 0; }
.dt-split.is-flat .dt-rail { display: none; }
.dt-split.is-flat .dt-panel { display: block; }

/* 单据动态挂到**明细区右侧**（用户 2026-09-22 20:3x：「调整生产计划所在的大卡片区域，
   把单据动态放到这个卡片的右侧」）—— `.dt-split` 变**三栏**：
   明细导航(104) + 明细面板(弹性) + 单据动态(360)；窄屏（≤1240）自动换行成上下 */
.dt-split > .df-card { flex: 0 0 360px; width: 360px; max-width: 100%; margin: 0; }
@media (max-width: 1240px) {
  .dt-split { flex-wrap: wrap; }
  .dt-split > .df-card { flex: 1 1 100%; width: 100%; }
}

/* 采购处理：采购单进度条改 **8 步**（用户 2026-09-21 15:5x）——
   步数变多 ⇒ 收紧字号 / 圆点 / 连接线，保证一行放得下（实测 1440 视口下不溢出）
   只作用于**采购处理**那份（`.is-compact`）：采购管理（已冻结）那条 5 步的维持原样 */
/* 轨道式样式（用户 2026-09-22，参照 Claw/output/erp-order-workbench-prototype.html 的
   .sbar/.strack/.snodes/.sslots；只改样式，节点/逻辑不动）——
   色带在上：每步一段等宽相连（未走浅灰 #E4E3EF / 已走浅紫 #A29DED / 当前主色 / 作废斜纹）；
   白心圆点压带（圆心=段中心=下方文字轴线；已走=实色深紫 #7F77DD，当前=白心+主色环且大 2px）；
   下方两行刻度 = 步骤名 / 子状态（13px，层级靠颜色不靠字号；当前步名主色、明细主色深档，
   未走统一更浅灰 .off）。取色全部来自参考原型实测值 */
#ptSteps.is-compact .step { display: block; position: relative; flex: 1; min-width: 0; height: 52px; padding: 0 6px; gap: 0; }
/* 色带：li::before 横跨整段（含 6px 内边距区），首段左圆角 / 末段右圆角（参考 .strack radius 4px）*/
#ptSteps.is-compact .step::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 8px; margin: 0; min-width: 0; background: #E4E3EF; }
#ptSteps.is-compact .step:first-child::before { border-radius: 4px 0 0 4px; }
#ptSteps.is-compact .step:last-child::before { border-radius: 0 4px 4px 0; }
#ptSteps.is-compact .step.is-done::before { background: #A29DED; }
#ptSteps.is-compact .step.is-active::before { background: var(--primary, #4F46E5); }
/* 「异常」：用户没给单独配色，也不要有杂色 ⇒ 跟「进行中」同款（它本质是"在走、没走完"）✓ */
#ptSteps.is-compact .step.is-bad::before { background: var(--primary, #4F46E5); }
#ptSteps.is-compact .step.is-void::before { background: repeating-linear-gradient(135deg, #ECEAF7 0 4px, #D6D4E6 4px 8px); }
/* 「已跳过」（用户 2026-09-22，取参考原型 .sseg.skip 的 135° 斜纹）——
   库存足够、无需采购 ⇒ 采购下单 / 到货质检 / 合格入库 三格：斜纹色带 + 空心点 + 浅灰字
   （文字色沿用下面 :not(.is-done):not(.is-active):not(.is-void) 那两条给的浅灰 ）*/
#ptSteps.is-compact .step.is-skip::before { background: repeating-linear-gradient(135deg, #ECEAF7 0 4px, #D6D4E6 4px 8px); }
#ptSteps.is-compact .step.is-skip .step-dot { background: var(--card, #fff); border-color: #C8C7D8; }
/* 节点圆点：压在色带上（12px 白心灰环，序号/对勾隐藏 —— 参考圆点是空心的）*/
#ptSteps.is-compact .step .step-dot { position: absolute; left: 50%; top: 4px; transform: translate(-50%, -50%); z-index: 1; width: 12px; height: 12px; border: 2px solid #C8C7D8; background: var(--card, #fff); font-size: 0; color: transparent; }
#ptSteps.is-compact .step .step-dot svg { display: none; }
/* 已走 = 实色点（比色带深一档才从带里分离）；当前 = 白心 + 主色环且大 2px
   —— **2026-09-27 定稿：这两条回到第一版**（用户：「还不如我第一版的颜色」）✓ */
#ptSteps.is-compact .step.is-done .step-dot { background: #7F77DD; border-color: #7F77DD; }
#ptSteps.is-compact .step.is-active .step-dot { width: 14px; height: 14px; background: var(--card, #fff); border-color: var(--primary, #4F46E5); box-shadow: none; }
#ptSteps.is-compact .step.is-bad .step-dot { width: 14px; height: 14px; background: var(--card, #fff); border-color: var(--primary, #4F46E5); box-shadow: none; }
#ptSteps.is-compact .step.is-void .step-dot { background: var(--card, #fff); border-color: #EF4444; }
/* 下方两行刻度：步骤名（未走更浅灰，已走常规灰，当前步主色+半粗，作废红）*/
#ptSteps.is-compact .step .step-label { display: block; text-align: center; padding-top: 14px; font-size: var(--fs-13, 13px); line-height: 16px; color: var(--t2, #51565D); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#ptSteps.is-compact .step.is-active .step-label { color: #4B87D9; font-weight: 500; }
#ptSteps.is-compact .step.is-bad .step-label { color: #4B87D9; font-weight: 500; }
#ptSteps.is-compact .step.is-void .step-label { color: #EF4444; font-weight: 700; }
#ptSteps.is-compact .step:not(.is-done):not(.is-active):not(.is-void):not(.is-bad) .step-label { color: #A8ADB5; }
/* 子状态（明细）：第二行，层级靠颜色 —— 已走 t3 灰 / 当前主色深档 / 未走更浅灰 */
#ptSteps.is-compact .step .step-sub { display: block; margin-top: 1px; padding: 0; border: 0; background: none; font-size: var(--fs-13, 13px); line-height: 16px; font-weight: 400; color: var(--t3, #8A9099); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#ptSteps.is-compact .step.is-active .step-sub { color: #4B87D9; }
#ptSteps.is-compact .step.is-bad .step-sub { color: #4B87D9; }
#ptSteps.is-compact .step:not(.is-done):not(.is-active):not(.is-void):not(.is-bad) .step-sub { color: #B3B8BF; }

/* 采购处理：明细表**不设「最多 5 行」限高**（用户 2026-09-21 16:0x）——
   容器带 data-dt-maxrows="0" ⇒ 展开态按内容自然高（这里先把 CSS 兜底的 223px 上限撤掉，
   免得 JS 跑之前闪一下「5 行高」）；真正的逻辑在 public/js/cardCollapse.js
   折叠态由 JS 写行内 max-height（1 行），优先级更高，不受这条影响 */
[data-dt-maxrows="0"] .pi-card .pi-wrap { max-height: none; }

/* 采购处理 · 库存检查面板（用户 2026-09-21 16:1x）：3 个选项 + 提交 —— 竖排单选，点「提交」AJAX 落库 */
.sc-wrap { padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; }
.sc-opt { display: flex; align-items: center; gap: 8px; font-size: var(--fs-14); color: var(--text); cursor: pointer; }
.sc-opt input { margin: 0; }
.sc-actions { display: flex; align-items: center; gap: 10px; }
/* 行首那个蓝标签与明细卡的 `.pi-tab` **逐项对齐**（用户 2026-09-21 16:1x：截图圈的就是这里，
   「你要和明细表中的按钮处理一致」）——
   明细卡里那个 `.pi-tab` 实测 computed `border-radius` = **0**（卡片内直角，见文件末尾直角白名单那一带的实际效果），
      不显式写就会是 `.pi-tab` 基类的 4px 4px 0 0 ⇒ 两边不一致 */
#stockCheck .pi-bar .pi-tab { border-radius: 0; }

/* 库存检查：每个选项后面跟一个备注输入框（用户 2026-09-21 16:2x）；提交后整块锁定（disabled 灰底） */
/* 三个选项**横向一行**（用户 2026-09-21 16:3x）：改前是「每项一行、各带一个备注框」 */
.sc-opts { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.sc-opt { flex: 0 0 auto; }
/* 备注只剩下方**一个**、整行宽（用户 2026-09-21 16:3x） */
.sc-note {
  /* 2 行高（用户 2026-09-21 16:5x）：min-height 68 = 两行文字 + 上下内距（与项目弹窗的多行文本同标准）*/
  width: 100%; height: auto; min-height: 68px; padding: 7px 10px; line-height: 1.5;
  border-radius: var(--radius-sm); resize: vertical;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  font-family: inherit; font-size: var(--fs-13); outline: none;
}
.sc-note:focus { border-color: var(--primary); box-shadow: var(--ring); }
.sc-note:disabled { background: var(--bg-list); color: var(--muted); cursor: not-allowed; }

/* 库存检查面板底部的元信息行（用户 2026-09-21 16:4x）：检查人 / 检查时间 / 更新时间 */
.sc-meta { display: flex; flex-wrap: wrap; gap: 16px; font-size: var(--fs-12); color: var(--muted); }
.sc-meta b { color: var(--text); font-weight: 600; }

/* 「出货记录」卡的 3 个字段（用户 2026-09-21 23:5x：「布局和样式要和库存检查的卡一致」）——
   外壳 / 按钮行 / 元信息 / 备注框全部**复用库存检查那套 `.sc-*`**，
   这里只补「出货日期 + 出货月份」这一行里每个字段自己的排布（标签在上、控件在下）*/
.sh-row { align-items: flex-end; gap: 24px; }
.sh-f { display: flex; flex-direction: column; gap: 6px; flex: 0 0 auto; }
.sh-lab { font-size: var(--fs-12); color: var(--muted); }
/* 出货月份：**自动算**的只读值 ⇒ 与其它只读字段同款（浅底、灰字、不过鼠标）*/
.sh-in {
  width: 170px; min-height: 34px; padding: 5px 9px;
  border: 1px solid var(--field-border); background: var(--field-bg); color: var(--text);
  border-radius: var(--radius-sm); font-family: inherit; font-size: var(--fs-14); outline: none;
}
.sh-in.is-auto { background: var(--bg-list); color: var(--muted); pointer-events: none; }
/* 出货日期用的是项目自己的日期控件 ⇒ 宽度与右边的「出货月份」对齐（同样 170px）*/
.sh-f .dp-field { width: 170px; max-width: 170px; }

/* 采购明细行「物流质检明细」子表（用户 2026-09-21 16:5x）——
   行首一个圆圈切换；展开时在该行下方插一行只读表（物流 → 到货 → 质检 → 处理 → 来源批次 / 备注）*/
.pi-exp-c { width: 28px; text-align: center; padding: 0 2px !important; }
.pi-exp-h { width: 28px; }
.pi-exp {
  width: 16px; height: 16px; padding: 0; margin: 0; vertical-align: middle;
  border: 1px solid var(--line-strong); border-radius: 50%; background: var(--field-bg); color: var(--muted);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.pi-exp svg { transition: transform .15s; }
.pi-exp.is-open svg { transform: rotate(90deg); }
.pi-exp:hover { border-color: var(--primary); color: var(--primary); }
.pi-no-t { vertical-align: middle; }
.pi-subrow > td { background: var(--bg-list); padding: 8px 10px !important; }
.pi-sub { overflow: auto; max-height: 280px; }
.pi-sub-table { width: 100%; border-collapse: collapse; background: var(--card); font-size: var(--fs-12); }
.pi-sub-table th, .pi-sub-table td { border: 1px solid var(--line); padding: 4px 8px; text-align: center; white-space: nowrap; }
.pi-sub-table thead th { background: var(--bg-list); color: var(--ink-2); font-weight: 600; }
.pi-sub-empty { font-size: var(--fs-12); color: var(--muted); }

/* 行内展开的「物流质检明细」= **一张独立卡片**（用户 2026-09-21 17:0x）——
   卡片头沿用明细卡那套（.pi-bar + 蓝底 .pi-tab + 条数），体里是只读批次表 */
.pi-subcard { margin: 0; }
/* 卡片体不能再套一层滚动容器（用户 2026-09-21 17:1x：「为什么会有 2 个滑动杆呢」）——
   内层台账的 .ar-wrap 自带横向滚动 ⇒ 这里 overflow 必须 visible、高度也不限，否则卡片里会出现**第二条**横滚 */
.pi-subcard > .pi-sub { padding: 0; overflow: visible; max-height: none; }

/* 到货质检合计表的**代理滚动容器**（用户 2026-09-21 19:3x：滑杆要和采购明细一致 —— 落在合计行**上方**）
   自己不显示滑杆，但能被程序设 scrollLeft ⇒ 成为合计表的滚动祖先 ⇒ 合计行跟着主表一起滚 */
.ar-total-clip { overflow: hidden; }

/* 台账「#」列要放得下**层级编号**（1-1 / 换1-1-1 / 退1-1）（用户 2026-09-21 20:1x）*/
.ar-table thead th:first-child { width: 78px; }
.ar-table .ar-no { white-space: nowrap; }

/* 台账**列宽拖拽把手**（用户 2026-09-21 19:3x，与采购明细 `.pi-resizer` 同款）——
   th 本身就是 sticky（定位元素）⇒ 可以直接当绝对定位的参照，**别再加 position:relative**（会顶掉 sticky ）*/
.ar-table thead th { position: sticky; }
.ar-resizer {
  position: absolute; top: 0; right: -3px; width: 7px; height: 100%;
  cursor: col-resize; z-index: 6; background: transparent;
}
.ar-resizer:hover { background: var(--primary-soft-2); }

/* **撤掉「只允许从抽屉录入」的只读闸门**（用户 2026-09-22 20:4x：
   「去掉采购明细默认弹窗编辑，修改为表格编辑。也就是采购明细不需要 iframe 了」）——
   ① 采购明细行控件、② 页面下方「到货质检」区 **恢复可直接编辑**；
   抽屉（.pi-two-side）已从模板里删除，相关 CSS（.pi-two-side / .pi-side-*）暂留、不再使用 */
/* 抽屉里的台账：可录 + 补上边框（它不在 .qc-cards 作用域里）*/
.pi-side-qc { margin-top: 14px; }
.pi-side-qc .ar-wrap { border-top: 1px solid var(--line); }

/* 采购下单面板：**左（明细 + 到货质检）｜右（常驻录入面板）**（用户 2026-09-21 19:3x）——
   右侧面板：选中哪一行就显示哪一行的字段输入 + 「保存本行」；不用弹窗、不用收起 */
.pi-two { display: block; }
.pi-two-main { min-width: 0; }
/* 右侧录入面板 = **抽屉式推出**（用户 2026-09-21 19:4x：「你把常驻改成推出吧。现在常驻态难看了」）——
   常驻占 320px 会把明细表压窄 ⇒ 改成 fixed 抽屉：默认整块滑出屏幕外，点「录入本行」才推出来 */
.pi-two-side {
  position: fixed; top: 0; right: 0; bottom: 0; width: 1200px; z-index: 4000;   /* 宽度 1200（用户 2026-09-21 20:5x 指定）*/
  background: var(--card); border-left: 1px solid var(--line);
  transform: translateX(102%); transition: transform .22s ease;
  display: flex; flex-direction: column; overflow: hidden;
}
.pi-two-side.is-on { transform: translateX(0); box-shadow: -6px 0 24px rgba(15, 23, 42, .14); }
.pi-two-side .pi-side-body { flex: 1 1 auto; }
/* 抽屉里的字段 **1 行 3 列**（用户 2026-09-21 20:5x：「采购明细布局，1行3列」）——
   grid 3 列；每格 = label + 控件（.pi-side-row 原本是竖排一整行，现在变成一格）*/
/* 字段区拆 3 段（用户 2026-09-21 21:1x）⇒ 外层回到块级，**每段内部**才是 1 行 3 列 */
.pi-side-body { display: block; }
.pi-side-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px 14px;
  align-items: center;
}
/* 段之间 + 最后那段到「到货质检」之间，都用一条线隔开 */
.pi-side-sec + .pi-side-sec,
.pi-side-sec + .pi-side-qc { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
/* 抽屉里「添加<下一段>」按钮（用户 2026-09-21 21:2x）：虚线分隔、整行居中偏左 */
.pi-side-add { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.pi-side-add-tip { margin-left: 10px; font-size: var(--fs-12); color: var(--muted); }

.pi-side-sec-t {
  margin: 0 0 8px; font-size: var(--fs-13); font-weight: 600; color: var(--ink-2);
  padding-left: 8px; border-left: 3px solid var(--primary); line-height: 1.2;
}
.pi-side-row { margin-bottom: 0; }           /* 间距交给 grid 的 gap */
.pi-side-k { flex: 0 0 72px; }               /* 3 列后每格变窄 ⇒ label 收一点 */
/* 到货质检台账要**跨满 3 列**（否则只占一个格子 ）*/
.pi-side-qc { grid-column: 1 / -1; }
/* 抽屉底部的操作条（用户 2026-09-21 19:4x：「保存本行按钮在下方，左边还有 1 个取消按钮」）——
   左「取消」｜右「保存本行」（中间用 .pi-side-sp 撑开）*/
.pi-side-foot {
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-top: 1px solid var(--line); background: var(--bg-list);
}
.pi-side-foot .pi-side-sp { flex: 1 1 auto; }
.pi-side-body { max-height: calc(100vh - 230px); overflow: auto; padding: 10px 12px; }
.pi-side-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.pi-side-k { flex: 0 0 84px; font-size: var(--fs-13); color: var(--muted); }
.pi-side-row input, .pi-side-row select, .pi-side-row textarea {
  flex: 1 1 auto; min-width: 0; height: 28px; padding: 0 8px;
  border: 1px solid var(--line); background: var(--field-bg);
  font-size: var(--fs-13); color: var(--text); font-family: inherit; text-align: left;
}
.pi-side-row textarea { height: auto; min-height: 46px; padding: 4px 8px; }
/* 断点 1240：1200 宽的抽屉在 1250 视口下已接近全屏，太晚就没意义了 */
@media (max-width: 1240px) { .pi-two-side { width: 100%; } }
/* 明细行的「选中圆圈」（用户 2026-09-21 18:2x）：**单选** —— 选中哪一行，
   下方「到货质检」区就只显示哪一行的批次（不然所有行一起铺出来 ）*/
.pi-pick-c { width: 28px; text-align: center; padding: 0 2px !important; }
.pi-pick-h { width: 28px; }
.pi-pick {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; padding: 0; border: 0; background: transparent;
  color: var(--muted); cursor: pointer; border-radius: 50%; vertical-align: middle;
}
.pi-pick:hover { color: var(--primary); }
.pi-pick .pi-pick-dot { opacity: 0; transition: opacity .12s; }
.pi-table tbody tr.is-picked .pi-pick { color: var(--primary); }
.pi-table tbody tr.is-picked .pi-pick .pi-pick-dot { opacity: 1; }
/* 选中行高亮 —— 必须同时盖住上面 `.pi-table tbody td:nth-child(-n+3){background:var(--card)}`
   （前 3 列是 sticky 列，有自己的底色 ⇒ 只写一条会露白 ）*/
.pi-table tbody tr.is-picked > td,
.pi-table tbody tr.is-picked > td:nth-child(-n+3) { background: var(--primary-soft); }
/* 卡片里的台账向**采购明细卡片**看齐（用户 2026-09-21 18:2x：
   「到货质检卡片你的设计和采购明细卡片区别有点大，你需要参考采购明细的卡片设计」）——
   · 采购明细：头部按钮**贴左边缘**（`.pi-card .pi-bar{padding-left:0}`）；输入控件 = `.pi-in`（`--field-bg` 底 + 1px 边框 + 圆角）；
     只读列 = `.po-in.is-readonly`（`--bg-list` 底 + muted 文字）；容器 `.pi-wrap`（完整边框 + 圆角）
   · 台账原来是：头部左右各 10px、控件**无边框透明底**、容器 `.ar-wrap border-top:0`（因上方紧贴 `.ar-bar`，而现在 bar 已搬去卡片头）
   ⇒ 全部收进 `.qc-cards` 作用域（**不动**「到货处理」弹窗里那份台账）*/
.qc-cards .pi-bar { padding-left: 0; }
.qc-cards .ar-wrap { border-top: 1px solid var(--line); }   /* bar 已搬去卡片头 ⇒ 补上边框（圆角保持系统默认 0 ） */
.qc-cards .ar-table thead th { height: 36px; }
.qc-cards .ar-table input:not([type=hidden]),
.qc-cards .ar-table select,
.qc-cards .ar-table textarea,
.qc-cards .ar-table .dp-display,
.qc-cards .ar-table .csel-trigger {
  height: 24px; min-height: 24px; padding: 0 6px;
  border: 1px solid var(--line); background: var(--field-bg); font-size: var(--fs-14);
}
.qc-cards .ar-table .dp-display { padding-right: 24px; }   /* 右边留出日历图标位 */
.qc-cards .ar-table input[readonly],
.qc-cards .ar-table input.is-readonly { background: var(--bg-list); }   /* 只读列 = 采购明细同款浅灰底 */
/* 锁定态（已生效 / 已结案的单）必须跟随采购明细：控件**透明 + 无边框**（= `.pt-locked .pt-ro`）——
   只给卡片控件加边框、不管锁定态，在锁定单上反而比采购明细更花 （用户 18:2x 报的差异正是它）*/
.pt-locked .qc-cards .ar-table input:not([type=hidden]),
.pt-locked .qc-cards .ar-table select,
.pt-locked .qc-cards .ar-table textarea,
.pt-locked .qc-cards .ar-table .dp-display,
.pt-locked .qc-cards .ar-table .csel-trigger {
  background: transparent !important; border: 0 !important;   /* 宽度也归零 = 采购明细同款（只把颜色弄透明会仍占 2px ）*/
}
/* 「采购下单」右侧的 **2 个上下排列按钮**（用户 2026-09-22：「变成2个上下按钮，1个叫单据详情，
   1个叫发起变更」）—— 外层竖排容器塞进横排导航里 */
.dt-rail-stack { display: inline-flex; flex-direction: column; gap: 4px; align-self: center; flex: 0 0 auto; }
.dt-rail-stack > .dt-rail-mini {
  flex: 0 0 auto; min-height: 20px; padding: 2px 14px; border: 1px solid var(--line-strong);
  background: var(--card); color: var(--ink-2); cursor: pointer; font-family: inherit;
  font-size: var(--fs-12); white-space: nowrap;
}
.dt-rail-stack > .dt-rail-mini:hover { border-color: var(--primary); color: var(--primary); }
.dt-rail-stack > .dt-rail-mini:disabled { opacity: .45; cursor: not-allowed; border-color: var(--line); }

/* 「到货质检」子按钮样式（现已不使用，保留给以后可能的子项）—— */
.dt-rail-sub {
  display: block; width: 100%; box-sizing: border-box; margin: 2px 0 8px;
  padding: 5px 10px 5px 22px; border: 0; background: transparent; text-align: left;
  cursor: pointer; font-size: var(--fs-12); color: var(--muted); border-radius: 6px;
}
.dt-rail-sub::before { content: "└"; margin-right: 5px; opacity: .55; }
.dt-rail-sub:hover { background: var(--bg-list); color: var(--ink); }

/* 左侧窄条**禁用态**（库存检查没提交 ⇒ 其余面板不可点）（用户 2026-09-21 21:3x）*/
.dt-rail-btn:disabled,
.dt-rail-sub:disabled { opacity: .45; cursor: not-allowed; }
.dt-rail-sub.is-hi { background: var(--bg-list); color: var(--primary); }
/* 到货质检区：平铺在「采购下单」面板下方（用户 2026-09-21 18:1x）*/
.qc-zone { margin-top: 14px; }
.qc-cards .pi-subcard { margin: 0 0 12px; }
.qc-cards .pi-subcard:last-child { margin-bottom: 0; }
/* 面板窄 ⇒ 17 列台账必须**横滚**（上面那条 `overflow: visible` 是行内卡片时代为消双层横滚写的，
   平铺后会把卡片撑破 ）*/
.qc-cards .pi-subcard > .pi-sub { overflow: visible; max-height: none; padding: 0; }   /* 横滚只由内层 .ar-wrap 一条承担（总计已挪进去）*/

/* ============================================================================
   采购处理 · 「生产记录」面板里的三块（用户 2026-09-21 22:2x）
   ----------------------------------------------------------------------------
   · 上：左「生产计划」右「生产结果」两张半宽卡 —— 它们在 .dt-panel 里、不是 .edit-form 的直接子元素
     ⇒ style.css 里那条 :has(> .card.prod-half) 规则**管不到**它们，这里给 .prod-cards 自己开两列
   · 卡里的字段行仍走 .form-row.is-h[data-span] 那套（标签 114px 左对齐 + 值列对齐 +
     只读行透明底）—— 那是 .edit-form .card > .modal-grid > .form-row 的规则，我们的层级一致
   · 下：「生产记录明细」明细卡（.pi-card.dt-card，沿用明细卡通用样式）
   ============================================================================ */
/* 三块装进**一个大卡片**（用户 2026-09-21 22:1x：
   「你现在要把生产记录中的，生产计划，生产结果，生产记录 3 个卡片放到 1 个大卡片上」）
   · 外层 = 1 张 `.card.prod-one`（标准卡片底/边/内衬）
   · 里面：上 = 两列（生产计划 / 生产结果），下 = 生产记录明细表，最后一行 = 保存按钮
   · 内部那两张半宽卡与明细卡 **只借布局类、不再各画一层卡片**（否则就是「卡中卡」——用户以前明确反对过）：
     · 半宽卡必须**保留 `.card` 这个类名** ——
       那套「标签 114px 左对齐 + 自动补冒号 + 只读行透明底」规则靠它命中 ⇒ 只把它的**外观**抹掉
     · 明细卡同理：`.pi-card.dt-card` 的布局 / 表头吸顶 / 折叠都靠它，只抹外观 */
.dt-panel[data-dt-tab="prod"] .prod-one { padding: 14px; }
.dt-panel[data-dt-tab="prod"] .prod-one > .prod-cards {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  /* `stretch`（不是 start）：两栏等高，右栏那条**竖分割线**才能顶满整段高度 */
  gap: 0 16px; align-items: stretch;
  padding-bottom: 12px; border-bottom: 1px solid var(--line); margin-bottom: 12px;
}
.dt-panel[data-dt-tab="prod"] .prod-one > .prod-cards > .card,
.dt-panel[data-dt-tab="prod"] .prod-one > .pi-card {
  background: transparent; border: 0; box-shadow: none; padding: 0; margin: 0;
}
/* 「生产计划」与「生产结果」之间的**竖分割线**（用户 2026-09-21 22:2x：「生产计划段落和生产结果段落
   中间需要有竖线分割」）—— 画在右栏的左边框上（两栏 gap 16px + 右栏 padding-left 16px ⇒ 线居中）
   特异性要压过上面那条「抹外观」的规则（多了两个 .prod-half + 一个 + 选择器）*/
.dt-panel[data-dt-tab="prod"] .prod-one > .prod-cards > .card.prod-half + .card.prod-half {
  border-left: 1px solid var(--line);
  padding-left: 16px;
}
@media (max-width: 1100px) {
  /* 窄屏堆成一列 ⇒ 那条**竖线必须收掉**（否则会变成左栏上面一条莫名其妙的竖线）*/
  .dt-panel[data-dt-tab="prod"] .prod-one > .prod-cards { grid-template-columns: 1fr; }
  .dt-panel[data-dt-tab="prod"] .prod-one > .prod-cards > .card.prod-half + .card.prod-half {
    border-left: 0; padding-left: 0; padding-top: 12px; border-top: 1px solid var(--line);
  }
}
.dt-panel[data-dt-tab="prod"] .prod-foot { display: flex; align-items: center; gap: 8px; margin: 12px 0 0; }


/* ============================================================================
   明细字段表（非内置 `.pi-table`）的「操作」列用**两个图标**时的间距（用户 2026-09-21 22:2x）
   —— 与内置「采购明细」操作列同款：两个 `.pi-ico` 之间 8px（那条规则写在 `.pi-table` 下、这里不命中）
   ============================================================================ */
.dt-table .dt-op2 { white-space: nowrap; text-align: center; }
.dt-table .dt-op2 .pi-ico + .pi-ico { margin-left: 8px; }

/* 「生产计划 / 生产结果」卡的标题行（用户 2026-09-21 22:4x）——
   标题左、「重置生产计划」按钮右；标题自带的下间距交给行来控制（否则两边不对齐）*/
.prod-sec-head { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; }
.prod-sec-head > .rel-title, .prod-sec-head > .sec-title { margin: 0; }

/* 采购处理·生产加工卡片的三项元信息（用户 2026-09-21 22:5x）—— 复用全局 .sc-meta，只补上间距*/
.dt-panel[data-dt-tab="prod"] .prod-meta { margin: 10px 0 0; }

/* 系统生成的生产记录行（用户 2026-09-21 23:0x）：不支持删除和修改 ——
   数据格淡底灰字（看得出是锁的）；select 用 pointer-events:none 注销交互
   （不能用 disabled：不提交 ⇒ 保存时那一行的值会丢）*/
.dt-body tr.dt-sysrow td { background: var(--bg-list); }
.dt-body tr.dt-sysrow .dt-no { color: var(--muted); }
.dt-body tr.dt-sysrow input, .dt-body tr.dt-sysrow textarea { color: var(--muted); }
.dt-body .dt-ro { pointer-events: none; }

/* 历史（已失效）的生产记录行（用户 2026-09-21 23:3x）：重置生产计划后不许删 ——
   内容前缀由服务端加「【已失效】」；这里把行淡一点，让「已归档」一眼可辨 */
.dt-body tr.dt-invalidrow td { background: var(--bg-list); }
.dt-body tr.dt-invalidrow input, .dt-body tr.dt-invalidrow textarea,
.dt-body tr.dt-invalidrow select { color: var(--muted); }

/* 「补加记录」行（用户 2026-09-21 23:3x）：进度固定「补加记录」、这一格锁死 ——
   本行及其**上方所有行**的 也被前端禁用（「从补加记录第一条开始往上，就不再支持删除了」）*/
.dt-body tr.dt-bujiarow select.dt-ro { color: var(--muted); }

/* 生产加工·生产记录明细（用户 2026-09-21 23:0x）：
   「记录内容标题左右居中，但是输入内容需要左对齐」
   —— 表头保持居中（单元格 text-align:center 传给了控件）；把文本输入框 / 多行文本改成左对齐，
      日期控件的显示框与下拉保持原样（居中）
   只限本面板（采购处理的生产加工），不动入库明细等其它明细表 */
.dt-panel[data-dt-tab="prod"] .dt-table input[type="text"]:not(.dp-display),
.dt-panel[data-dt-tab="prod"] .dt-table textarea { text-align: left; }


/* ============================================================================
   详情页文字统一 **13px / 不加粗**（含列标题）（用户 2026-09-22 21:0x：
   「所有详情页文字大小为13，不加粗，包括列标题」）——
   · 作用域 = 详情页表单容器 **`.form-flat`**（`entity/form` / `purchasesProc/form` / `purchases/_orderCard` /
     `inspections/form` / `supplies/new` 等**所有详情页**都用它）⇒ **列表页 / 首页完全不受影响**
   · 用 `!important`：详情页里已有一堆更具体的字号/字重规则（`.card h2{18}` / `.pi-table th{600}` /
     `.dt-*` …），要一次性压平只能这样（用户要的就是"全页统一"）
   ============================================================================ */
.form-flat, .form-flat * { font-size: 13px !important; font-weight: 400 !important; }

/* 「新增采购明细」弹窗（用户 2026-09-22 21:1x，参照其截图）—— 居中对话框 */
.pi-add-modal { position: fixed; inset: 0; z-index: 5000; background: rgba(15,23,42,.45); display: flex; align-items: center; justify-content: center; }
.pi-add-modal[hidden] { display: none; }
.pi-add-dlg { width: 1200px; max-width: 94vw; max-height: 88vh; overflow: auto; background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow-lg); display: flex; flex-direction: column; }
.pi-add-hd { display: flex; align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--line); }
.pi-add-x { margin-left: auto; border: 0; background: transparent; font-size: 18px; line-height: 1; cursor: pointer; color: var(--muted); }
/* ⚠️ 这个左右 16px 的 padding 要算进弹窗宽度：
   弹窗 1200 - 边框 2 - padding 32 = 1166 ≥ 表格列宽合计 1164 ⇒ 不出现横向滚动 ✓
   （2026-09-27 踩过：只按 1164 把弹窗设成 1180，**漏算这 32px** ⇒ 内层横滚 58px ✗）*/
.pi-add-bd { padding: 14px 16px; }
/* 上方五格 = **上下显示**（用户 2026-09-22：「把所属订单，下单日期，申请人，本单采购总成本，调整为上下显示」；
   随后又加了「销售订单」一格、并把「所属订单」改名「系统单号」⇒ 现在 5 格）
   —— 原来是「标签在左、值在右」的两对并排 ⇒ 现在每格一个 .pi-add-f（标签在上、值在下）*/
.pi-add-top { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px 16px; align-items: start; }
.pi-add-f { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.pi-add-f > label { color: var(--dt-label); }
.pi-add-ro { min-height: 30px; display: flex; align-items: center; color: var(--text); }
/* 弹窗「下单日期」= 系统标准日期选择器（用户 2026-09-22：原来原生 date 输入不支持修改）——
   这里只把它压到**信息行的高度 30**、并放开 .dp-field 默认的 220 上限（要撑满格子）*/
.pi-add-f .dp-field { max-width: none; width: 100%; }
.pi-add-top .dp-display { height: 30px; min-height: 30px; line-height: 28px; padding: 0 30px 0 8px; }
/* 弹窗里的日期控件**必须能改**（用户 2026-09-22：「现在弹窗中，下单日期不支持修改，需要支持修改」）——
   真凶：.pt-locked .pi-dp 那条给了 pointer-events:none，.pt-locked .pi-dp .dp-trigger 那条把日历图标
   display:none !important（这两条本来是给**明细表格单元格**的日期用的）⇒ 弹窗的字段原来带了 pi-dp 类，
   整单只读时被一起锁死 ⇒ ① 弹窗的 .dp-field 不再带 pi-dp；② 这里再兜一条，任何锁定态下都能点 */
.pt-locked .pi-add-modal .dp-field,
.pi-locked .pi-add-modal .dp-field { pointer-events: auto !important; }
.pt-locked .pi-add-modal .dp-trigger,
.pi-locked .pi-add-modal .dp-trigger { display: inline-flex !important; }
.pt-locked .pi-add-modal .dp-display::placeholder { color: var(--muted); }
/* 上方四格 ↔ 采购明细 的分割线（用户 2026-09-22）——
   只画上边框，自身不带下外边距：下面 .pi-add-sec 自带 14px 上边距，相邻外边距自动合并
   ⇒ 线在四格下方 14px、采购明细标题在线下方 14px */
.pi-add-hr { border-top: 1px solid var(--line); margin: 14px 0 0; }
/* 「＋ 添加明细行」的定位（用户 2026-09-22 两条口径一起满足）：
   ① 「和删除按钮居中对齐」②「右侧要和下方备注的右侧右对齐」
   ⇒ 把表格**操作列加宽到 100px**（= 按钮宽度）+ 该列内容**居中**，容器也与它**同宽 100px**、内部居中：
      · 容器右缘 = 内容右缘 = 下方「备注」textarea 的右缘 ⇒ ②
      · 按钮中心 = 操作列中心 = 图标中心 ⇒ ①
   容器宽度必须 = 操作列宽：容器比按钮窄的话，按钮会向右侧溢出弹窗、挤出横向滚动条
      （2026-09-22 实测：36px 容器 + 100px 按钮 ⇒ 右缘超出弹窗 15px）*/
.pi-add-sec-end { flex: none; width: 100px; display: flex; justify-content: center; }
.pi-add-sec-end > .btn { flex: none; white-space: nowrap; }
.pi-add-sec { display: flex; align-items: center; gap: 8px; margin: 14px 0 8px; }
.pi-add-sp { flex: 1 1 auto; }
.pi-add-table { width: 100%; table-layout: fixed; border-collapse: collapse; }
.pi-add-table th { height: 32px; padding: 2px 6px; border-bottom: 1px solid var(--line); text-align: center; color: var(--dt-label); }
/* **列标题与行内容一律左右居中**（用户 2026-09-22：「所有的列标题与行左右居中对齐，
   包括总计对应的汇总，也要左右居中对齐」）——
   与明细主表同款：单元格居中 + 控件里的文字也居中（select 要 text-align-last）*/
.pi-add-table td { height: 36px; padding: 2px 6px; border-bottom: 1px dashed var(--line); text-align: center; }
.pi-add-table select, .pi-add-table input { text-align: center; }
.pi-add-table select { text-align-last: center; }
/* 单位那个窄下拉：左右内边距收窄，腾地方给 1 个字 + 原生箭头 */
.pi-add-table select.pia-unit { padding: 0 2px; }
/* 弹窗里的数字输入框**去掉浏览器自带的上下箭头** —— 它在悬停/聚焦时会占掉输入框内约 13px，
   一填 8 位数字就有几位被箭头盖住 ⇒ 「确保能输入 8 位」落不了地 （用户 2026-09-22 特意强调）*/
.pi-add-table input[type=number] { appearance: textfield; -moz-appearance: textfield; }
.pi-add-table input[type=number]::-webkit-inner-spin-button,
.pi-add-table input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.pi-add-table select, .pi-add-table input { width: 100%; box-sizing: border-box; height: 28px; padding: 0 6px; border: 1px solid var(--field-border, var(--line)); background: var(--field-bg); color: var(--text); font-family: inherit; }
.pi-add-table .pia-sub { text-align: center; }   /* 合计/采购成本两格也居中（用户 2026-09-22：所有行左右居中）*/
/* 新增明细弹窗里的**金额 / 百分比**列（用户 2026-09-22：「货币符号 / 百分号没有带出来」）——
   与明细表 .pi-table .pi-cur / .pia-pct 同一套做法：符号是单元格里的一个 i，跟着值走
   采购成本 = 公式列（总价 ÷ (1 + 税率)）⇒ 只读展示、不给输入框 */
.pi-add-table .pia-cur, .pi-add-table .pia-pct { display: flex; align-items: center; justify-content: center; gap: 2px; }
.pi-add-table .pia-cur > input, .pi-add-table .pia-pct > input { flex: 1 1 auto; min-width: 0; width: auto; }
.pi-add-table .pia-cur > i, .pi-add-table .pia-pct > i { flex: none; font-style: normal; }
/* 左侧「**采购明细**」卡（用户 2026-09-22：「在全部那一行左侧，插入1张卡片，卡片叫采购明细」）——
   面板改成「左卡 + 右内容」：左 248px 的供应商列表，右边照旧（子 tab + 明细 + 到货质检）
   样式照用户截图：圆点单选 / 供应商名 / 货品行 / ¥金额 ｜ N 批到货 ｜ 入库 N/M，行间 1px 浅线 */
.pi-cardrow { display: flex; align-items: flex-start; gap: 12px; }
.pm-main { flex: 1 1 auto; min-width: 0; }
.pm-sup-card { flex: 0 0 248px; width: 248px; background: var(--card); border: 1px solid var(--line); max-height: 620px; overflow: auto; }
.pm-sup-hd { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--line); color: var(--ink-2); }
/* 卡头那枚「+采购明细」：比默认 .pi-tab 收窄一点，免得把小卡头撑高 */
.pm-sup-hd > .pm-sup-add { flex: none; padding: 3px 10px; font-size: var(--fs-12); }
.pm-sup { display: flex; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--line); cursor: pointer; }
.pm-sup:last-child { border-bottom: 0; }
.pm-sup:hover { background: var(--bg-list); }
.pm-sup.is-on { background: var(--tint-primary); }
.pm-dot { flex: none; width: 12px; height: 12px; margin-top: 3px; box-sizing: border-box; border: 1.5px solid var(--line-strong); border-radius: 50%; }
.pm-sup.is-on .pm-dot { border-color: var(--primary); background: var(--primary); box-shadow: inset 0 0 0 2px var(--card); }
.pm-sup-bd { flex: 1 1 auto; min-width: 0; }
.pm-sup-nm { color: var(--text); }
.pm-sup-pr { margin-top: 2px; color: var(--ink-2); font-size: var(--fs-12); }
.pm-sup-mt { margin-top: 4px; display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: var(--fs-12); }
.pm-amt { color: var(--text); }
.pm-sep { display: inline-block; width: 1px; height: 11px; background: var(--line); }

/* 采购明细弹窗的**查看态**（单据详情；用户 2026-09-22）——
   同一个弹窗、同一套布局，只是：隐藏「＋添加明细行」/ 保存按钮 / 每行的 与删除列，
   控件本身由 JS 置只读（输入框 readonly / 下拉 disabled）*/
.pi-add-modal.is-view .pi-add-sec #piaAddRow { display: none !important; }
.pi-add-modal.is-view .pi-add-ft #piaSave { display: none !important; }
.pi-add-modal.is-view .pi-add-table .pia-del { display: none !important; }
/* 删除列（表头 .pia-del-h / 行尾 .pia-del-c）隐藏 —— 用**类名**精准命中，别再写 :last-child：
   查看态的行只有 9 个文本格，用 :last-child 会把「采购成本」一起藏掉 */
.pi-add-modal.is-view .pi-add-table .pia-del-h,
.pi-add-modal.is-view .pi-add-table .pia-del-c { display: none !important; }
/* 查看态的明细行 = **纯文字**（用户 2026-09-22：「明细里面就不要加什么外框了，直接都以值的形式呈现。
   要不总是觉得想点击输入」）—— 行里已经没有任何输入框/下拉了；这里再把日期框 / 备注框的外框也去掉 */
.pi-add-modal.is-view .pi-add-table .pi-view-row td { cursor: default; }
.pi-add-modal.is-view .dp-display,
.pi-add-modal.is-view #piaNote { border: 0 !important; background: transparent !important; box-shadow: none !important; }
/* 查看态里「下单日期」必须**彻底收掉**（触发器 + 面板都要），否则还能点日历改日期 （用户 2026-09-22 报）——
   坑在优先级：早先为修「弹窗里日期点不动」加过
     .pt-locked .pi-add-modal .dp-trigger { display: inline-flex !important; }   ← 3 个类
   而 is-ro 那条只有 2 个类 ⇒ 被它压住、触发器又冒出来了
   ⇒ 这里用 4 个类的新选择器（.pi-add-modal.is-view .pi-add-top .dp-field .dp-trigger）压过它 */
.pi-add-modal.is-view .pi-add-top .dp-field .dp-trigger,
.pi-add-modal.is-view .pi-add-top .dp-field .dp-panel { display: none !important; }
.pi-add-modal.is-view .pi-add-top .dp-field { pointer-events: none !important; }

/* 「采购下单」按钮下方的**子 tab 导航**（用户 2026-09-22）——
   与列表页 tab 同款：白底 + 选中项**主色文字** + 文字下方 3px 主色下划线 */
.pi-subtabs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; background: var(--card); border-bottom: 1px solid var(--line); padding: 0 8px; margin-bottom: 8px; }
.pi-subtab { position: relative; border: 0; background: transparent; padding: 8px 12px; font-family: inherit; font-size: var(--fs-13); color: var(--ink-2); cursor: pointer; }
.pi-subtab:hover { color: var(--primary); }
.pi-subtab.is-on { color: var(--primary); font-weight: var(--fw-medium); }
.pi-subtab.is-on::after { content: ''; position: absolute; left: 12px; right: 12px; bottom: -1px; height: 3px; background: var(--primary); }

/* 弹窗「总计行」（用户 2026-09-22）—— 与「采购明细」卡的 .pi-total-row 同款：
   底色 / 上边框 / 字重沿用全局 .pi-total-row td；这里只补**居中**并去掉行末那条虚线 */
.pi-add-table .pi-total-row td { text-align: center; border-bottom: 0; }
.pi-add-sum { text-align: right; margin-top: 8px; color: var(--text); }
.pi-add-sum b { color: var(--danger); }
.pi-add-lb { display: block; margin-top: 12px; color: var(--dt-label); }
.pi-add-bd > textarea { width: 100%; box-sizing: border-box; min-height: 64px; padding: 8px; border: 1px solid var(--field-border, var(--line)); background: var(--field-bg); color: var(--text); font-family: inherit; }
.pi-add-ft { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--line); }

/* ============================================================
   审批弹窗 + 左卡「审核结果 / 前往审核」按钮（用户 2026-09-22）
   「采购明细左侧卡片，在 +采购明细 左侧放上审批记录按钮…弹窗样式大概是这样」
   ------------------------------------------------------------------
   · 外壳**直接复用** .pi-add-modal / .pi-add-dlg / .pi-add-hd / .pi-add-bd / .pi-add-ft
   · 申请人 = 审核记录（审批链路 + 流转记录，宽 660）；审核人 = 前往审核（再带一份采购详情，宽 1140）
   ============================================================ */
.pm-sup-hd > .pm-sup-apv { flex: none; margin-left: auto; padding: 3px 10px; font-size: var(--fs-12); background: var(--bg-list); color: var(--primary); border: 1px solid var(--primary); }
.pm-sup-hd > .pm-sup-apv:hover { filter: none; background: var(--tint-primary); }
.pm-sup-hd > .pm-sup-apv.is-act { background: var(--c-green); border-color: var(--c-green); color: #fff; }
.pm-sup-hd > .pm-sup-apv.is-act:hover { filter: brightness(1.06); }
.pm-sup-hd > .pm-sup-add { margin-left: 0; }

.apv-dlg { width: 660px; }
.apv-dlg.is-rev { width: 1140px; }
.apv-h { margin: 14px 0 8px; color: var(--ink-2); }
.apv-h:first-child { margin-top: 0; }
.apv-sub { color: var(--muted); font-size: var(--fs-12); font-weight: 400; }
.apv-box { border: 1px solid var(--line); background: var(--card); }
.apv-hint { padding: 8px 12px; border-bottom: 1px solid var(--line); background: var(--bg-list); color: var(--ink-2); font-size: var(--fs-12); }
.apv-empty { padding: 12px; color: var(--muted); font-size: var(--fs-12); }
.apv-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--line); }
.apv-row:last-child { border-bottom: 0; }
.apv-n { flex: none; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; font-style: normal; font-size: var(--fs-12); color: var(--primary); background: var(--tint-primary); border: 1px solid var(--primary); }
.apv-lv { flex: none; color: var(--text); }
.apv-appr { flex: none; color: var(--ink-2); }
.apv-cond { flex: none; color: var(--muted); font-size: var(--fs-12); }
.apv-sp { flex: 1 1 auto; }
.apv-frow { display: grid; grid-template-columns: 118px 76px 62px minmax(0, 1fr); gap: 10px; align-items: start; padding: 8px 12px; border-bottom: 1px solid var(--line); font-size: var(--fs-12); }
.apv-frow:last-child { border-bottom: 0; }
.apv-at { color: var(--muted); }
.apv-who { color: var(--text); }
.apv-act { color: var(--ink-2); }
.apv-det { color: var(--ink-2); word-break: break-all; }
.apv-op { width: 100%; box-sizing: border-box; min-height: 64px; padding: 8px; border: 1px solid var(--field-border, var(--line)); background: var(--field-bg); color: var(--text); font-family: inherit; }
.apv-detail { border: 1px solid var(--line); overflow-x: auto; }
.apv-detail-tb .pia-del-h, .apv-detail-tb .pia-del-c { display: none !important; }
.apv-detail-tb td { color: var(--text); }

/* 确认框（#globalConfirm）必须压在 .pi-add-modal（z-index 5000）**之上**（用户 2026-09-22 实测）——
   从审批弹窗里点「审核通过」会先弹应用内确认框（.to-modal 默认 z-index 90）
   ⇒ 被弹窗（5000）整个盖住：看得见却**点不到**，审批动作永远发不出去
   （真鼠标探针实锤：确认框「确认」按钮 center 点下去命中的是审批弹窗）*/
#globalConfirm { z-index: 9000; }

/* 查看态收尾（用户 2026-09-22 23:1x）——
   ① 「单据详情」里**备注又能编辑了** ⇒ JS 已补 `#piaNote.readOnly = true`（在 open() 之后）；
      这里再把「看着像输入框」的部分收掉（光标 / 颜色跟其它只读值一致）
   ② 「下单日期不可编辑了，但颜色跟其它字段的值不一样」——
      真凶 = `.dp-field.is-ro .dp-display { color: var(--muted) }`（第 4043 行那条，是给**表单里**只读日期用的灰）
      ⇒ 其它只读值是 `.pi-add-ro { color: var(--text) }` 的深色 ⇒ 查看态把日期值也改成 var(--text)
      （背景在上一段已改成 transparent，颜色这条当时漏了）*/
.pi-add-modal.is-view .pi-add-top .dp-field .dp-display { color: var(--text) !important; cursor: default; }
.pi-add-modal.is-view #piaNote { color: var(--text) !important; cursor: default; resize: none; }

/* ============================================================================
   只读 / 带出字段：**全站统一「纯文字」观感**（用户 2026-09-25）
   ----------------------------------------------------------------------------
   诉求（原话）：「全局，包括列表和弹窗所有带出的字段，并且不支持修改的字段。
     统一不带背景框；鼠标放上去的时候，下拉的也不应该出现其他无法选择的下拉值；
     日期也不应该出现日期选择；字段也不需要再显示 1 个外框。」
   ⇒ 只读字段一律 = **一段普通文字**：无底色 / 无边框 / 无外框；不弹下拉、不弹日历。
   ----------------------------------------------------------------------------
   只认下面这些**只读标记**（前面各页面零散写过的规则都归到这里，一处生效全站）：
     · 控件自带：`.is-readonly` / `.pt-ro` / `.pi-locked-ctl` / `.lk-input` / `.sh-in.is-auto`
     · 容器标记：`.pt-locked`（整单锁定）/ `.pi-locked` / `.is-ro`（form-row · dp-field · csel · tr）
   ----------------------------------------------------------------------------
   **绝不写 `input[readonly]` 这种通配**：日期控件的「显示框」(.dp-display / .tp-display)
      天生就带 readonly（真正的值存在同行的 .dp-value 里）⇒ 一刀切会把**可编辑的日期**
      也抹成纯文字 （2026-09-25 实测确认：订单列表筛选栏「下单开始日期」、转单卡片的日期
      都是**可编辑**的日期控件，必须保留框）
   `.pi-unlock`（整单锁定下仍允许改的列，如采购明细「物流状态」）**必须排除**——
      否则 pointer-events 一关，单据一提交就再也推不动状态
   ============================================================================ */

/* ① 去掉「输入框色块」：底色 / 边框 / 阴影全清 */
.is-readonly, .pt-ro, .pi-locked-ctl:not(.pi-add-modal *),
.pt-locked .po-in:not(.pi-add-modal *), .pt-locked .pi-in:not(.pi-add-modal *),
.pt-locked .pi-table .pi-in:not(.pi-add-modal *),
.pt-locked .pi-table select.pt-ro:not(.pi-add-modal *), .pt-locked .pi-table input.pt-ro:not(.pi-add-modal *),
.pi-locked .pi-in:not(.pi-add-modal *), .pi-locked select:not(.pi-add-modal *),
.lk-wrap .lk-input, .sh-in.is-auto, .sc-note[disabled],
.is-ro input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
.is-ro textarea, .is-ro select,
.is-ro .dp-display, .is-ro .tp-display, .is-ro .csel-trigger,
.pt-locked input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not(.pi-unlock):not(.pi-add-modal *),
.pt-locked textarea:not(.pi-unlock):not(.pi-add-modal *):not(.sc-note), .pt-locked select:not(.pi-unlock):not(.pi-add-modal *),
.pt-locked .dp-display:not(.pi-add-modal *), .pt-locked .tp-display:not(.pi-add-modal *), .pt-locked .csel-trigger:not(.pi-unlock):not(.pi-add-modal *),
.pi-locked input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not(.pi-unlock):not(.pi-add-modal *),
.pi-locked textarea:not(.pi-unlock):not(.pi-add-modal *):not(.sc-note), .pi-locked .dp-display:not(.pi-add-modal *), .pi-locked .csel-trigger:not(.pi-unlock):not(.pi-add-modal *),
tr.is-ro input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
tr.is-ro textarea, tr.is-ro select,
tr.is-ro .dp-display, tr.is-ro .tp-display, tr.is-ro .csel-trigger {
  background: transparent !important;
  background-color: transparent !important;
  background-image: none !important;
  border-color: transparent !important;
  box-shadow: none !important;
  color: var(--text) !important;
}

/* ② 只读字段不接收鼠标 ⇒ 下拉不展开（再也没有「选不了的候选集」）、日期不弹日历
   ⚠️ `.sc-note`（库存检查的**备注**框）必须豁免（用户 2026-09-27）：
      它是「提交后 disabled / 重置后即时解锁」的**特例**，而 body 上的整页锁定类会把
      `pointer-events` 一起关掉 ⇒ 重置后 radio 能点、备注却点不进去 ✗
      豁免后仍然安全：锁定时它带 `disabled`，浏览器自己就拦住交互 ✓ */
.is-readonly, .pt-ro, .pi-locked-ctl:not(.pi-add-modal *), .lk-input, .sh-in.is-auto, .sc-note[disabled],
.is-ro input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
.is-ro textarea, .is-ro select,
.is-ro .csel-trigger, .is-ro .dp-trigger, .is-ro .tp-trigger,
.pt-locked input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not(.pi-unlock):not(.pi-add-modal *),
.pt-locked textarea:not(.pi-unlock):not(.pi-add-modal *):not(.sc-note), .pt-locked select:not(.pi-unlock):not(.pi-add-modal *),
.pt-locked .csel-trigger:not(.pi-unlock):not(.pi-add-modal *),
.pi-locked input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not(.pi-unlock):not(.pi-add-modal *),
.pi-locked textarea:not(.pi-unlock):not(.pi-add-modal *):not(.sc-note), .pi-locked .csel-trigger:not(.pi-unlock):not(.pi-add-modal *),
tr.is-ro input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
tr.is-ro textarea, tr.is-ro select,
tr.is-ro .csel-trigger {
  cursor: default !important;
  pointer-events: none !important;
}

/* ④ 「单据动态」卡片**整体放行**（用户 2026-09-27：「处理中心，单据动态，发表评论，
   现在一直是被锁的状态，无法操作。包括下面的按照人员，操作筛选」）——
   它虽然被 `include` 在 `<form id="entityForm">` **里面**、又整个落在 `.pt-locked` 作用域内，
   但**不是单据字段**：评论框（`#dfText`）与两个筛选下拉（`#dfWho` / `#dfAct`）
   在任何单据状态下都该能打字 / 能选 ✓
   ⚠️ 两个后果都要一起还原，否则只是"半修"：
     · 上面 ② 把它 `pointer-events:none` ⇒ 点不进、选不动
     · 上面 ① 把它 `background:none / border-color:transparent` ⇒ **看着就像被锁**（纯文字观感）
   ⚠️ 「发表」按钮是 `<button>`、不在上面那些选择器里 ⇒ 它**一直能点**，
      点了只弹「请先写内容」—— 正是用户看到的"锁着、按钮又像能点"的怪状态 ✗✗
   ⚠️ **已作废**的单不走这里（那是 `body.pt-voided` 的另一套规则）⇒ 作废单上仍按"只放行查看类"锁着 ✓ */
.pi-locked #docFeed textarea, .pt-locked #docFeed textarea,
.pi-locked #docFeed select, .pt-locked #docFeed select,
.pi-locked #docFeed input:not([type="hidden"]), .pt-locked #docFeed input:not([type="hidden"]) {
  pointer-events: auto !important;
  cursor: auto !important;
  background: var(--field-bg) !important;
  border-color: var(--field-border) !important;
  box-shadow: none !important;
  color: var(--text) !important;
  -webkit-text-fill-color: var(--text) !important;
}

/* ③ 把「入口按钮」整条收掉：下拉箭头 · 日历 / 时间按钮 · 展开的面板 */
/* 注意：这里**不隐藏 .dp-panel / .tp-panel**（用户 2026-09-25 报的回归）——
   日历面板打开时会被**移到 body** 去定位，而 body 在锁定单据上带 .pt-locked
   ⇒ `.pt-locked .dp-panel{display:none}` 会把**弹出来的日历**整个藏掉（点日期框毫无反应）✗
   只读日期本来靠"触发器隐藏 + display 不可点"就够（面板压根打不开），无需再藏面板 ✓ */
.is-ro .csel-caret, .pt-locked .csel-caret:not(.pi-unlock):not(.pi-add-modal *), .pi-locked .csel-caret:not(.pi-add-modal *), tr.is-ro .csel-caret,
.is-ro .dp-trigger, .is-ro .tp-trigger,
.pt-locked .dp-trigger:not(.pi-add-modal *), .pt-locked .tp-trigger:not(.pi-add-modal *),
.pi-locked .dp-trigger:not(.pi-add-modal *), .pi-locked .tp-trigger:not(.pi-add-modal *),
tr.is-ro .dp-trigger, tr.is-ro .tp-trigger,
.dp-field.is-ro .dp-trigger, .tp-field.is-ro .tp-trigger {
  display: none !important;
}

/* ④ 「字段还有一个外框」——控件级包装本身也不画框（csel 外壳 / 查找引用外壳 / 输入框壳） */
.is-ro .csel, .pt-locked .csel:not(.pi-add-modal *), .pi-locked .csel:not(.pi-add-modal *), tr.is-ro .csel,
.is-ro .lk-wrap, .pt-locked .lk-wrap:not(.pi-add-modal *),
.is-ro .amount-wrap, .pt-locked .amount-wrap:not(.pi-add-modal *), tr.is-ro .amount-wrap,
.is-ro .num-wrap, .pt-locked .num-wrap:not(.pi-add-modal *), tr.is-ro .num-wrap,
.is-ro .range-field, .pt-locked .range-field:not(.pi-add-modal *),
.is-ro .pi-dp, .pt-locked .pi-dp:not(.pi-add-modal *), tr.is-ro .pi-dp {
  border-color: transparent !important;
  background: transparent !important;
  box-shadow: none !important;
}
/* 只读**表单行**的内距归零：文字与同排其它文字**左边界对齐**
   （否则框没了、输入框的左内距 10px 还在 ⇒ 看着是「框没了但缩进还在」）
   —— 与项目早先给 `[data-span]` 只读行定的口径一致（本文件上部那条 `padding-left:0`），此处推广到所有只读表单行；
      表格单元格（`tr.is-ro`）**不动**，那里要保持表格内距 */
.form-row.is-ro > input,
.form-row.is-ro > textarea,
.form-row.is-ro > .dp-field .dp-display,
.form-row.is-ro > .tp-field .tp-display,
.form-row.is-ro > .csel .csel-trigger { padding-left: 0 !important; }

/* ============================================================
   「审批流程设置」（用户 2026-09-26：按原型图重做的流程列表 + 新建/编辑弹窗）
   ------------------------------------------------------------------
   · 列表 = .apvf-card（标题行 + 右上「新建审批流程」+ 表格 6 列：流程ID / 名称 / 单据类型 / 状态 / 节点预览 / 操作）
   · 弹窗 = .apvf-mask（遮罩）+ .apvf-modal（居中卡片，主体可滚动）
   · 节点卡片 = .apvf-node（灰底方框：标签 + 审批人下拉 + 红「移除」），节点之间一个 ↓ 箭头
   · 固定节点（节点1 发起人提交）用 .apvf-node-fix（浅一档底色）
   全站 border-radius:0 ⇒ 这里也不写圆角 ✓
   ============================================================ */
.apvf-card { max-width: 1080px; margin-top: 16px; padding: 16px; }
.apvf-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.apvf-head h2 { margin: 0; font-size: 16px; }
.apvf-table-wrap { border: 1px solid var(--line); }
.apvf-table { width: 100%; }
.apvf-table th { padding: 9px 10px; }
.apvf-table td { padding: 9px 10px; font-size: var(--fs-14); }
.apvf-preview { color: var(--text); }
.apvf-inline { display: inline-block; margin-left: 6px; }
.apvf-hint { margin-top: 10px; font-size: var(--fs-13); color: var(--muted); line-height: 1.8; }
.apvf-mask { position: fixed; inset: 0; z-index: 500; background: rgba(17, 24, 39, .38);
  display: flex; align-items: flex-start; justify-content: center; padding: 40px 16px; overflow: auto; }
.apvf-mask[hidden] { display: none !important; }
.apvf-modal { width: 520px; max-width: 100%; background: var(--card); border: 1px solid var(--line);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column; max-height: calc(100vh - 80px); }
.apvf-modal-body { padding: 16px; overflow: auto; }
.apvf-modal-t { margin: 0 0 14px; font-size: 16px; }
.apvf-lab { display: block; margin: 12px 0 6px; font-size: var(--fs-13); color: var(--text); }
.apvf-inp { width: 100%; }
.apvf-node { border: 1px solid var(--line); background: var(--bg-list); padding: 10px 12px; margin-bottom: 8px; }
.apvf-node-t { display: block; font-size: var(--fs-13); color: var(--text); margin-bottom: 8px; }
.apvf-node-fix { background: var(--card-2); }
.apvf-node .apvf-del { margin-top: 8px; }
.apvf-arrow { text-align: center; color: var(--muted); margin: 4px 0 8px; }
.apvf-add { margin-top: 4px; }
.apvf-modal-foot { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--line); }
.apvf-chk { display: flex; align-items: center; gap: 6px; font-size: var(--fs-13); cursor: pointer; }
.apvf-chk input { margin: 0; }
.apvf-head-r { display: flex; align-items: center; gap: 14px; }
.apvf-plans { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.apvf-cond { margin-top: 10px; padding: 10px; border: 1px solid var(--line); background: var(--card); }
.apvf-cond-t { font-size: var(--fs-13); color: var(--muted); margin-bottom: 8px; }
.apvf-rule { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.apvf-rule .apvf-rstaff { flex: 1 1 0; min-width: 0; }
.apvf-rule .apvf-rwho { flex: 1 1 0; min-width: 0; }
.apvf-rule-arrow { color: var(--muted); flex: 0 0 auto; }
.fd-side-item.is-detail2 { padding-left: 44px; font-size: var(--fs-12); }
