/* ============================================================
   进销存财务管理系统 —— 视觉层
   设计参考：Apple Human Interface Guidelines
   原则：Clarity（清晰）/ Deference（内容优先）/ Depth（层次）
   —— 大量留白、克制的分隔线、柔和的多层阴影、毛玻璃材质、
      胶囊形控件、连续曲率圆角、系统字体栈
   ============================================================ */

/* ---------------- 设计令牌 ----------------
   配色主题：墨蓝 · 雅致（Graphite & Indigo）
   —— 中性色偏一点冷调石墨，比纯灰更沉静、更"贵"；
   强调色用更深的靛蓝（indigo），配柔和渐变与细腻阴影，追求"美观大气"。
   财务惯例：涨/正=红，跌/负=绿（中国习惯）由语义色承担，不受强调色影响。 */
:root {
  /* 强调色（靛蓝 Indigo · 沉稳大气） */
  --blue: #2f5bd7;
  --blue-hover: #3a68e6;
  --blue-press: #2749b8;
  --blue-tint: rgba(47, 91, 215, .1);
  --blue-soft: rgba(47, 91, 215, .06);
  /* 行遮罩（全局统一）：所有表格的行悬停/联动高亮都只用 --row-hover 这一层极淡蓝，
     任何地方都不叠第二层（叠加会出现同组行深浅不一）；选中/勾选态再往上走一档，
     与遮罩拉开区分。改配色只动这四个令牌即可。 */
  --row-hover: rgba(47, 91, 215, .05);
  --row-sel: rgba(47, 91, 215, .1);
  --row-sel-hover: rgba(47, 91, 215, .14);
  --row-sel-strong: rgba(47, 91, 215, .18);
  /* 强调渐变（用于主按钮、激活态、品牌卡） */
  --grad-blue: linear-gradient(135deg, #3a68e6 0%, #2a4fbe 100%);
  --grad-blue-hover: linear-gradient(135deg, #4773ec 0%, #2f5bd7 100%);

  /* 文字层级（石墨体系，比纯黑更柔和、更高级） */
  --label: #16181d;          /* 主文字 */
  --label-2: #565b66;        /* 次要文字 */
  --label-3: #838894;        /* 三级文字 / 表头 */
  --label-4: #a8adb8;        /* 占位符 */

  /* 背景与材质 */
  --bg: #f4f5f8;             /* 画布底色（微冷灰） */
  --surface: #ffffff;        /* 卡片 */
  --surface-2: #f8f9fc;      /* 次级填充 */
  --separator: rgba(22, 24, 29, .08);
  --separator-strong: rgba(22, 24, 29, .14);
  --fill: rgba(88, 94, 108, .12);
  --fill-2: rgba(88, 94, 108, .065);

  /* 侧栏配色：走国产 ERP（金蝶云星空 / K3）那一路的深色导航栏 ——
     深色底 + 半透明白字，选中项是实心强调色、左缘一条高亮竖条；
     圆角 6px（不是药丸），行高 38px，信息密度高一些，符合这类系统
     「一屏要看很多东西」的使用习惯。
     浮出菜单是独立的白底卡片，自带一套深色文字令牌，不受这里影响。
     整套就是下面这 9 个变量，换配色不必动任何选择器 —— 底部留了两套备选，
     整段替换即可。 */
  /* 当前：浅色 + 品牌蓝。强调色跟全站蓝调统一，不引入第二种品牌色；
     侧栏与内容区明度接近，所以 .sidebar 需要一条 border-right 把边界交代清楚。 */
  --nav-bg: #f4f6fa;                       /* 侧栏底：浅灰白 */
  --nav-ink: #3d4452;                      /* 常态文字 */
  --nav-ink-strong: #16181d;               /* 悬停文字 */
  --nav-hover: rgba(22, 24, 29, .055);     /* 悬停状态层 */
  --nav-sel: #dce8fd;                      /* 选中底：淡蓝 */
  --nav-sel-hover: #cfe0fc;
  --nav-sel-ink: #0b3b8c;                  /* 选中文字：深蓝 */
  --nav-bar: #2f6fed;                      /* 选中项左缘竖条：品牌蓝 */
  --nav-r: 6px;                            /* 小圆角，不用药丸 */
  /* 浮出菜单（白底卡片）里的选中态。单独开一组变量而不是直接复用外层的
     --nav-sel：外层换深色方案时（备选 A / B）那三个值会变成「实心色 + 白字」，
     而浮层永远是白底卡片，跟着变就会白底白字。默认与外层保持一致的淡蓝底。 */
  --nav-fly-sel: #dce8fd;
  --nav-fly-sel-hover: #cfe0fc;
  --nav-fly-sel-ink: #0b3b8c;
  /* 下面三套换色只需整段替换上面 8 个值；深色方案还要去掉 .sidebar 的 border-right。
     备选 A · 深蓝灰 + 品牌蓝（金蝶云星空那一路）：
       --nav-bg: #1f2a3d;  --nav-ink: rgba(255,255,255,.72);  --nav-ink-strong: #ffffff;
       --nav-hover: rgba(255,255,255,.09);
       --nav-sel: #2f6fed;  --nav-sel-hover: #3b79f0;
       --nav-sel-ink: #ffffff;  --nav-bar: #5a9bff;
     备选 B · 灰红（暖炭灰底 + 砖红选中）：
       --nav-bg: #2e2a2b;  --nav-ink: rgba(255,255,255,.74);  --nav-ink-strong: #ffffff;
       --nav-hover: rgba(255,255,255,.09);
       --nav-sel: #b53a30;  --nav-sel-hover: #c4453a;
       --nav-sel-ink: #ffffff;  --nav-bar: #ff8b7e;
     注意：选中态用压暗的砖红而不是正红 —— 正红（#cf2b2b，系统里的「预警/负数」色）
     放在侧栏会和表格里的红色数字抢读。
     备选 C · 用友路线（白底 + 用友红）：
       --nav-bg: #ffffff;  --nav-ink: #5a5f6a;  --nav-ink-strong: #1f2329;
       --nav-hover: rgba(230,51,58,.06);
       --nav-sel: #fdeced;  --nav-sel-hover: #fbdfe1;  --nav-sel-ink: #d9363e;
       --nav-bar: #e6333a; */

  /* 语义色（在系统色基础上压深，保证文字对比度） */
  --red: #cf2b2b;            /* 涨 / 正 / 预警（中国习惯：红=涨） */
  --red-soft: rgba(207, 43, 43, .09);
  --green: #1a8a52;          /* 跌 / 负 / 正常 */
  --green-soft: rgba(26, 138, 82, .1);
  --amber: #b26a00;
  --amber-soft: rgba(178, 106, 0, .11);
  /* 彩字墨色：语义色直接当文字色压在浅色底上只有 4.2:1，压暗一档到 6:1 以上，
     专供「柔和底 + 彩字」的轻量按钮（tonal）使用，不参与图表/标签配色 */
  --green-ink: #126b3e;
  --amber-ink: #8a5200;
  --red-ink: #a52222;
  --purple: #6d3fd0;

  /* 圆角 */
  --r-card: 18px;
  --r-field: 10px;
  --r-pill: 980px;

  /* 阴影：多层柔和投影 + 一丝蓝调，提升"大气"质感 */
  --shadow-card: 0 1px 2px rgba(22, 30, 60, .04), 0 8px 24px rgba(22, 30, 60, .05);
  --shadow-card-hover: 0 2px 6px rgba(22, 30, 60, .05), 0 16px 38px rgba(22, 30, 60, .09);
  --shadow-pop: 0 4px 12px rgba(22, 30, 60, .08), 0 32px 80px rgba(22, 30, 60, .24);
  --shadow-blue: 0 2px 8px rgba(47, 91, 215, .28);

  /* 动效曲线 */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --ease-spring: cubic-bezier(.32, .72, 0, 1);

  /* —— 兼容旧变量别名：app.js 的内联样式仍在引用 —— */
  --brand: var(--blue);
  --brand-dark: var(--blue-press);
  --brand-light: var(--blue-tint);
  --line: var(--separator);
  --text: var(--label);
  --text-2: var(--label-2);
}

/* ---------------- 基础 ---------------- */
* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
               "SF Pro Text", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB",
               "Microsoft YaHei UI", "Microsoft YaHei", system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.47;
  color: var(--label);
  background: var(--bg);
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* 细滚动条（Apple 悬浮式滚动条观感） */
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, .17);
  border: 3px solid transparent;
  border-radius: 8px;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, .3); background-clip: content-box; }

/* 选中文字用品牌色 */
::selection { background: rgba(47, 91, 215, .18); }

/* ---------------- 顶栏（毛玻璃） ---------------- */
/* 顶部铝价滚动显示屏（放在当前账期左侧，样式与账期胶囊一致） */
.aluminum-ticker { flex-shrink: 0; width: 360px; margin-right: 14px; font-size: 12.5px; font-weight: 600; color: var(--label-2); background: var(--fill-2); padding: 6px 13px; border-radius: var(--r-pill); white-space: nowrap; overflow: hidden; display: flex; align-items: center; }
.aluminum-ticker .at-scroll { display: inline-block; white-space: nowrap; will-change: transform; animation: at-marquee 60s linear infinite; }
.aluminum-ticker .at-scroll:hover { animation-play-state: paused; }
@keyframes at-marquee { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }

.topbar {
  height: 56px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 24px;
  z-index: 20;
  background: linear-gradient(180deg, rgba(255, 255, 255, .86) 0%, rgba(255, 255, 255, .72) 100%);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  backdrop-filter: saturate(180%) blur(22px);
  border-bottom: 1px solid var(--separator);
}
.topbar .logo {
  font-size: 15.5px;
  font-weight: 700;
  letter-spacing: -.25px;
  color: var(--label);
  display: flex;
  align-items: center;
  gap: 8px;
}
/* 品牌标记：星标 + WiseCore（与手机端一致）。星标用品牌蓝，不做方块底。 */
.topbar .logo-mark {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  color: #3C5DFF;
  flex-shrink: 0;
}
.topbar .logo-mark svg { width: 24px; height: 24px; display: block; }
.topbar .logo-brand {
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -.02em;
  color: #3C5DFF;
  font-family: "Arial Rounded MT Bold", "SF Pro Rounded", "Nunito", "Quicksand", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
.topbar .logo-sys {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .2px;
  color: var(--label);
}
.topbar .logo .logo-ver {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .5px;
  color: var(--blue);
  background: var(--blue-tint);
  padding: 3px 9px;
  border-radius: var(--r-pill);
}
.topbar .spacer { flex: 1; }
.topbar .period {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--label-2);
  background: var(--fill-2);
  padding: 6px 13px;
  border-radius: var(--r-pill);
  white-space: nowrap;
}

/* ---------------- 顶栏：当前登录用户 ----------------
   头像字 + 姓名的小胶囊，点开是「修改口令 / 退出登录」。
   还在用默认口令时右上角一个小红点 —— 提醒但不打断。 */
.userchip { position: relative; flex: none; }
.userchip-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 11px 0 5px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--fill-2);
  font-family: inherit;
  cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.userchip-btn:hover { background: var(--fill); }
.userchip-ava {
  width: 25px;
  height: 25px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--grad-blue);
  color: #fff;
  font-size: 12.5px;
  font-weight: 650;
  box-shadow: 0 1px 3px rgba(47, 91, 215, .35);
}
.userchip-name {
  font-size: 13px;
  font-weight: 550;
  color: var(--label);
  max-width: 108px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.userchip-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--red);
  flex: none;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .9);
}
.userchip-menu {
  display: none;
  position: absolute;
  top: calc(100% + 7px);
  right: 0;
  min-width: 190px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(16, 24, 40, .14);
  z-index: 60;
}
.userchip-menu.on { display: block; animation: chipIn .16s var(--ease); }
@keyframes chipIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }
.userchip-head { padding: 9px 11px 10px; border-bottom: 1px solid var(--separator); margin-bottom: 5px; }
.userchip-head b { display: block; font-size: 13.5px; font-weight: 650; color: var(--label); }
.userchip-head span { font-size: 11.5px; color: var(--label-3); }
.userchip-warn {
  margin: 0 3px 6px;
  padding: 7px 9px;
  border-radius: 8px;
  background: var(--red-soft);
  color: var(--red);
  font-size: 11.5px;
  line-height: 1.45;
}
.userchip-item {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-radius: 8px;
  background: none;
  font-family: inherit;
  font-size: 13px;
  color: var(--label);
  text-align: left;
  cursor: pointer;
  transition: background .12s var(--ease);
}
.userchip-item:hover { background: var(--fill-2); }
.userchip-item.danger { color: var(--red); }
.userchip-item.danger:hover { background: var(--red-soft); }
/* 打印时不要把用户菜单印出来 */
@media print { .userchip { display: none; } }

/* ---------------- 单据列表：批量操作 ----------------
   行首勾选框列 + 底部浮出的操作栏。只有购销单四类有（见 app.js 的 BATCH_TYPES）。 */
table.grid th.bt-ck,
table.grid td.bt-ck {
  width: 38px;
  padding-left: 12px;
  padding-right: 0;
  text-align: center;
}
table.grid .bt-ck input {
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: var(--blue);
  cursor: pointer;
  vertical-align: middle;
}
/* 选中行：淡蓝底 + 左缘一道竖条，和核销表的选中行同一套视觉语言 */
table.grid tbody tr.bt-on > td { background: rgba(47, 111, 237, .055); }
table.grid tbody tr.bt-on > td:first-child { box-shadow: inset 2px 0 0 var(--blue); }

/* 底部操作栏：没选中时整条滑下去收起，不留一条占视线的细边 */
.batch-bar {
  position: sticky;
  bottom: 0;
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 9px;
  /* ---- 收起时必须**不占任何高度** ----
     原来写的是 padding:11px 16px + margin-bottom:-58px，用「负外边距抵消自身
     高度」的写法把「这条栏在当前主题下正好多高」写死进了 CSS。
     实测这条栏是 51px，多减的 7px 把后面的分页条整个往上拽，
     正好压住列表底部的横向滚动条 —— 销售单列表 12 列放不下，
     横向滚动是看全列的唯一入口，被盖掉 8px 就几乎点不着。
     （这个偏差一直都在，只是以前批量栏后面没有别的元素，被卡片身高吸收掉了；
      加了分页条才暴露。）
     改成 height:0 + padding:0 + border:0：高度归零不依赖任何数字，
     主题、字号、按钮换行怎么变都不会再出现残余高度。 */
  height: 0;
  padding: 0 16px;
  margin-top: 0;
  border-top: 0 solid transparent;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .92) 0%, #fff 60%);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  backdrop-filter: saturate(180%) blur(16px);
  border-radius: 0 0 var(--r-card, 14px) var(--r-card, 14px);
  transition: opacity .18s var(--ease);
}
/* 选中之后撑回自身高度，并用一段关键帧补上「从下往上滑入」的手感 ——
   height:auto 参与不了 transition，滑入交给 transform 做。 */
.batch-bar.on {
  height: auto;
  padding: 11px 16px;
  margin-top: -1px;
  border-top-width: 1px;
  border-top-color: var(--separator-strong);
  overflow: visible;
  opacity: 1;
  pointer-events: auto;
  box-shadow: 0 -4px 16px rgba(16, 24, 40, .07);
  animation: batchIn .2s var(--ease);
}
@keyframes batchIn {
  from { transform: translateY(100%); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.batch-bar .spacer { flex: 1; }
.batch-count { font-size: 13px; color: var(--label-2); white-space: nowrap; }
.batch-count b {
  display: inline-block;
  min-width: 20px;
  padding: 1px 7px;
  margin-right: 3px;
  border-radius: var(--r-pill);
  background: var(--blue);
  color: #fff;
  font-size: 12.5px;
  font-weight: 650;
  text-align: center;
}
.batch-sep { width: 1px; height: 20px; background: var(--separator-strong); margin: 0 2px; }
/* 批量栏里的三个动作按钮要同一档视觉重量。
   .btn-warning 在单据动作区是 tonal（柔和底 + 彩字、无投影），因为那一排里
   已经有一个实心主按钮了，再实心就互相抢。但批量栏不同 ——
   审核(primary) / 反审核 / 删除(danger) 是三个并列的批量动作，没有主次，
   tonal 的反审核夹在两个实心按钮中间会显得「不是一类东西」。
   所以在这条栏里把它提到实心 + 同款彩色投影。

   ⚠ 底色**不能**直接用 --amber(#b26a00)：那个令牌是为「浅底上的深彩字」
   压深过的，拿来做实心底会发褐、像生锈，跟旁边明快的蓝和红不是一个调子。
   但也不能太亮 —— 白字压在亮橙(#f0a020)上只有 2.4:1，三颗按钮摆一起时
   反审核的字明显比蓝、红两颗糊（实测截图可见）。
   取中间档 #e08a14 → #cc7a08：色相仍是明快的琥珀橙，白字对比度约 3.4:1，
   配合 600 字重与蓝(#2f5bd7)、红(#cf2b2b)那两颗读起来一样清楚。 */
.batch-bar .btn-warning {
  background: linear-gradient(135deg, #e08a14 0%, #cc7a08 100%);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 2px 8px rgba(204, 122, 8, .28);
}
.batch-bar .btn-warning:hover:not(:disabled) {
  background: linear-gradient(135deg, #ea9420 0%, #d8830e 100%);
  border-color: transparent;
  box-shadow: 0 4px 12px rgba(204, 122, 8, .36);
}
.batch-bar .btn-warning:active:not(:disabled) { background: #b76c05; }

/* ---------------- 单据列表分页条 ----------------
   列表改成服务端分页（每页 100 张）之后的分页控件。
   与 .batch-bar 不同，它**常驻可见**、不做滑出动画 —— 翻页是浏览的常规动作，
   不是「选中之后才出现」的临时状态。用普通流式布局而不是 sticky：
   .batch-bar 已经占了 bottom:0，两条都 sticky 会叠在一起。
   放在 .card-body 之后、卡片内部，跟随卡片圆角收边。 */
.page-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-top: 1px solid var(--separator);
  background: var(--surface);
  border-radius: 0 0 var(--r-card) var(--r-card);
}
.page-bar .spacer { flex: 1; }
.page-info { font-size: 12.5px; color: var(--label-2); white-space: nowrap; }
.page-info b {
  color: var(--label);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  margin: 0 2px;
}
.page-cur {
  min-width: 66px;
  text-align: center;
  font-size: 12.5px;
  color: var(--label-2);
  font-variant-numeric: tabular-nums;
}
/* 打印时不要把操作栏印出来 */
@media print { .batch-bar { display: none; } }

/* 首页版本标记：固定在看板内容底部，便于后续发布时识别版本。 */
.dashboard-version {
  padding: 14px 0 4px;
  color: var(--label-4);
  font-size: 11px;
  line-height: 1;
  letter-spacing: .4px;
  text-align: center;
  user-select: none;
}
/* 备案号：继承版本号那行的灰色小字，链接不带下划线，鼠标划过变蓝 */
.dashboard-version a {
  color: inherit;
  text-decoration: none;
  user-select: text;
}
.dashboard-version a:hover { color: var(--blue); }

/* ---------------- 布局 ---------------- */
.layout { flex: 1; display: flex; min-height: 0; }

/* ---------------- 侧边导航 ---------------- */
.sidebar {
  /* 分组收起后每行最多「四个字 + 图标 + 箭头」，180px 足够，
     省下的宽度全给内容区（单据明细表最吃宽度） */
  width: 180px;
  flex-shrink: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  /* 分组收起后只剩七行，不需要滚动；必须是 visible，否则 overflow-y:auto
     会连带把横向也裁掉，右侧浮出的下级菜单会被切掉 */
  overflow: visible;
  /* 底部留白：这块空间决定「核算引擎」那块离屏幕底有多远。
     原来是 36px，视觉上悬得太高；收到 14px，让它贴近底边。
     注意别调成 0 —— 贴死底边反而显得挤，也会和窗口圆角打架。 */
  padding: 10px 8px 14px;
  background: var(--nav-bg);
  /* 浅色侧栏与内容区的明度差很小，补一条分隔线把边界交代清楚。
     换成深色方案（备选 A / B）时这一行要去掉——深浅差本身就够分了。 */
  border-right: 1px solid var(--separator);
}
.nav-group { margin-bottom: 2px; }

/* 左下角核算引擎状态：灯色对应当前模式。
   智能模式使用偏新能源蓝的冷色发光；全量为绿色；增量为黄色。 */
.engine-mode-box {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: auto 4px 0;
  padding: 8px 10px;
  min-height: 34px;
  border: 1px solid var(--separator);
  border-radius: 8px;
  background: rgba(255,255,255,.72);
  box-shadow: 0 2px 8px rgba(34, 47, 73, .05);
  cursor: default;
}
.engine-mode-light {
  width: 9px;
  height: 9px;
  flex: 0 0 9px;
  border-radius: 50%;
  background: #2389ff;
  box-shadow: 0 0 0 3px rgba(35,137,255,.13), 0 0 10px rgba(35,137,255,.82);
  animation: engineLightBreathBlue 2.2s ease-in-out infinite;
}
@keyframes engineLightBreathBlue {
  0%, 100% { box-shadow: 0 0 0 3px rgba(35,137,255,.13), 0 0 8px rgba(35,137,255,.58); }
  50% { box-shadow: 0 0 0 5px rgba(35,137,255,.2), 0 0 18px rgba(35,137,255,.95); }
}
@keyframes engineLightBreathGreen {
  0%, 100% { box-shadow: 0 0 0 3px rgba(37,180,107,.13), 0 0 8px rgba(37,180,107,.56); }
  50% { box-shadow: 0 0 0 5px rgba(37,180,107,.2), 0 0 18px rgba(37,180,107,.9); }
}
@keyframes engineLightBreathYellow {
  0%, 100% { box-shadow: 0 0 0 3px rgba(229,173,34,.15), 0 0 8px rgba(229,173,34,.52); }
  50% { box-shadow: 0 0 0 5px rgba(229,173,34,.22), 0 0 18px rgba(229,173,34,.88); }
}
.engine-mode-copy {
  display: flex;
  align-items: baseline;
  min-width: 0;
  line-height: 1.15;
  white-space: nowrap;
}
.engine-mode-caption {
  color: var(--label-4);
  font-size: 11px;
  letter-spacing: .2px;
}
.engine-mode-copy strong {
  color: var(--label);
  font-size: 12px;
  font-weight: 700;
}
.engine-mode-green .engine-mode-light {
  background: #25b46b;
  box-shadow: 0 0 0 3px rgba(37,180,107,.13), 0 0 10px rgba(37,180,107,.78);
  animation-name: engineLightBreathGreen;
}
.engine-mode-yellow .engine-mode-light {
  background: #e5ad22;
  box-shadow: 0 0 0 3px rgba(229,173,34,.15), 0 0 10px rgba(229,173,34,.72);
  animation-name: engineLightBreathYellow;
}
.nav-title {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--label-4);
  padding: 20px 12px 7px;
}

/* ---- 分组悬浮菜单：标题即按钮，悬停时下级项目从右侧浮出 ---- */
.nav-group.has-flyout { position: relative; outline: none; }
.nav-group.has-flyout > .nav-title {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 38px;
  padding: 0 12px;
  margin-bottom: 10px;
  border-radius: var(--nav-r);
  /* 覆盖上面那套「小号全大写分组标签」的排版：现在它是一个可点的导航行 */
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--nav-ink);
  cursor: default;
  user-select: none;
  position: relative;
  overflow: hidden;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.nav-group.has-flyout:hover > .nav-title,
.nav-group.has-flyout:focus-within > .nav-title { background: var(--nav-hover); color: var(--nav-ink-strong); }
/* 当前所在页面在哪个分组里，分组标题就带上选中底 —— 下级收起后仍然知道自己在哪 */
.nav-group.has-flyout.active > .nav-title {
  background: var(--nav-sel);
  color: var(--nav-sel-ink);
  font-weight: 600;
}
.nav-group.has-flyout.active > .nav-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--nav-bar);
}
.nav-caret { width: 14px; height: 14px; margin-left: auto; opacity: .45; flex-shrink: 0; }
.nav-group.has-flyout:hover .nav-caret { opacity: .8; }

.nav-flyout {
  position: absolute;
  /* 16px 而不是 7px：.nav-group 的宽度是侧栏「内容区」宽（已扣掉 9px 内边距），
     所以 100% 还落在侧栏里面，偏移量必须盖过那段内边距和 1px 边框，
     浮层才真正浮在侧栏之外，而不是压在分隔线上。 */
  left: calc(100% + 16px);
  top: -6px;
  z-index: 60;
  min-width: 196px;
  padding: 8px;
  background: var(--surface);
  border: none;
  /* Material 的高程用阴影表达，不画边框 */
  border-radius: 14px;
  box-shadow: 0 1px 3px rgba(31, 31, 31, .12), 0 8px 24px rgba(31, 31, 31, .14);
  opacity: 0;
  visibility: hidden;
  transform: translateX(-4px);
  transition: opacity .14s var(--ease), transform .14s var(--ease), visibility .14s;
}
.nav-group.has-flyout:hover > .nav-flyout,
.nav-group.has-flyout:focus-within > .nav-flyout {
  opacity: 1;
  visibility: visible;
  transform: none;
}
/* 触发区与浮层之间有 7px 缝隙，鼠标斜着移过去会从缝里「掉出来」导致菜单收起。
   补一条同高的透明桥把缝盖住。 */
.nav-flyout::before {
  content: '';
  position: absolute;
  left: -20px;
  top: 0;
  width: 20px;
  height: 100%;
}
/* 侧栏底部的资金管理 / 会计：二级菜单向上展开，避免被浏览器底部挡住。 */
.nav-group-finance > .nav-flyout,
.nav-group-accounting > .nav-flyout {
  top: auto;
  bottom: -6px;
}
.nav-flyout-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .3px;
  color: var(--label-4);
  padding: 4px 12px 8px;
}
/* 浮层是独立的白底卡片，文字自带一套令牌 —— 这样侧栏换成深色方案时，
   浮层不会跟着变成白底白字（曾经就是这么坏掉的）。 */
.nav-flyout .nav-item { margin-bottom: 0; color: var(--label-2); }
.nav-flyout .nav-item + .nav-item { margin-top: 6px; }
.nav-flyout .nav-item:hover { background: var(--fill-2); color: var(--label); }
/* 浮层的选中态走自己那组变量（默认与外层同样的淡蓝底 + 深蓝字）。
   不能直接复用 --nav-sel：外层换深色方案时它会变成「实心色 + 白字」，
   而浮层永远是白底卡片，跟着变就成了白底白字 —— 之前正是这么坏掉的。
   左缘竖条用 --nav-bar，淡蓝底上蓝条看得清。 */
.nav-flyout .nav-item.active {
  background: var(--nav-fly-sel);
  color: var(--nav-fly-sel-ink);
}
.nav-flyout .nav-item.active:hover { background: var(--nav-fly-sel-hover); }
/* 导航行：深色侧栏上的一行，选中即实心品牌蓝 + 左缘高亮竖条。 */
.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 38px;
  padding: 0 12px;
  margin-bottom: 10px;
  border-radius: var(--nav-r);
  font-size: 13px;
  font-weight: 500;
  color: var(--nav-ink);
  cursor: pointer;
  user-select: none;
  position: relative;
  overflow: hidden;
  transition: background .15s var(--ease), color .15s var(--ease);
}
/* 图标尺寸不能只挂在 .nav-item 下：分组标题行不是 .nav-item，
   漏掉它图标就会退回 SVG 的默认尺寸，把标题挤成两行。 */
.nav-item .nav-icon,
.nav-title .nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  opacity: .78;
  transition: opacity .15s var(--ease);
}
.nav-item > span,
.nav-title > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-item:hover { background: var(--nav-hover); color: var(--nav-ink-strong); }
.nav-item:hover .nav-icon { opacity: 1; }
.nav-item.active {
  background: var(--nav-sel);
  color: var(--nav-sel-ink);
  font-weight: 600;
  box-shadow: none;
}
/* 左缘高亮竖条：国产 ERP 侧栏的标志性细节，让「当前在哪」在余光里也成立 */
.nav-item.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--nav-bar);
}
.nav-item.active .nav-icon { opacity: 1; }
.nav-item.active:hover { background: var(--nav-sel-hover); }

/* ---------------- 主内容区 ---------------- */
.main {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 28px 34px 80px;
  scroll-behavior: smooth;
}

/* 页面大标题 */
.page-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}
.page-head h2 {
  position: relative;
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -.6px;
  color: var(--label);
  padding-left: 15px;
}
/* 标题左侧靛蓝竖条：给页面一个沉稳的视觉起点 */
.page-head h2::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 22px;
  border-radius: 2px;
  background: var(--grad-blue);
}

/* ---------------- 按钮（胶囊形） ----------------
   统一高度：所有按钮同一行内高度必须一致（用固定 height 而非 padding 撑，
   避免 .btn-sm / .btn-quiet 各自 padding 不同导致"并排按钮参差不齐"）。
   —— --btn-h 为大号（默认 34px），--btn-h-sm 为小号（28px）。 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  height: 34px;
  min-height: 34px;
  padding: 0 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--separator-strong);
  background: var(--surface);
  color: var(--label);
  cursor: pointer;
  white-space: nowrap;
  box-sizing: border-box;
  transition: background .18s var(--ease), border-color .18s var(--ease),
              transform .12s var(--ease), box-shadow .18s var(--ease);
}
.btn svg { width: 14px; height: 14px; flex-shrink: 0; }
/* hover / active 一律只对**可用**的按钮生效。
   原来是裸 :hover，于是禁用的按钮鼠标放上去照样换底色 —— 实心的主按钮和
   删除按钮会从蓝（红）变成灰白，看着像按钮坏了，而不是「不能点」。 */
.btn:hover:not(:disabled) { background: var(--surface-2); border-color: rgba(22, 24, 29, .22); }
.btn:active:not(:disabled) { transform: scale(.97); }
/* 按钮点击反馈：轻微压下并回弹，避免生硬切换；不改变按钮尺寸和布局 */
.btn:not(:disabled) { will-change: transform, box-shadow, background-color; }
.btn:not(:disabled):active { transform: translateY(1px) scale(.965); transition-duration: .06s; }
.btn:not(:disabled):focus-visible { transform: translateY(-1px); }

/* 单据动作区（开单页与收付款单共用同一套 .bill-bar 按钮）：
   整体比页面里的按钮小一档 —— 这是一条常驻顶部的工具条，不是页面主内容，
   34px 的通用尺寸排四五颗下来过于抢眼。降到 30px / 12.5px 字。 */
.bill-bar .btn {
  height: 30px;
  min-height: 30px;
  padding: 0 13px;
  font-size: 12.5px;
}
.bill-bar .btn svg { width: 13px; height: 13px; }
/* 状态切换时短暂浮入；并且所有动作按钮等宽 ——「保存并审核 ↔ 审核」
   「保存 ↔ 编辑」随单据状态互换时宽度不跳动，整条动作区落点稳定，
   连点两下不会误点到旁边那颗。 */
.bill-bar .btn.bill-state-in {
  min-width: 86px;
  padding: 0 12px;
  position: relative;
  animation: billButtonStateIn .22s var(--ease-spring) both;
}
/* 「新增」是开另一张单的中性动作，和右侧「提交/状态」动作不是一类，
   用「间距 + 一道浅竖线」分组。竖线两侧各留 8px：主按钮的蓝色投影扩散约 8px，
   贴太近会把 1px 细线糊掉，只在「反审核」这种无投影的状态才看得见。 */
.bill-bar #billNew,
.bill-bar #settleNew { margin-right: 8px; }
.bill-bar .bar-sep {
  flex: none;
  width: 1px;
  height: 16px;
  margin-right: 8px;
  background: var(--separator-strong);
  border-radius: 1px;
}
.bill-bar .tag.bill-state-in {
  animation: billTagStateIn .24s var(--ease-spring) both;
}

/* 主按钮：靛蓝渐变 + 蓝调投影，是整个界面的视觉重心 */
.btn-primary {
  background: var(--grad-blue);
  border-color: transparent;
  color: #fff;
  box-shadow: var(--shadow-blue);
}
.btn-primary:hover:not(:disabled) {
  background: var(--grad-blue-hover);
  border-color: transparent;
  box-shadow: 0 4px 14px rgba(47, 91, 215, .34);
}
.btn-primary:active:not(:disabled) { background: var(--blue-press); box-shadow: 0 1px 3px rgba(47, 91, 215, .3); }

/* 语义动作按钮（tonal）：柔和色底 + 深色彩字，没有实心块和投影。
   一组动作里只允许一个实心彩色按钮——即承担提交语义的 .btn-primary；
   保存 / 编辑 / 反审核 如果各自实心，单据动作区就是四颗彩球互相抢视觉，
   用户反而看不出该点哪个。tonal 保住语义色，又明确低主按钮一档。
   .btn-success / .btn-warning 目前只用在单据动作区（开单页 + 收付款单）。 */
.btn-success,
.btn-warning,
.bill-bar .btn-danger {
  border-color: transparent;
  box-shadow: none;
  font-weight: 600;
}
/* 底色比同名的 --*-soft 标签底略重：标签躺在白卡上，按钮躺在动作区的灰玻璃上，
   用标签那档浓度会糊进背景里看不出是个按钮。
   hover / active 一律加 :not(:disabled) —— 否则禁用态的按钮鼠标放上去还会变色，
   看着像能点。禁用态只保留一层 38% 透明（见 .btn:disabled）。 */
.btn-success { background: rgba(26, 138, 82, .13); color: var(--green-ink); }
.btn-success:hover:not(:disabled) { background: rgba(26, 138, 82, .19); border-color: transparent; }
.btn-success:active:not(:disabled) { background: rgba(26, 138, 82, .25); }
.btn-warning { background: rgba(178, 106, 0, .14); color: var(--amber-ink); }
.btn-warning:hover:not(:disabled) { background: rgba(178, 106, 0, .2); border-color: transparent; }
.btn-warning:active:not(:disabled) { background: rgba(178, 106, 0, .27); }
/* 列表里的「删除」仍是实心红：那是一次性破坏操作，需要最强的视觉刹车 */
.btn-danger { background: var(--red); border-color: var(--red); color: #fff; box-shadow: 0 2px 8px rgba(207, 43, 43, .22); }
.btn-danger:hover:not(:disabled) { background: #ad2424; border-color: #ad2424; box-shadow: 0 4px 12px rgba(207, 43, 43, .3); }
.btn-danger:active:not(:disabled) { background: #8d1d1d; }
/* 单据动作区的「反审核」是可逆的状态回退，不是删除，故降到 tonal */
.bill-bar .btn-danger { background: rgba(207, 43, 43, .12); color: var(--red-ink); }
.bill-bar .btn-danger:hover:not(:disabled) { background: rgba(207, 43, 43, .18); border-color: transparent; box-shadow: none; }
.bill-bar .btn-danger:active:not(:disabled) { background: rgba(207, 43, 43, .24); }
/* 单据动作区的「删除」：白底 + 红边 + 红字。同处一条动作区里，tonal 的「反审核」
   和实心红都不合适 —— 前者弱到和其它按钮分不开，后者抢过主操作「保存 / 审核」。
   描边是唯一能「被一眼挑出来又不抢主次」的档位，配合排在最左侧降低误触。
   边框压到 45% 透明度、字重回到常规 500：满边框红 + 600 字重两样叠在一起，
   在一排无边框按钮里重得像主操作，反而把「保存 / 审核」压下去了。
   悬停才补到实色红 —— 真要点的时候才需要那一下确认感。 */
.btn-danger-ghost {
  background: var(--surface);
  border-color: rgba(207, 43, 43, .45);
  color: var(--red-ink);
  box-shadow: none;
}
.btn-danger-ghost:hover:not(:disabled) { background: var(--red-soft); border-color: var(--red); color: var(--red-ink); box-shadow: none; }
.btn-danger-ghost:active:not(:disabled) { background: rgba(207, 43, 43, .16); }

/* 顶栏上的低调按钮（仅换底色/字号，高度与 .btn 一致） */
.btn-quiet {
  background: var(--fill-2);
  border-color: transparent;
  color: var(--label);
  font-size: 12.5px;
  padding: 0 14px;
}
.btn-quiet:hover:not(:disabled) { background: var(--fill); border-color: transparent; }

.btn-sm { height: 28px; min-height: 28px; padding: 0 13px; font-size: 12.5px; }
.btn-sm svg { width: 13px; height: 13px; }
/* 禁用态：整体压到 38% 透明，保留各自的底色，但**去掉阴影**。
   彩色投影是「这个按钮突出、可以点」的信号，禁用了还浮着就自相矛盾。
   **不能在 :hover 里改 background** —— 原来写的是 `background: var(--surface)`，
   对白底按钮没事，可一旦是实心的主按钮 / 删除按钮，鼠标一放上去就从蓝（红）
   变成白底，看着像按钮坏了。禁用态本来就该「鼠标放上去毫无反应」，
   所以这里只锁住变换和阴影，颜色一个都不动。 */
.btn:disabled {
  opacity: .38;
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:hover,
.btn:disabled:active,
.btn:disabled:focus-visible {
  transform: none;
  filter: none;
  box-shadow: none;
}

/* 键盘可达性：靛蓝聚焦环 */
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px rgba(47, 91, 215, .18);
}

/* ---------------- 卡片 ---------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  margin-bottom: 22px;
  /* clip 而不是 hidden：两者都裁圆角，但 hidden 会创建滚动容器，
     使里面的 position:sticky 表头被截断、跟着卡片一起滚走。
     用 clip 之后表头可以直接相对页面吸顶，不必再在表格里套一层滚动条
     —— 内层滚动条会从表格宽度里吃掉约 11px，把最右一列压窄，
     还会在灰色表头右侧留下一条对不上色的缝。 */
  overflow: clip;
  animation: riseIn .34s var(--ease) both;
}
.card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 22px;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -.2px;
  color: var(--label);
  background: linear-gradient(180deg, rgba(248, 249, 252, .7) 0%, rgba(255, 255, 255, 0) 100%);
  border-bottom: 1px solid var(--separator);
}
.card-head h3 { font-size: 15px; font-weight: 650; letter-spacing: -.2px; }
.card-head .spacer { flex: 1; }
.card-body { padding: 18px 22px; }

/* ---------------- 指标卡 ---------------- */
/* 固定 4 列：8 张指标卡正好 2×4，避免 auto-fit 出现 7+1 的孤行 */
.metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 22px;
}

/* ---------------- 首页看板图表 ----------------
   零依赖约束下三张图都是内联 SVG 手拼（见 app.js 的 barChartHtml / donutChartHtml /
   rankBarHtml）。布局：第一行一张宽图（趋势），第二行两张窄图并排。 */
.dash-charts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 22px;
}
.chart-card .card-head { display: flex; align-items: center; gap: 10px; }
/* 上限兜底：超宽屏下两列还是可能把单张卡撑过 560px，SVG 会等比变高。
   给个高度上限，letterbox 的留白比「一张图占半屏高」好接受。 */
svg.chart { max-height: 230px; }
.chart-card .card-body { padding: 14px 16px 10px; }
.chart-legend {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  color: var(--label-3);
}
.chart-note { margin-left: 8px; color: var(--label-2); }
/* 整条折线共用一个颜色（今日 vs 昨日），所以这里要把差额写出来 ——
   不然只看到一条红线或绿线，不知道跟谁比、差多少 */
.chart-trend { font-weight: 600; font-variant-numeric: tabular-nums; }
.chart-trend.up { color: var(--red); }
.chart-trend.down { color: var(--green); }
.lg-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.chart-legend .lg-dot + .lg-dot { margin-left: 6px; }

/* 柱状图：宽度随容器缩放，高度由 viewBox 比例决定 */
svg.chart { display: block; width: 100%; height: auto; }
.bar-rev { fill: var(--blue); }
.bar-cost { fill: var(--amber); }
.col-bar { fill: var(--blue); }
.bar-rev:hover, .bar-cost:hover, .col-bar:hover { opacity: .82; }
.bar-axis { stroke: var(--separator-strong); stroke-width: 1; }
.bar-val {
  fill: var(--label-2);
  font-size: 11px;
  font-weight: 600;
  text-anchor: middle;
  font-variant-numeric: tabular-nums;
}
.bar-lab { fill: var(--label-3); font-size: 11.5px; text-anchor: middle; }

/* 折线图：逐段着色（红涨绿跌，中国习惯）+ 线下同色淡面积 + 实心圆点。
   整条线一个颜色的话，「哪天掉下来的」要靠眼睛沿着线找；逐段上色一眼就能扫出来。
   面积只负责交代量级，透明度压到 10% 左右，不抢线本身。 */
.line-seg {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
}
.line-area { stroke: none; }
.line-dot { stroke: var(--surface); stroke-width: 2; }
.line-dot:hover { r: 5; }

/* 不跌即红（涨和持平都算红），真下滑才绿 —— 与股票软件一致。
   .line-flat 已不再产生，保留规则只为万一有旧标记残留时不至于没有颜色。 */
.line-up { stroke: var(--red); fill: var(--red); }
.line-down { stroke: var(--green); fill: var(--green); }
.line-flat { stroke: var(--label-3); fill: var(--label-3); }
.line-area-up { fill: rgba(207, 43, 43, .11); }
.line-area-down { fill: rgba(26, 138, 82, .11); }
.line-area-flat { fill: rgba(88, 94, 108, .08); }

/* 环形图 + 图例：图在左、图例在右，图例是真正要读的那部分 */
.donut-wrap { display: flex; align-items: center; gap: 18px; }
svg.donut { width: 140px; height: 140px; flex-shrink: 0; }
.donut-seg { transition: opacity .15s var(--ease); }
.donut-seg:hover { opacity: .78; }
/* 底环：所有账户都透支时环里一段都没有，靠这圈浅灰托住中心的数字 */
.donut-track { stroke: rgba(0, 0, 0, .06); }
.donut-center {
  fill: var(--label);
  font-size: 17px;
  font-weight: 700;
  text-anchor: middle;
  font-variant-numeric: tabular-nums;
}
.donut-legend { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.lg-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.lg-name { color: var(--label-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lg-pct { margin-left: auto; color: var(--label-3); font-variant-numeric: tabular-nums; }
.lg-val {
  width: 86px;
  text-align: right;
  color: var(--label);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* 透支账户：不占环形份额，图例里用空心灰点和红字区分出来 */
.lg-dot-neg { background: transparent; box-shadow: inset 0 0 0 2px var(--label-3); }
.lg-val.lg-neg { color: var(--red); }

/* 横向条形榜：名称定宽 + 条 + 数值，三列对齐才扫得快 */
.rank { display: flex; flex-direction: column; gap: 10px; }
.rank-row { display: flex; align-items: center; gap: 10px; font-size: 12.5px; }
.rank-name {
  width: 108px;
  flex-shrink: 0;
  color: var(--label-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-track { flex: 1; min-width: 0; height: 9px; border-radius: 5px; background: var(--fill-2); }
.rank-fill { display: block; height: 100%; border-radius: 5px; background: var(--grad-blue); }
.rank-val {
  width: 86px;
  text-align: right;
  color: var(--label);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 1100px) {
  .dash-charts { grid-template-columns: minmax(0, 1fr); }
  .donut-wrap { gap: 12px; }
}
.metric {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: 16px;
  padding: 18px 20px 19px;
  box-shadow: var(--shadow-card);
  transition: transform .25s var(--ease), box-shadow .25s var(--ease), border-color .25s var(--ease);
  animation: riseIn .34s var(--ease) both;
}
/* 左上角一抹语义色柔光：让指标卡不呆板，同时暗示数值语义 */
.metric::after {
  content: '';
  position: absolute;
  top: -30px;
  right: -30px;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: var(--metric-tint, transparent);
  pointer-events: none;
}
.metric:hover { transform: translateY(-3px); box-shadow: var(--shadow-card-hover); border-color: transparent; }
.metric .k {
  position: relative;
  z-index: 1;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--label-2);
  margin-bottom: 9px;
  letter-spacing: -.05px;
}
.metric .v {
  position: relative;
  z-index: 1;
  font-size: 26px;
  font-weight: 650;
  letter-spacing: -.7px;
  color: var(--label);
  font-variant-numeric: tabular-nums;
}
.metric .u {
  font-size: 13px;
  font-weight: 500;
  color: var(--label-3);
  letter-spacing: 0;
  margin-left: 4px;
}
.metric.blue { --metric-tint: var(--blue-soft); }
.metric.red { --metric-tint: var(--red-soft); }
.metric.green { --metric-tint: var(--green-soft); }
.metric.amber { --metric-tint: var(--amber-soft); }
.metric.blue .v { color: var(--blue); }
.metric.red .v { color: var(--red); }
.metric.green .v { color: var(--green); }
.metric.amber .v { color: var(--amber); }

/* ---------------- 资金管理 ---------------- */
.cash-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(184px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.cash-cards .card.total {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, var(--green), #1a9f5d);
  color: #fff;
  border: none;
  border-radius: 16px;
  padding: 15px 18px;
  box-shadow: var(--shadow-card);
  animation: riseIn .34s var(--ease) both;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease);
}
.cash-cards .card.total:hover { transform: translateY(-2px); box-shadow: var(--shadow-card-hover); }
/* 可双击看流水的账户卡：抬起 + 外发光。
   发光用 box-shadow，不另加发光层 —— 卡片是 overflow:hidden（要裁水印），
   放在里面的发光元素会被一起裁掉；box-shadow 画在边框盒外面，不受裁剪。
   颜色取卡片自己的品牌色（--brand2），所以招行卡发红光、微信卡发绿光，
   发的是「这张卡自己的光」，而不是统一一层灰影。 */
.cash-cards .card.total.clickable-card {
  cursor: pointer;
  --glow: rgba(26, 159, 93, .55);          /* 认不出银行时的默认绿 */
  /* 慢一点、不回弹：苹果那种是顺滑地浮起来，弹一下属于另一种语气 */
  transition:
    transform .45s cubic-bezier(.22, .61, .36, 1),
    box-shadow .45s cubic-bezier(.22, .61, .36, 1),
    filter .45s cubic-bezier(.22, .61, .36, 1);
}
.cash-cards .card.total.clickable-card.brand { --glow: var(--brand2); }
.cash-cards .card.total.clickable-card:hover {
  transform: translateY(-6px) scale(1.025);
  /* 第一层**没有 Y 偏移**，这是「发光」和「阴影」的分界：有偏移只亮下边，
     那是投影；四面均匀地晕开才是光。后两层带偏移，负责把卡托起来的纵深。
     一律大 blur 配负 spread —— 单层实色只会糊成一块生硬的色斑。 */
  box-shadow:
    0 0 28px -2px var(--glow),
    0 14px 30px -12px var(--glow),
    0 30px 62px -20px var(--glow);
  filter: brightness(1.05) saturate(1.04);
}
/* 按下时压回去。没有按下反馈，双击就只是「什么都没发生，然后弹窗出现」——
   中间那一下缺了，用户不确定自己到底点中了没有。必须是瞬时的，拖一下就不像「按」。 */
.cash-cards .card.total.clickable-card:active {
  transform: translateY(-2px) scale(.995);
  transition-duration: .09s;
}
/* 双击打开后给一下「收到了」的回弹（JS 加 .card-pulse，动画结束即移除）。
   收尾停在 hover 的位置上 —— 鼠标还在卡上，停回原位会突然掉下去一截。 */
.cash-cards .card.total.clickable-card.card-pulse { animation: cardPulse .44s var(--ease); }
@keyframes cardPulse {
  0% { transform: translateY(-2px) scale(.985); }
  52% { transform: translateY(-9px) scale(1.04); }
  100% { transform: translateY(-6px) scale(1.025); }
}
/* 总账户卡：金色卡面 + 元宝水印 + 金额居中放大。
   旁边每张分支账户卡都是自家银行的品牌色，总账户要是再用蓝色就跟它们一个量级了 ——
   金色把「这张是合计、其余是明细」直接讲清楚，不必靠位置去猜。 */
.cash-cards .card.total.primary {
  /* 两层：上层是一道斜向的高光带（金属反光），下层是金色本体。
     单层渐变只能做出「土黄」—— 金属的辨识点不是颜色，是那条亮带。 */
  background:
    linear-gradient(115deg, rgba(255, 255, 255, 0) 30%, rgba(255, 252, 232, .5) 43%,
      rgba(255, 255, 255, .14) 49%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(135deg, #f0cb63 0%, #dca93a 28%, #c9942a 56%, #a06d16 100%);
  /* 上缘一道亮边 + 下缘一道暗边 = 倒角；外面两层暖色辉光让它从背景里浮起来 */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .55),
    inset 0 -1px 0 rgba(110, 72, 8, .3),
    0 2px 10px rgba(176, 122, 26, .38),
    0 14px 34px rgba(176, 122, 26, .26);
  /* 居中：合计是整屏唯一要一眼读到的数，左对齐会被旁边一排明细卡的节奏吃掉 */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}
/* 右上角的暖色柔光：硬边圆片换成径向渐变，边界不再是一道看得见的弧线 */
.cash-cards .card.total.primary::before {
  content: '';
  position: absolute;
  top: -58px;
  right: -40px;
  width: 170px;
  height: 170px;
  background: radial-gradient(circle, rgba(255, 251, 226, .52) 0%, rgba(255, 251, 226, 0) 68%);
  pointer-events: none;
}
.cash-cards .card.total.primary .card-label,
.cash-cards .card.total.primary .card-value { position: relative; z-index: 2; }
/* 比分支账户的 24px 大一号。tabular-nums 已在 .card-value 上，换行长度不会跳 */
.cash-cards .card.total.primary .card-value {
  font-size: 30px;
  letter-spacing: -.8px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .18);
}
/* 铜钱水印：位置与银行 logo 水印同一档（右下、白剪影、低透明度），
   只是尺寸放大一点 —— 它没有品牌字标那么多细节，小了就看不出是什么。 */
.cash-cards .card.total.primary .coin-wm {
  position: absolute;
  right: 6px;
  bottom: 4px;
  z-index: 1;
  width: 72px;
  height: 72px;
  fill: #fff;
  opacity: .2;
  pointer-events: none;
  user-select: none;
}
/* 星芒：位置和大小写在各自的 style 上（见 goldSparkles），这里只管共性。
   比铜钱水印亮得多是故意的 —— .2 那一档和底色糊在一起，等于没加。 */
.cash-cards .card.total.primary .shine-wm {
  position: absolute;
  z-index: 1;
  fill: #fffbe6;
  pointer-events: none;
  user-select: none;
}
.cash-cards .card-label {
  font-size: 12px;
  font-weight: 500;
  opacity: .85;
  margin-bottom: 6px;
  letter-spacing: -.05px;
}
.cash-cards .card-value {
  font-size: 24px;
  font-weight: 650;
  letter-spacing: -.5px;
  font-variant-numeric: tabular-nums;
}

/* 品牌主题卡片（钱包式）：品牌色渐变卡面 + 顶部「logo 圆片 + 品牌名」+ 右下浅水印。
   水印统一为 logo 转白低透明度剪影（单色标/彩色官方标一致处理，不再用白底圆牌）；
   其余银行首字徽标 + 首字大水印（建行/建/建设银行等别名均可识别）。 */
.cash-cards .card.total.brand {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, var(--brand1), var(--brand2));
}
/* 柔光装饰圆：提亮卡面层次 */
.cash-cards .card.total.brand::before {
  content: '';
  position: absolute;
  top: -42px;
  left: -36px;
  width: 130px;
  height: 130px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  pointer-events: none;
}
.cash-cards .card.total.brand::after {
  content: '';
  position: absolute;
  right: -54px;
  top: -64px;
  width: 170px;
  height: 170px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  pointer-events: none;
}
.cash-cards .card.brand .card-label,
.cash-cards .card.brand .card-value {
  position: relative;
  z-index: 2;
}
.cash-cards .card.brand .card-value { text-shadow: 0 1px 3px rgba(0, 0, 0, .2); }

/* 顶部品牌行：logo 圆片 + 品牌名 */
.cash-cards .card.brand .brand-head {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 8px;
}
.cash-cards .card.brand .brand-chip {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, .24);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .25);
  font-size: 12.5px;
  font-weight: 700;
  color: #fff;
}
.cash-cards .card.brand .brand-chip-solid { background: #fff; }
.cash-cards .card.brand .brand-chip-wide {
  width: 40px;
  border-radius: 13px;
}
.cash-cards .card.brand .brand-logo-img {
  width: 15px;
  height: 15px;
  object-fit: contain;
  display: block;
  pointer-events: none;
  user-select: none;
}
.cash-cards .card.brand .brand-chip-wide .brand-logo-img {
  width: 32px;
  height: 10px;
}
.cash-cards .card.brand .brand-star {
  width: 17px;
  height: 17px;
  display: block;
  fill: currentColor;
}
.cash-cards .card.brand .brand-chip-star {
  background: rgba(255, 255, 255, .2);
}
.cash-cards .card.brand .brand-logo-img.white { filter: brightness(0) invert(1); }
.cash-cards .card.brand .brand-name {
  font-size: 12.5px;
  font-weight: 600;
  color: rgba(255, 255, 255, .92);
  letter-spacing: .3px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .14);
}

/* 右下浅水印：logo 转白 + 低透明度（宽幅字标容器加宽，无白底） */
.cash-cards .card.brand .brand-big {
  position: absolute;
  right: 8px;
  bottom: 4px;
  z-index: 1;
  width: 58px;
  height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  user-select: none;
}
.cash-cards .card.brand .brand-big .brand-logo-img.white {
  width: 50px;
  height: 50px;
  opacity: .2;
}
.cash-cards .card.brand .bigchar {
  font-size: 46px;
  font-weight: 800;
  line-height: 1;
  color: rgba(255, 255, 255, .2);
}
.cash-cards .card.brand .brand-big-wide {
  width: 96px;
  height: 42px;
  right: 6px;
  bottom: 8px;
}
.cash-cards .card.brand .brand-big-wide .brand-logo-img.white {
  width: 88px;
  height: 36px;
}
.cash-cards .card.brand .brand-big-star {
  width: 72px;
  height: 72px;
  right: 8px;
  bottom: 3px;
}
.cash-cards .card.brand .brand-star-watermark {
  width: 66px;
  height: 66px;
  display: block;
  fill: #fff;
  opacity: .2;
}
table.grid .num.up { color: var(--green); font-weight: 600; }
table.grid .num.down { color: var(--red); font-weight: 600; }
.link.danger { color: var(--red); }
.link.danger:hover { text-decoration: underline; }

/* ---------------- 表格 ---------------- */
table.grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13.5px;
}
table.grid th {
  padding: 11px 14px;
  text-align: left;
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .35px;
  text-transform: uppercase;
  color: var(--label-3);
  white-space: nowrap;
  background: var(--surface-2);
  border-bottom: 1px solid var(--separator);
}
table.grid td {
  padding: 12px 14px;
  color: var(--label);
  border-bottom: 1px solid var(--separator);
}
/* 列表「操作」列：一行排开的轻量按钮。
   列表每一行都重复这组按钮，所以既不描边也不用实心块 —— 底色留给悬停，
   语义只靠文字色表达（审核 = 主题蓝、删除 = 红），整列安静下来才扫得动。
   真正的「刹车」留在确认弹窗里：确认删除 / 确认反审核仍是实心红按钮。
   原来是 132px 宽的上二下二栅格，一行单据被撑成两行按钮那么高。 */
table.grid td .doc-actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  vertical-align: middle;
}
table.grid td .doc-actions .btn {
  height: 26px;
  min-height: 26px;
  width: auto;
  padding: 0 10px;
  font-size: 12px;
  font-weight: 500;
  color: var(--label-2);
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
table.grid td .doc-actions .btn:hover { background: var(--fill-2); border-color: transparent; color: var(--label); }
table.grid td .doc-actions .btn-primary { color: var(--blue); }
table.grid td .doc-actions .btn-primary:hover { background: var(--blue-tint); border-color: transparent; color: var(--blue-press); box-shadow: none; }
table.grid td .doc-actions .btn-danger { color: var(--red-ink); }
table.grid td .doc-actions .btn-danger:hover { background: rgba(207, 43, 43, .12); border-color: transparent; color: var(--red-ink); box-shadow: none; }

table.grid tbody tr:hover { background: var(--row-hover); }
table.grid tbody tr.clickable { cursor: pointer; }
/* 可点击行与普通行用同一层遮罩：能点由 cursor 提示，不靠加深底色 */
table.grid tbody tr.clickable:hover { background: var(--row-hover); }
table.grid tbody tr:last-child td { border-bottom: none; }
table.grid .num { text-align: right; font-variant-numeric: tabular-nums; }
table.grid .center { text-align: center; }
/* 带悬停提示的单元格（如实时库存的结存数量，悬停列出各仓库数量）：
   虚线下划线是「这里还有信息」的通用暗示，没有它用户不会想到去悬停。 */
table.grid td.has-tip {
  cursor: help;
  text-decoration: underline dotted rgba(22, 24, 29, .32);
  text-underline-offset: 3px;
}
/* 紧跟在数量列后面的「单位」列：收到内容宽度、左对齐、次级字色 ——
   和右对齐的数量贴在一起读成「+120 米」，又不会被当成一个独立数据列抢读 */
table.grid th.unit-col,
table.grid td.unit-col { width: 1%; white-space: nowrap; padding-left: 6px; }
table.grid td.unit-col { color: var(--label-2); }
table.grid tr.total-row td {
  font-weight: 600;
  background: var(--surface-2);
  border-top: 1px solid var(--separator);
}
/* 一单多账户（同一张收/付款单拆多个入账账户）：整组高亮由 JS 给同单各行加 .arap-hover，
   遮罩统一落在 td 这一层 —— tr 自身的悬停底色必须让位，否则鼠标所在那一行会多叠一层，
   同组行深浅不一（合并单元格跨行，鼠标在哪一行上还会来回跳）。
   选择器必须带 table.grid 前缀：只写 .arap-detail-grid 压不过上面的 table.grid tbody tr:hover。 */
/* 长列表（购销单 / 收付款单 / 出入库流水 / 资金流水 / 实时库存 / 应收应付）：
   表格自带滚动条、表头吸顶。
   滚动条占去的那约 11px 槽位，底色取自滚动容器自身的背景（轨道透明时直接透出）。
   所以容器背景用一条渐变：表头那一段刷成表头灰、往下透明透出卡片白底 ——
   这样灰表头右侧接的是灰槽、白数据行右侧接的是白槽，上下都对得上，
   既不用改表头颜色，也不会留下色缝。
   渐变高度必须等于表头实际高度（见下方注释中的实测值）。 */
.list-sticky {
  max-height: 62vh;
  overflow: auto;
  /* --head-h 必须等于表头实际高度（实测值；改了表头的 padding/字号要跟着重量）。
     表头是 sticky 的，永远贴在容器顶端，所以这条灰带的位置是固定的。 */
  --head-h: 40px;
  background: linear-gradient(var(--surface-2) 0 var(--head-h), transparent var(--head-h)) no-repeat;
}
/* 应收应付那两张表用了更紧凑的内边距（table.grid.arap-table），表头矮 6px */
.arap-col .list-sticky { --head-h: 34px; }
/* 单据列表给一个最小宽度，窗口放不下时**横向滚动**而不是把列挤扁。
   table.grid 本身是 width:100%，不设下限的话列数一多（销售单现在有 11 列）
   就会被压到单号换行、金额列只剩两个字。min-width 一上，.list-sticky 的
   overflow:auto 自然就把横向滚动条给出来了 —— 容器本来就是 auto，
   缺的只是「表格拒绝被压扁」这一半。 */
.list-sticky table.grid.doc-grid { min-width: 1180px; }
/* 列少的长列表（资金流水只有 6 列）不吃这个下限：1180 是按十来列的单据列表量的，
   套在 6 列上等于永远挂着一条用不上的横向滚动条。去掉下限后表格仍是
   width:100% + nowrap，真放不下时照样会滚 —— 这正是「放不下才启用」。 */
.list-sticky table.grid.doc-grid.doc-grid-auto { min-width: 0; }
/* 1180 是这些列不折行所需要的真实宽度（实测：降到 1080 单号和日期就开始换行，
   行高从 65px 涨到 104px）。所以**不给窄屏另设一档更小的下限** ——
   那只会把「挤扁」换个宽度重演一遍，横向滚动本来就是为这种情况准备的。 */
/* 整张表单行显示。既然放不下时已经横向滚动了，就没有理由再让任何一格折行 ——
   折行会让行高参差不齐（日期折成两行、客户名折成三行），一列对不上一列。
   关联单据那一列不受影响：它是多个块级 .settled-line 堆起来的，本来就该多行。 */
.list-sticky table.grid.doc-grid th,
.list-sticky table.grid.doc-grid td { white-space: nowrap; }
/* 轨道保持透明（透出卡片白底，与白表头、白数据行连成一色，这是不露色缝的前提）；
   滑块比全局那档 .17 调深，因为这条滚动条现在是表格唯一的滚动入口，太淡会看不见。 */
.list-sticky::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, .3);
  background-clip: content-box;
}
.list-sticky::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, .45);
  background-clip: content-box;
}
/* 横向那条单独加粗加深。
   销售单列表有 12 列、放不下时**只能**靠它看全，而纵向滚动条只是顺手的；
   两条同样淡的话，鼠标用户常常找不到横向那条。11px 的条里留 7px 实体
   （纵向那条因为 border 是 3px，只有 5px 实体）。 */
.list-sticky::-webkit-scrollbar-thumb:horizontal {
  background: rgba(0, 0, 0, .42);
  background-clip: content-box;
  border-width: 2px;
}
.list-sticky::-webkit-scrollbar-thumb:horizontal:hover {
  background: rgba(0, 0, 0, .58);
  background-clip: content-box;
}
.list-sticky table.grid thead th {
  position: sticky;
  top: 0;
  z-index: 12;
  background: var(--surface-2);      /* 表头颜色保持原样，不动 */
  /* 表头自带的 border-bottom 在 sticky 时不跟随，用投影补一条分隔线 */
  box-shadow: 0 1px 0 var(--separator);
}

/* 应收应付：客户在左、供应商在右并排。两张表是对照着看的，上下堆叠要来回滚。
   align-items:start 让两张卡各自按内容高度收缩，不会被对侧撑成一样高。 */
.arap-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}
/* 一列 = 该侧的一张总数卡 + 该侧的明细表，卡片与表格严格对齐在同一列 */
.arap-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* 总数卡：应收款 / 预收款左右两半，等宽、同字号，中间一条竖线。
   预收和应收分属资产负债表两行、不能相减 —— 竖线就是这层意思的视觉形式，
   既不像上下堆叠那样暗示「主 + 附注」，也不像写在一起那样暗示能冲抵。
   两半永远都画（为零也画 0.00），客户卡与供应商卡因此高度完全一致。

   这张卡比首页看板的指标卡**明显压扁**（实测 105px → 71px）：那边一屏就四张卡，
   高一点是主角；这里卡下面还压着一张要翻的明细表，卡每高 1px，表就少 1px。
   宽度不动 —— 卡片必须和下方表格严格对齐在同一列，那是这个版面的骨架。 */
.metric.arap-metric {
  display: flex;
  align-items: stretch;
  gap: 14px;
  padding: 12px 15px 13px;
  border-radius: 12px;
}
.arap-metric .k { font-size: 12px; margin-bottom: 5px; }
.arap-metric .v { font-size: 20px; letter-spacing: -.4px; }
.arap-metric .u { font-size: 12px; margin-left: 3px; }
/* 柔光圆跟着卡一起收小，不然一张扁卡被一团大色晕占掉半边 */
.arap-metric::after { top: -34px; right: -24px; width: 72px; height: 72px; }
.arap-metric .am-half { flex: 1 1 0; min-width: 0; position: relative; z-index: 1; }
.arap-metric .am-sep {
  flex: 0 0 1px;
  background: var(--separator);
  position: relative;
  z-index: 1;
}
/* 预收不是应收，不跟着主数字染语义色 */
.arap-metric .am-half + .am-sep + .am-half .v { color: var(--label-2); }

/* 两张明细表的抬头与底部合计条都压薄一档。
   这一页的主角是中间那串往来单位，上下两条一个是标签、一个是单个数字，
   它们每薄 1px，能看到的单位就多 1px。字号跟着降一档，不然条子薄了反而显挤。
   只作用在 .arap-col 内 —— 实时库存那条合计是整页唯一的落点，不跟着瘦。 */
.arap-col .card-head { padding: 9px 16px; font-size: 13.5px; }
.arap-col .card-head h3 { font-size: 13.5px; }
.arap-col .card-total-bar { padding: 7px 16px 8px; }
.arap-col .card-total-bar .v { font-size: 16px; }

/* 长列表的合计条：在卡片**里面**、滚动区（.list-sticky）**外面**。
   .list-sticky 自己滚（max-height:62vh），所以这一条永远贴在卡片底边、不会被滚走；
   放进表格的 tfoot 则会跟着行一起滚上去，数据一多就找不着了。
   卡片是 overflow:clip，底部圆角由卡片裁出来，这里不用自己补 border-radius。 */
/* 卡片标题栏里的检索框（资金流水用）。
   那一页有两张卡，检索框挂在页面顶上会分不清它管哪一张；
   单独占一行又白吃掉一截高度，所以和标题并排塞进 .card-head。
   标题栏的 font-weight 是 650，输入框要自己掰回常规字重。 */
.card-head .searchbox.head-search {
  flex: 0 1 340px;
  max-width: 340px;
  margin-left: 4px;
}
.card-head .searchbox.head-search input {
  height: 30px;
  font-size: 12.5px;
  font-weight: 400;
}
.card-head .searchbox.head-search svg { width: 13px; height: 13px; }

.card-total-bar {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 11px 16px 12px;
  background: var(--surface-2);
  border-top: 1px solid var(--separator);
  font-variant-numeric: tabular-nums;
}
.card-total-bar .k { font-size: 12.5px; font-weight: 500; color: var(--label-2); }
.card-total-bar .v { font-size: 18px; font-weight: 650; letter-spacing: -.3px; color: var(--label); }
.card-total-bar .u { font-size: 12.5px; color: var(--label-3); }
.card-total-bar .n { margin-left: auto; font-size: 12.5px; color: var(--label-3); }

/* 实时库存的合计条压薄一档。
   基础那条（.card-total-bar）上下留 11/12px，是给往来明细弹窗那种
   「一行里既有标题又有大号数字」的场景留的呼吸感；
   实时库存这张表整屏都是数字，底边再挂一条同样厚度的横条就显得很占地方。
   只加修饰类，不动基础样式 —— 弹窗里那条的留白本来就该更松。 */
.card-total-bar.stk-total { padding: 5px 16px 6px; gap: 7px; }
.card-total-bar.stk-total .v { font-size: 15px; letter-spacing: -.2px; }
.card-total-bar.stk-total .k,
.card-total-bar.stk-total .u,
.card-total-bar.stk-total .n { font-size: 12px; }

/* 批量删除的待删清单与失败清单（基础资料页）。
   .mb-list 是确认弹窗里「将要删掉哪几条」的预览，最多列 12 条；
   .mb-fails 是执行后的失败明细 —— 档案名可能很长，
   所以失败原因单独一行，不做成两列对齐（对齐了反而挤成一团）。 */
.mb-list {
  margin: 0;
  padding: 8px 0 8px 22px;
  max-height: 190px;
  overflow: auto;
  border-radius: 10px;
  background: var(--surface-2);
  font-size: 12.5px;
  line-height: 1.9;
  color: var(--label-2);
}
.mb-list li { list-style: disc; }
.mb-fails {
  max-height: 260px;
  overflow: auto;
  border: 1px solid var(--separator);
  border-radius: 10px;
}
.mb-fail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px;
  font-size: 12.5px;
  border-bottom: 1px solid var(--separator);
}
.mb-fail:last-child { border-bottom: 0; }
.mb-fail .mb-fail-msg { color: var(--red-ink); }
/* 低于安全库存的商品数：夹在金额和「共 N 个商品」之间，用琥珀色跟行内那个标签呼应 */
/* 实时库存：分类分区卡 + 商品卡。浅底、细边和小幅阴影沿用报价单卡片的视觉语言，
   商品数量放大，成本/安全库存放在次级信息区；滚动只发生在库存卡区域。 */
.stock-card-board {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
  overflow: auto;
  padding: 2px 2px 8px;
}
.stock-category-card {
  background: rgba(255,255,255,.72);
  border: 1px solid #e5e8ef;
  border-radius: 16px;
  box-shadow: 0 1px 1px rgba(30,50,90,.04), 0 14px 34px -26px rgba(30,60,130,.28), inset 0 1px 0 rgba(255,255,255,.8);
  overflow: hidden;
}
.stock-category-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px 10px;
  border-bottom: 1px solid rgba(226,231,240,.9);
  background: linear-gradient(135deg, rgba(247,250,255,.96), rgba(255,255,255,.72));
}
.stock-category-head > div { display: flex; align-items: center; min-width: 0; gap: 8px; }
.stock-category-mark { color: #5574d9; font-size: 17px; line-height: 1; }
.stock-category-head strong { color: var(--label); font-size: 15px; letter-spacing: -.2px; max-width: min(62vw, 520px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stock-category-path { color: var(--label-4); font-size: 11px; white-space: nowrap; }
.stock-category-count { flex: none; color: var(--label-3); font-size: 12px; white-space: nowrap; }
.stock-product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 10px;
  padding: 12px;
}
.stock-product-card {
  min-width: 0;
  padding: 13px 14px 11px;
  border: 1px solid #edf0f5;
  border-radius: 13px;
  background: rgba(255,255,255,.88);
  box-shadow: 0 5px 18px -15px rgba(30,60,130,.35);
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}
.stock-product-card:hover { transform: translateY(-1px); border-color: #d8e0f3; box-shadow: 0 10px 24px -16px rgba(30,60,130,.42); }
.stock-product-card.is-low { border-color: #f0d79a; background: linear-gradient(145deg, #fffdf7, #fffaf0); }
.stock-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.stock-card-name { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 3px; }
.stock-card-name strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--label); font-size: 14px; }
.stock-card-code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #5f78c9; font-size: 11px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.stock-card-state { flex: none; padding: 3px 7px; border-radius: 999px; font-size: 10.5px; white-space: nowrap; }
.stock-card-state.ok { color: #297750; background: #edf8f1; }
.stock-card-state.low { color: #9b6800; background: #fff1c9; }
.stock-card-spec { margin-top: 7px; color: var(--label-3); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stock-card-main { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-top: 12px; }
.stock-card-qty { min-width: 0; display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px; }
.stock-card-label, .stock-card-amount span, .stock-card-metrics span { display: block; color: var(--label-3); font-size: 11px; }
.stock-card-qty b { color: var(--label); font-size: 25px; line-height: 1; letter-spacing: -.8px; font-variant-numeric: tabular-nums; }
.stock-card-qty em { color: var(--label-2); font-size: 12px; font-style: normal; }
.stock-card-qty small { flex-basis: 100%; color: var(--label-4); font-size: 10px; }
.stock-card-amount { text-align: right; min-width: 92px; }
.stock-card-amount b { display: block; margin-top: 3px; color: #3f5fbd; font-size: 16px; font-variant-numeric: tabular-nums; }
.stock-card-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 13px; padding-top: 9px; border-top: 1px dashed #e6eaf1; }
.stock-card-metrics div:last-child { text-align: right; }
.stock-card-metrics b { display: block; margin-top: 3px; color: var(--label-2); font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.stock-card-warehouse { margin-top: 9px; padding: 6px 8px; border-radius: 7px; background: #f5f7fb; color: var(--label-3); font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stock-empty { padding: 48px 20px; background: var(--surface); border: 1px solid var(--separator); border-radius: 14px; }
@media (max-width: 720px) {
  .stock-product-grid { grid-template-columns: 1fr; padding: 9px; }
  .stock-category-head { padding-left: 12px; padding-right: 12px; }
  .stock-category-path { display: none; }
}

.card-total-bar.stk-total .stk-low {
  font-size: 12px;
  color: var(--amber);
  padding: 1px 7px;
  border-radius: var(--r-pill);
  /* 不用 color-mix：它要 Chrome 111+ / Safari 16.2+，平板上的浏览器未必支持。
     与 .tag.open 同源的 --amber-soft 是现成的浅底，直接用它。 */
  background: var(--amber-soft);
}

/* 紧凑行高：一屏能多看一倍的单位。
   选择器必须带 table.grid 前缀 —— 只写 .arap-table td 会被 table.grid td 的
   12px 内边距压过去（同类数、元素更多者胜），行高根本降不下来。 */
table.grid.arap-table th { padding: 8px 14px; }
table.grid.arap-table td { padding: 6px 14px; }
/* 可排序表头：点一下在升序/降序之间切换 */
.arap-table th.sortable { cursor: pointer; user-select: none; }
.arap-table th.sortable:hover { color: var(--label); }
.arap-table th .sort-arrow { margin-left: 4px; color: var(--blue); }
.arap-cols .card { margin-bottom: 0; }

/* 往来明细：表格自己滚，标题栏吸顶。
   弹窗的 .dialog-body 本身也能滚，但那样顶部的汇总字段会一起滚走；
   单独套一层滚动容器，下拉时就只有单据行在动。 */
.arap-detail-scroll {
  /* 高度留出顶部四个汇总字段和弹窗内边距，使其正好填满 .dialog-body 的 66vh
     上限 —— 内层滚完即到底，外层就不会再冒出第二条滚动条 */
  max-height: calc(66vh - 150px);
  min-height: 160px;
  overflow: auto;
  border-radius: 10px;
}
table.grid.arap-detail-grid thead th {
  position: sticky;
  top: 0;
  /* 必须高过合并单元格：rowspan 的 td 会盖住普通表头 */
  z-index: 20;
  background: var(--surface-2);
  box-shadow: 0 1px 0 var(--separator);
}
table.grid.arap-detail-grid tbody tr:hover { background: transparent; }
table.grid.arap-detail-grid tbody tr.arap-hover td { background: var(--row-hover); }
.arap-detail-grid td.merge-cell {
  text-align: center;
  vertical-align: middle;
  border-bottom-color: var(--separator);
}
.arap-detail-grid td.detail-cell {
  border-bottom-color: var(--separator);
}
.arap-detail-grid td.merge-cell[rowspan] {
  vertical-align: middle;
}

/* 表格单元格内第二行小字：单位换算链 / 数量复合换算（如 31片（1箱+1片）） */
table.grid .u-sub {
  display: block;
  font-size: 11.5px;
  color: var(--label-3);
  margin-top: 2px;
  font-weight: 400;
}

/* 空状态 */
.empty {
  color: var(--label-3);
  text-align: center;
  padding: 48px 20px;
  font-size: 13.5px;
  line-height: 1.8;
}

/* ---------------- 状态标签 ---------------- */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .1px;
  line-height: 16px;
  color: var(--label-2);
  background: var(--fill-2);
  white-space: nowrap; /* 状态文字（已审核/未审核等）永不换行 */
}
.tag.draft { background: var(--amber-soft); color: var(--amber); }
.tag.audited { background: var(--green-soft); color: var(--green); }
.tag.low { background: var(--red-soft); color: var(--red); }
.tag.ar { background: var(--blue-tint); color: var(--blue); }
.tag.ap { background: rgba(109, 63, 208, .12); color: var(--purple); }
.tag.red { background: var(--red-soft); color: var(--red); }
/* 已结账：比已审核更「死」的状态 —— 灰色，表示只能看、什么都动不了 */
.tag.closed { background: var(--fill-2); color: var(--label-2); }
.tag.open { background: var(--amber-soft); color: var(--amber); }
/* 出入库流水里「暂估成本」：调出仓当时库存不足、按暂估价结转，待补货追平 */
.tag.est { background: var(--amber-soft); color: var(--amber); }
.tag.done { background: var(--green-soft); color: var(--green); }
/* 实时库存状态：正常 = 绿，低于安全库存 = 琥珀（呼应手机端 stk-state 的配色） */
.tag.ok { background: var(--green-soft); color: var(--green); }
.tag.amber { background: var(--amber-soft); color: var(--amber); }

/* ---------------- 弹窗 ---------------- */
.mask {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 56px 20px;
  overflow-y: auto;
  background: rgba(20, 24, 38, .34);
  -webkit-backdrop-filter: blur(7px) saturate(140%);
  backdrop-filter: blur(7px) saturate(140%);
  animation: maskIn .22s var(--ease);
}
.dialog {
  width: 100%;
  max-width: 780px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--shadow-pop);
  overflow: hidden;
  animation: dialogIn .32s var(--ease-spring);
}
.dialog-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 18px 22px 16px;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -.25px;
  color: var(--label);
  background: linear-gradient(180deg, rgba(248, 249, 252, .85) 0%, rgba(255, 255, 255, 0) 100%);
  border-bottom: 1px solid var(--separator);
}
.dialog-head .spacer { flex: 1; }
.dialog-head .x {
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--label-3);
  font-size: 13px;
  cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.dialog-head .x:hover { background: var(--fill); color: var(--label); }
.dialog-body { padding: 20px 22px; max-height: 66vh; overflow-y: auto; }
/* 内容自带滚动容器的弹窗（往来明细）：关掉这一层滚动，否则右侧会叠出两条滚动条 */
.dialog-body.dialog-body-flush { overflow: hidden; }
.dialog-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 9px;
  padding: 14px 22px 17px;
  border-top: 1px solid var(--separator);
  background: var(--surface-2);
}
/* 底部按钮：不设 min-width（避免与 .btn-sm 混排时被撑成两种高度/宽度），统一高度 */
/* 同一行内相邻按钮必须等高：底栏里 .btn-sm 也拉齐到大号 34px（视觉一条线） */
.dialog-foot .btn,
.dialog-foot .btn.btn-sm { min-width: 0; height: 34px; min-height: 34px; font-size: 13px; padding: 0 16px; }
/* 多选弹窗：清空按钮右侧列出当前选中的项。
   min-width:0 + 省略号是必须的 —— 选多了不能把右边的「确定/关闭」挤出底栏。
   完整清单在 title 里（见 syncPickFoot）。 */
.dialog-foot .pick-sel {
  min-width: 0;
  flex: 0 1 auto;
  font-size: 12.5px;
  color: var(--label-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: default;
}
.dialog-foot .pick-sel.pick-sel-none { color: var(--label-3); }
.dialog-foot .btn.btn-sm svg { width: 14px; height: 14px; }
/* 宽版弹窗（商品超级检索、往来明细等） */
.dialog-wide { width: 92vw; max-width: 1120px; }
/* 往来明细加了「已核销 / 未核销」两列之后共 13 列，1120px 放不下（实测要 1258px），
   再宽一档。只放宽这一个弹窗 —— 商品检索那几个列少，拉宽反而空。
   :has() 不被支持时规则整条失效，退回原宽度 + 一条横向滚动条，不会错版。 */
.dialog-wide:has(.arap-detail-scroll) { max-width: 1360px; }
/* 宽版弹窗内表格：一行完整显示，防止日期/状态标签换行 */
.dialog-wide .grid.dense th,
.dialog-wide .grid.dense td { white-space: nowrap; }

/* ---------------- 列表工具栏（模糊检索） ---------------- */
.toolbar {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}
.searchbox { position: relative; flex: 1; max-width: 420px; }
.searchbox svg {
  position: absolute;
  left: 12px;
  top: 50%;
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  color: var(--label-3);
  pointer-events: none;
}
.searchbox input {
  padding-left: 33px;
  border-radius: var(--r-pill);
  background-color: var(--fill-2);
  border-color: transparent;
  color: var(--label);
}
.searchbox input::placeholder { color: var(--label-3); }
.searchbox input:hover:not(:focus) { border-color: transparent; background-color: var(--fill); }
.searchbox input:focus {
  background-color: var(--surface);
  border-color: var(--blue);
}
.search-count {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--label-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ---------------- 会计：科目 / 凭证 / 余额表 / 明细账 ----------------
   没有新的版式，全部复用既有底盘（.card / .list-sticky / .card-total-bar /
   .bill 骨架）。这里只补三样东西：期间选择条、凭证正文表、借贷平衡指示。 */
/* 期间条：.toolbar 是 flex，而全局 input 带 width:100%，不给宽度两个日期框
   会把整条撑开成两条大白杠（科目下拉同理）。这里逐个定宽，全部 flex:none。 */
.gl-period { align-items: center; gap: 8px; flex-wrap: wrap; }
.gl-period > * { flex: none; }
.gl-period input[type="date"] { width: 148px; height: 32px; font-size: 12.5px; }
.gl-period select { height: 32px; font-size: 12.5px; width: auto; min-width: 96px; }
/* 这一条里的东西必须一个高度，否则「全部期间」按钮（btn-sm 28px）会比两个
   日期框矮 4px、底边各在各的位置，一条工具栏看着像两条。 */
.gl-period .btn { height: 32px; min-height: 32px; }
.gl-period select#gl_code { min-width: 210px; }
.gl-period .spacer { flex: 1 1 auto; }
.gl-lab { font-size: 12.5px; color: var(--label-2); white-space: nowrap; }
.gl-dash { color: var(--label-3); }
.gl-muted { color: var(--label-3); }
.gl-dir { font-size: 12.5px; color: var(--label-2); white-space: nowrap; }
.gl-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--label-2);
  cursor: pointer;
  white-space: nowrap;
}
.gl-check input { width: 15px; height: 15px; flex: none; cursor: pointer; }
/* 试算平衡／借贷不平：整张科目余额表就是为了回答这一句，不该是个小字标签。
   比通用 .tag 放大一档，并与期间条上的日期框、按钮同高（32px），一条线对齐。 */
.tag.tb-flag {
  height: 32px;
  padding: 0 16px;
  font-size: 13px;
  font-weight: 650;
  line-height: 1;
  letter-spacing: .2px;
}
/* 期初行：明细账的第一行不是一笔业务，压暗一点、底色略深，和下面的分录分开 */
.gl-opening td { background: var(--fill-2); color: var(--label-2); }

/* 凭证正文（详情弹窗 / 查看凭证共用） */
.vch-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 0 2px 12px;
  font-size: 12.5px;
  color: var(--label-2);
}
.vch-meta .vch-summary { font-weight: 600; color: var(--label); }
table.grid.vch-table th { padding: 8px 12px; }
table.grid.vch-table td { padding: 7px 12px; }
table.grid.vch-table tfoot td {
  background: var(--surface-2);
  border-top: 1px solid var(--separator-strong);
  font-weight: 650;
}
/* 会计四张表走紧凑行高。科目余额表一行八个数字、凭证列表九列、明细账七列，
   按默认的 12px 行内边距一屏只放得下十来行，而这几张表恰恰是用来一眼扫全貌的。
   密度与应收应付那两张表取齐（th 8px / td 6px）。
   选择器必须带 table.grid 前缀 —— 只写 .gl-table td 会被 table.grid td 的
   12px 压过去（同类数、元素更多者胜），行高根本降不下来。 */
table.grid.gl-table th { padding: 6px 10px; }
table.grid.gl-table td { padding: 3px 10px; line-height: 1.45; }
/* .list-sticky 用 --head-h 画表头那条灰带（见它自己的注释），默认 40px 是按
   全站通用表头量的。会计这几张表的表头被压矮了，不跟着改，灰带就会**漏到
   标题行下面**，看上去像一道多出来的灰色遮罩 —— 实测值，改 padding/字号要重量。 */
.gl-sticky { --head-h: 30px; }
/* 双层表头那张（科目余额表）两行之和 */
.tb-sticky { --head-h: 60px; }

/* 合计行钉在滚动区底边。
   这两张表（科目余额表、明细账）的合计必须**压在各自那一列底下**才读得出来，
   所以它只能是表格自己的 tfoot —— 卡片底部那条 .card-total-bar 在表格外面，
   拿不到列宽，横排成一行文字就得让眼睛上下来回找对应关系。
   sticky bottom 让它照样不跟着行滚走，效果和 .card-total-bar 一样。
   （实时库存那种只有一个合计数的表继续用 .card-total-bar，那边没有对列的问题。） */
table.grid.gl-table tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 11;                      /* 低于表头的 12：横向滚动时表头压着合计行更自然 */
  background: var(--surface-2);
  font-weight: 650;
  color: var(--label);
  /* 自带的 border-top 在 sticky 下不跟随，用投影补一条分隔线（和表头那条同理） */
  box-shadow: 0 -1px 0 var(--separator);
}
/* 行数少时也要把合计压到圆角矩形的底边。
   光靠 `position: sticky; bottom: 0` 不够 —— sticky 只在「本来会被滚出去」时才生效，
   表格比容器矮的时候合计行就紧跟在最后一条数据下面，悬在卡片中间。
   做法：表格撑满容器高度，再在 tbody 末尾垫一个空行把富余高度吃掉。

   ⚠ 只写 `table { height: 100% }` 会**把富余高度摊给每一行** —— 12 行的明细账
   行高直接从 26px 涨到 56px，看着比没改之前还粗。关键在后面这一条：
   给正常行一个具体高度（表格里 height 是下限，不是上限，所以不会压扁内容），
   富余高度就只能全落到没写高度的那个垫高行上。这是 CSS 表格的老套路，
   少写一条就完全是另一个效果。

   ⚠⚠ 而且 `height: 100%` **只许加在有垫高行的表上**（.gl-foot）。
   科目表和凭证列表没有合计行、也就没有垫高行，给它们撑满容器的话，
   富余高度无处可去，只能摊给那几行 —— 平时行多看不出来，一用超级检索
   过滤到两三行，行高立刻暴涨把表格填满。这就是它当初的症状。 */
table.grid.gl-table.gl-foot { height: 100%; }
/* 四张表的行高必须是同一个数，不能让「这一列里有没有按钮」去决定它 ——
   科目表和凭证列表的操作列有 .btn-sm（28px），光靠 padding 对齐的话它们
   天生比明细账、余额表高 6px，四个页面来回切会看到行距一跳一跳的。
   办法是两头一起锁：按钮压到 24px，行高给一个统一下限 31px（24 + 上下各 3 + 1px 下边框）。
   表格里 height 是**下限**不是上限，所以内容更高的行照样撑得开；
   同时它也是上面那条「富余高度只落到垫高行上」的前提。 */
table.grid.gl-table tbody tr { height: 31px; }
/* 选择器必须一路写到 `td .doc-actions .btn` —— 上面那条通用规则
   `table.grid td .doc-actions .btn { height: 26px }` 的权重是 (0,3,2)，
   只写 `.gl-table .btn-sm` 是 (0,3,1)，差一个元素就压不过去，按钮纹丝不动。
   24px 按钮 + 上下各 3px 内边距 + 1px 下边框 = 31px，与上面的行高下限正好相等，
   四张表因此严丝合缝地一样高。 */
table.grid.gl-table td .doc-actions .btn {
  height: 24px;
  min-height: 24px;
  padding: 0 9px;
}
/* 垫高行必须留 auto：有具体高度的行不吃富余高度，只有它会，合计才顶得到底 */
table.grid.gl-table tbody tr.gl-fill { height: auto; }
table.grid.gl-table tr.gl-fill td {
  padding: 0;
  border-bottom: 0;
  background: var(--surface);
}
table.grid.gl-table tr.gl-fill:hover td { background: var(--surface); }

/* 余额表的双层表头：第一层跨列的「期初 / 本期 / 期末」要有分组竖线，
   否则六个数字列连成一片，读的时候根本数不清哪两列是一组。 */
table.grid.tb-table thead tr:first-child th[colspan] {
  border-left: 1px solid var(--separator);
  text-align: center;
}
table.grid.tb-table thead tr:last-child th:nth-child(odd) { border-left: 1px solid var(--separator); }
table.grid.tb-table td:nth-child(3),
table.grid.tb-table td:nth-child(5),
table.grid.tb-table td:nth-child(7) { border-left: 1px solid var(--separator); }
/* ⚠ 双层表头的吸顶。
   .list-sticky 那条通用规则把 thead 的每个 th 都设成 position:sticky; top:0 ——
   单层表头没问题，两层就坏了：第二行也贴在 0，和第一行**叠在一起**，
   往上拖滚动条时「期初余额 / 本期发生额 / 期末余额」会从「借方 / 贷方」
   底下透出来，两行字糊成一团。第二行必须停在第一行的下沿。

   --tb-row 是第一行的实测高度，--head-h 是两行之和（.list-sticky 用它画那条
   灰色表头带，不改的话灰带只盖住上面一行，下面一行悬在白底上）。
   改了上面 .gl-table 的 padding 或字号，这个数要跟着重量 —— 实测值，别估：
   第一版估了 31px，实际 34px，两行照样叠着 3px，肉眼还不太看得出来。 */
.tb-sticky { --tb-row: 30px; }
.tb-sticky table.grid.tb-table thead tr:nth-child(2) th { top: var(--tb-row); }
/* 跨两行的「科目 / 类别」本来就从 0 开始、占满两行高，保持 top:0 不动 */
.tb-sticky table.grid.tb-table thead tr:first-child th { top: 0; }

/* ---------------- 库存内部单据 / 资金转账单（第三期） ---------------- */
/* 无往来单位的开单页：抬头只有日期 + 一句说明 */
.bill-head .bill-date-only { flex: 0 0 200px; }
.bill-head .bill-head-hint { flex: 1; }
.bill-hint-text {
  min-height: 34px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  border-radius: var(--r-sm, 8px);
  background: var(--fill-2);
  font-size: 12.5px;
  color: var(--label-2);
  line-height: 1.5;
}
/* 盘盈绿、盘亏红：和出入库流水「入绿出红」同一个直觉。
   要写上 table.grid td 前缀 —— 表格格子自己设了颜色，单个类名压不过它 */
.diff-up, table.grid td.diff-up { color: var(--green); }
.diff-down, table.grid td.diff-down { color: var(--red); }
/* 资金转账单：转出 → 转入 中间那个箭头，与输入框竖直居中 */
.fund-arrow { flex: none; align-self: flex-end; height: 34px; display: flex; align-items: center; color: var(--label-3); font-size: 16px; }
.bill-head .fund-remark { flex: 2; }
.fund-hint { margin-top: 2px; padding: 9px 12px; border-radius: 8px; background: var(--fill-2); font-size: 12.5px; color: var(--label-2); line-height: 1.55; }

/* ---------------- 财务报表（利润表 / 资产负债表） ----------------
   行少、不滚动，所以不走 list-fill；行高沿用会计页的紧凑行。
   小计 / 合计行加粗、底色压一档，读表时一眼找到结论行。 */
.rpt-card { max-width: 720px; }
.rpt-two { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
.rpt-two .rpt-card { max-width: none; }
table.grid.rpt-table td.rpt-lv1 { padding-left: 30px; }
table.grid.rpt-table tr.rpt-total td { font-weight: 650; background: var(--surface-2); }
table.grid.rpt-table tr.rpt-grand td { font-weight: 700; background: var(--fill-2); border-top: 1px solid var(--separator-strong); }
table.grid.rpt-table tr.rpt-sec td { font-weight: 600; color: var(--label-2); background: var(--surface); }
.rpt-note { padding: 10px 14px; font-size: 12px; color: var(--label-3); }

/* ---------------- 库存余额表 / 成本调整单 ----------------
   表格走会计页那一套（紧凑行 + 分组表头 + 吸底合计），上方一条工具栏放下全部筛选。 */
/* 工具栏：期间 + 上月/本月/下月 + 竖线 + 超级检索 + 计数 + 右侧状态，一行。
   .gl-period 让每个孩子 flex:none，检索框要单独放开去吃剩余宽度；
   高度统一 32px（与期间条其余控件一致），一条线上不高低错落。 */
.inv-bar input[type="month"] { width: 128px; height: 32px; font-size: 12.5px; }
.inv-bar select { min-width: 128px; }
.seg-btns { display: inline-flex; gap: 4px; }
.inv-bar .tb-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--separator-strong); border-radius: 1px; }
.inv-bar .searchbox { flex: 1 1 240px; max-width: 360px; }
.inv-bar .searchbox input { height: 32px; font-size: 12.5px; }
.adj-sum { font-size: 12.5px; color: var(--label-2); white-space: nowrap; }
.adj-sum b { font-size: 14px; font-weight: 650; margin: 0 2px; }
/* 库存余额表的双层表头：复用科目余额表的 tb-table，但分组是 2 / 2 / 2 / 3 ——
   期末多一列单价。tb-table 按「每两列一组」画的竖线到期末这一组要改：
   第 9 个数据格（期末结存的第一列）补一条，第二层表头第 9 格（期末金额）去掉一条。 */
table.grid.sb-table td:nth-child(9) { border-left: 1px solid var(--separator); }
table.grid.sb-table thead tr:last-child th:nth-child(9) { border-left: 0; }
/* 「暂估」：这一行本期有按暂估价结转的出库，成本还没定下来 */
.tag.sb-est { margin-left: 6px; height: 18px; padding: 0 6px; font-size: 11px; vertical-align: 1px; }
/* 出库金额里含成本调整时的小角标 */
.sb-adj {
  display: inline-block;
  margin-left: 4px;
  padding: 0 3px;
  border-radius: 4px;
  font-size: 10.5px;
  line-height: 15px;
  color: var(--amber);
  background: var(--amber-soft);
  vertical-align: 1px;
}

/* 手工凭证编辑页：借贷两列右对齐，底部差额不为 0 时标红 */
table.bill-table.vch-edit td input.num { text-align: right; }
.vch-ok { color: var(--green); font-size: 12.5px; font-weight: 600; }
.vch-bad { color: var(--red); font-size: 12.5px; font-weight: 600; }

/* ---------------- 系统设置弹窗 ----------------
   一行一个开关：勾选框 + 标题 + 一段说明。说明写足 —— 这些开关会改变
   系统的记账行为（比如允许负库存会让一段时间内的出库成本按 0 计），
   不把代价讲清楚，用户是凭感觉在拨开关。 */
/* 分区：功能开关 / 账务工具 / 危险操作。三块语义不同 ——
   勾选即生效、点一下跑一次、不可撤销 —— 混在一个长列表里用户分不清轻重。
   同系列设置放进一个圆角矩形框，标题在框内顶部。 */
.set-sec {
  border: 1px solid var(--separator);
  border-radius: 14px;
  background: var(--surface);
  padding: 8px;
}
.set-sec + .set-sec { margin-top: 14px; }
.set-sec-head {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--label-2);
  padding: 2px 6px 8px;
}
.set-list { display: flex; flex-direction: column; gap: 2px; }
/* 组间分隔线（税务设置里把「增值税」与「企业所得税」分开）。
   .set-list 是 flex 列，这里给一条贴着左右内边距的细线 ——
   两组开关连着排时，勾上增值税冒出来的税率框会像是挨着的所得税的参数。
   首项之前不画（:first-child），免得在分段标题下面多出一条线。 */
.set-sep {
  height: 1px;
  margin: 7px 14px;
  background: var(--separator);
  flex: none;
}
.set-sep:first-child { display: none; }
/* 三块合起来要在 900px 高的窗口里一屏放下（弹窗仍可滚，那是兜底不是常态），
   所以这里的 padding / 行高都压到了贴着可读下限的值，改大之前先量一遍。 */
.set-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 11px 14px;
  border-radius: 12px;
  cursor: pointer;
  transition: background .15s var(--ease);
}
.set-row:hover { background: var(--fill-2); }
.set-row .set-ck { width: 16px; height: 16px; flex: none; margin-top: 2px; cursor: pointer; }
.set-row .set-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.set-row .set-text b { font-size: 13.5px; font-weight: 600; color: var(--label); }
.set-row .set-hint { font-size: 12.5px; line-height: 1.55; color: var(--label-2); }
/* 设置页里的「这件事没生效」提示（目前用于后台调度器未启动）。
   用告警色而不是普通灰 —— 这一行说的是「你设的定时不会跑」，
   和旁边那些中性的说明文字必须一眼分得开。 */
.set-row .set-hint .set-warn { color: var(--red-ink, #a52222); font-weight: 600; }
/* 文本设置项（企业抬头等）：说明占左侧，输入框靠右，和工具行的按钮对齐成一条竖线。
   输入框给固定宽度而不是 flex —— 说明文字长短不一，输入框跟着伸缩会让各行左边界参差。 */
.set-field { cursor: default; align-items: center; padding: 8px 14px; }
.set-field .set-text { flex: 1; }
.set-field .set-tx,
.set-field .set-sel { flex: none; width: 190px; margin-left: 4px; }
/* 时间框（定时执行时间）：浏览器自带时钟图标 + HH:MM 掩码，190px 里图标会把
   数字挤到换行；收窄到刚够，并让数字等宽 —— 时分跳动时不会左右抖。
   <input type="time"> 的高度、圆角、边框都继承 .set-tx，这里只调宽度和字距。 */
.set-field .set-time { width: 132px; font-variant-numeric: tabular-nums; }
/* 这一行的控件是按钮（列宽调整），宽度与上面的输入框 / 下拉对齐成一条竖线 */
.set-field .btn { flex: none; min-width: 72px; margin-left: 4px; }
/* 用户管理：右侧的角色下拉 + 按钮组。比默认的 190px 下拉窄，行内放得下。 */
.set-user-ctl { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.set-user-ctl .set-sel { width: auto; min-width: 84px; margin-left: 0; }
.set-user-ctl .btn { min-width: 0; margin-left: 0; }
.set-user-add { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: none; }
.set-user-add .set-tx, .set-user-add .set-sel { width: auto; min-width: 0; margin-left: 0; }
.set-user-add .set-tx { width: 118px; }
/* 模型框那行：给「获取模型列表」弹出来的下拉面板一个定位基准 */
.set-model-row { position: relative; }
/* 输入框 + 下拉面板包在一起，让面板能贴着输入框下方、左对齐 */
.set-model-wrap { position: relative; flex: none; margin-left: 4px; }
.set-model-wrap .set-tx { margin-left: 0; }
.set-model-pop {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 40;
  display: none; flex-direction: column; gap: 2px;
  min-width: 100%; max-width: 320px; max-height: 240px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--separator-strong);
  border-radius: 10px; box-shadow: var(--shadow-pop); padding: 4px;
}
.set-model-pop.open { display: flex; }
.set-model-pop .opt {
  display: block; width: 100%; text-align: left; padding: 7px 10px;
  border: 0; border-radius: 6px; background: none; cursor: pointer;
  font-size: 12.5px; color: var(--label); line-height: 1.4;
  word-break: break-all;
}
.set-model-pop .opt:hover { background: var(--fill-2); }
/* AI 长期记忆：多行 textarea，比单行输入框宽、高 */
.set-field .set-tx-long {
  flex: 1; width: auto; min-height: 76px; resize: vertical;
  padding: 8px 10px; line-height: 1.6; font-size: 13px;
  border: 1px solid var(--separator-strong); border-radius: 8px;
  background: var(--surface); color: var(--label);
}
.set-field .set-tx-long:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-tint); }
/* AI 自动记忆列表：占整行右侧，一条一删 */
.set-mem-row { align-items: flex-start; cursor: default; }
.set-memlist { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* 自动记忆条目多时不无限拉高设置弹窗，限高后内部滚动 */
.set-memlist { max-height: 180px; overflow-y: auto; padding-right: 4px; }
.set-memlist::-webkit-scrollbar { width: 8px; }
.set-memlist::-webkit-scrollbar-thumb { background: var(--separator-strong); border-radius: 4px; }
.set-memlist::-webkit-scrollbar-track { background: transparent; }
.set-memlist .hint { font-size: 12px; color: var(--label-3); }
.set-mem-item { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--fill-2); border-radius: 8px; }
.set-mem-item .set-mem-txt { flex: 1; font-size: 12.5px; color: var(--label); line-height: 1.5; word-break: break-word; }
.set-mem-item .btn { flex: none; }

/* ---------------- 二级设置项（依附于某个开关的那几项）----------------
   「默认增值税率」「单价为含税价」依附于「启用增值税」。它们合并成一行、
   整体比一级小一档：层级关系靠「缩进 + 小字 + 同行」表达，
   而不是让每一项都占满一行高度 —— 那样二级看着和一级一样重，
   用户读不出谁管谁，税务段也会拉得很长。
   左边一道竖线 + 缩进，视觉上挂在上面那个开关之下。 */
.set-subwrap {
  /* 缩进对齐到一级标题的文字起点：14(行内边距) + 16(勾选框) + 11(间距) = 41 */
  margin: 2px 14px 6px 41px;
  padding: 7px 11px;
  border-left: 2px solid var(--separator);
  border-radius: 0 6px 6px 0;
  background: transparent;
}
/* 更深的依附层级（核算引擎模式 → 智能模式触发方式 → 定时执行时间）：
   在上一级的缩进里再退一格，竖线改成虚线、颜色更淡。
   一眼能看出「这一项依附的是上面那一项，不是最外层那个开关」——
   三层都顶格排的话，用户读不出「执行时间」到底跟着谁。 */
.set-subwrap .set-subwrap {
  margin: 4px 0 2px 16px;
  padding: 5px 0 5px 10px;
  border-left-style: dashed;
  border-left-color: var(--separator-strong);
}
.set-subrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 14px;
}
/* 每个二级项自己撑到统一高度后再垂直居中 —— 勾选框(15px)和输入框(30px)
   原生高度差一截，只靠容器的 align-items 对不齐，看着像是错开的两行。 */
.set-subrow .set-sub-item {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  min-height: 30px;
}
/* 二级一律 12.5px：比一级标题的 13.5px 小一档 */
.set-subrow .set-sub-label,
.set-subrow .set-sub-ck > span {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--label-2);
  white-space: nowrap;
}
.set-subrow .set-sub-ck { cursor: pointer; }
.set-subrow .set-sub-ck > span { cursor: pointer; }
.set-subrow .set-ck { width: 15px; height: 15px; flex: none; margin: 0; cursor: pointer; }
/* 二级输入框收窄：一行要并排放两项，190px 会撑破 */
.set-subrow .set-sub-ctl {
  flex: none;
  width: 92px;
  height: 30px;
  padding: 5px 9px;
  font-size: 12.5px;
}
.set-subrow select.set-sub-ctl { width: 126px; padding-right: 26px; }
/* 二级行里的时间框：92px 放不下「时钟图标 + HH:MM」，会截掉分钟 */
.set-subrow .set-time { width: 112px; font-variant-numeric: tabular-nums; }
/* 二级项的说明不再常驻成块 —— 说明改挂 title（悬停可见）。
   核算引擎那串原来把三条说明全列出来，比设置项本身还长，
   而它恰恰是二级项最多的那一处，最该瘦身。 */

/* ---------------- 单据列宽：可视化拖动（设置 → 单据列宽 → 调整…） ----------------
   预览按真实比例缩放，列宽用百分比 —— 弹窗宽度随窗口变时天然跟着缩，
   不必在 resize 时重算。拖动时才按容器实测宽度把像素换成毫米。 */
.cw-paper {
  background: #fff;
  border: 1px solid var(--line-2, #d6dae1);
  border-radius: 8px;
  padding: 12px;
  overflow: hidden;
  /* 宽度不能写死：平板竖屏（或窄窗口）下写死会撑破弹窗，
     手柄被推到可视区之外就拖不动了 —— 现在按容器自适应。 */
  width: 100%;
  box-sizing: border-box;
}
/* 分隔线靠绝对定位放在这一层上，所以它必须是定位上下文 */
.cw-inner { position: relative; }
.cw-tbl {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;          /* 列宽完全由 colgroup 决定，内容再长也不撑开 */
  font-size: 12px;
  color: #111;
}
.cw-tbl th, .cw-tbl td {
  border: 1px solid #888;
  padding: 5px 4px;
  text-align: left;
  /* 和真实打印一致：超长截断成省略号，这样预览里就能看出哪一列调窄了会丢字 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cw-tbl th { font-weight: 600; text-align: center; }
.cw-tbl td.num { text-align: right; }
.cw-tbl td.center { text-align: center; }
/* 拖动手柄：比视觉上的线宽得多（命中区域 11px），否则很难抓住。
   touch-action: none 是给平板用的 —— 不写的话手指按在分隔线上拖，
   浏览器会当成页面滚动手势，列宽纹丝不动、弹窗倒被翻走了。 */
.cw-h {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 11px;
  margin-left: -5px;
  cursor: col-resize;
  touch-action: none;
  z-index: 2;
}
.cw-h::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: transparent;
  transition: background .12s var(--ease);
}
.cw-h:hover::after { background: var(--primary, #2f6bff); width: 2px; left: 4px; }
/* 拖动中：整页禁选，光标全局保持 col-resize（否则划过表格会变成文本光标）。
   再禁掉触摸滚动 —— 手指从手柄滑出去之后，pointermove 还在冒泡，
   但浏览器同时也在滚页面，两边一起动会很晕。 */
body.cw-dragging { user-select: none; cursor: col-resize !important; touch-action: none; }
body.cw-dragging .cw-h::after { background: var(--primary, #2f6bff); width: 2px; left: 4px; }
/* 毫米数一行流式排开，列多也不会撑破弹窗 */
.cw-nums {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--label-2);
}
.cw-nums span { white-space: nowrap; }
/* 已经被拖到下限的那一列：标红 + 「下限」两个字。
   不说的话用户会以为「拖不动了 = 卡住了」，反复试着再拖一点点。 */
.cw-nums span.cw-min { color: #c0392b; }
.cw-nums span.cw-min i {
  font-style: normal;
  font-size: 11px;
  opacity: .75;
  margin-left: 2px;
}
.cw-nums b { font-weight: 400; color: var(--label-3, var(--label-2)); margin-right: 5px; }
.cw-nums .cw-total { margin-left: auto; color: var(--label-3, var(--label-2)); }
/* 工具行：文字占满，按钮靠右顶格，各行按钮因此是一条竖直线。
   上下 6px：整窗（三个开关 + 工具 + 危险操作）要压在 594px 以内 */
.set-tool { cursor: default; align-items: center; padding: 6px 14px; }
.set-tool .set-text { flex: 1; }
.set-tool-btn { flex: none; min-width: 72px; margin-left: 4px; }
.set-soon {
  display: inline-block;
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: 5px;
  font-size: 10.5px;
  font-style: normal;
  font-weight: 500;
  letter-spacing: .02em;
  color: var(--label-2);
  background: var(--fill-2);
  border: 1px solid var(--separator);
  vertical-align: 1px;
}
/* 危险区：底色偏红。不可撤销的操作不该和「勾一下就好」的开关长得一样。 */
.set-danger {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 14px;
  border-radius: 12px;
  border: 1px solid rgba(207, 43, 43, .22);
  background: rgba(207, 43, 43, .04);
}
.set-danger .set-hint { flex: 1; }
.set-danger .btn { flex: none; }
.set-danger-head { color: var(--red); }
/* 危险操作分组：外框也用红色调，内部提示条不再套一层边框 */
.set-sec-danger { border-color: rgba(207, 43, 43, .25); }
.set-sec-danger .set-danger { border: 0; }
/* 初始化弹窗里的危险区是「说明 + 输入框」，竖排 */
.set-danger.col { display: block; }
.set-danger.col .btn { margin-top: 10px; }
.set-danger.col input { margin-top: 8px; }
.set-list + .set-danger { margin-top: 14px; }   /* 初始化弹窗里危险区紧跟在范围单选后面 */
.set-danger .set-hint { font-size: 12.5px; line-height: 1.55; color: var(--label-2); }
.set-danger .btn { margin-top: 10px; }
.set-danger input { margin-top: 8px; }

/* ---------------- 表单 ---------------- */
.form-row { display: flex; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.field { flex: 1; min-width: 152px; }
.field label {
  display: block;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--label-2);
  margin-bottom: 6px;
}
.field label .req { color: var(--red); font-weight: 600; }
/* 字段下方的辅助说明 / 操作链接 */
.field .sub { margin-top: 7px; font-size: 12px; }
.field .sub .link { font-weight: 500; }

/* 表单控件（输入框 / 下拉框）共用同一套外观：同高、同边框、同圆角、同聚焦环 */
input,
select {
  width: 100%;
  font-family: inherit;
  font-size: 13.5px;
  line-height: 1.47;
  color: var(--label);
  background-color: var(--surface);
  border: 1px solid var(--separator-strong);
  border-radius: var(--r-field);
  padding: 8px 11px;
  height: 34px;          /* 与 .btn 同高，保证同一行内输入框 / 按钮基线对齐 */
  box-sizing: border-box;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease), background .18s var(--ease);
}
input::placeholder { color: var(--label-4); }
/* 实时库存结存数量后的整件散包括注：次要信息，压浅一档，和主数量分得开 */
.stk-pack { margin-left: 2px; color: var(--label-2); font-size: 12.5px; }
/* 下拉框的占位项（「请选择仓库」「请选择账户」）要和输入框的 placeholder 一样是灰字，
   否则它长得和一个真选中的值一模一样，用户看不出这一行还没选。
   做法：select 挂 required、占位项 value=""，未选时 select 处于 :invalid ——
   不靠 JS 切 class，选中、重建选项、刷新恢复哪条路径都自动对。
   展开后的选项列表要恢复正常颜色，不然整个下拉都是灰的。 */
select:invalid { color: var(--label-4); }
select:invalid option { color: var(--label); }
select:invalid option[value=""] { color: var(--label-4); }
input:hover:not(:focus):not([readonly]), select:hover:not(:focus) { border-color: rgba(22, 24, 29, .24); }
input:focus, select:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(47, 91, 215, .15);
}
input[readonly] {
  background-color: var(--surface-2);
  color: var(--label-2);
  border-color: var(--separator);
}

/* 隐藏原生数字输入框的微调箭头（Apple 风格不带这个） */
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; appearance: textfield; }

/* 下拉框自绘箭头 */
select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2386868b' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.2 6.4 8 10.2l3.8-3.8'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 15px;
  cursor: pointer;
}

/* 表格内的行内输入控件 */
table.grid input, table.grid select { padding: 6px 10px; font-size: 13px; }
table.grid select { padding-right: 28px; background-position: right 8px center; }

/* ---------------- 提示条 ---------------- */
.hint {
  font-size: 13px;
  line-height: 1.68;
  color: var(--label-2);
  background: var(--blue-soft);
  border: 1px solid rgba(47, 91, 215, .12);
  border-radius: 12px;
  padding: 13px 16px;
  margin-top: 16px;
}
/* 警示语义的提示条（如删除前的风险说明） */
.hint.warn {
  background: var(--amber-soft);
  border-color: rgba(178, 106, 0, .2);
  color: var(--amber);
}

/* ---------------- 轻提示（Toast） ---------------- */
.toast {
  position: fixed;
  top: 72px;
  left: 50%;
  z-index: 99;
  display: flex;
  align-items: center;
  gap: 9px;
  width: max-content;
  max-width: min(560px, 78vw);
  min-height: 42px;
  padding: 0 18px 0 14px;
  border: 1px solid rgba(255, 255, 255, .42);
  border-radius: 12px;
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: .1px;
  white-space: normal;
  text-align: left;
  transform: translateX(-50%);
  box-shadow: 0 8px 22px rgba(25, 32, 48, .22), 0 2px 6px rgba(25, 32, 48, .12);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  backdrop-filter: blur(14px) saturate(130%);
  animation: none !important;
  transition: none !important;
}
.toast::before {
  display: grid;
  place-items: center;
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  border: 1px solid rgba(255, 255, 255, .5);
  border-radius: 50%;
  font-size: 12px;
  font-weight: 800;
}
.toast.ok { background: rgba(30, 112, 72, .96); }
.toast.ok::before { content: '✓'; }
.toast.err { background: rgba(177, 45, 50, .96); }
.toast.err::before { content: '!'; }

/* ---------------- 开单页（金蝶式整页单据） ----------------
   整页固定布局：.main 在开单视图下变为纵向 flex 容器且不滚动——
   顶部动作条（返回列表/保存）作为首行常驻，与系统标题栏齐平锁死；
   抬头 + 明细网格 + 合计放在 .bill-scroll 内独立滚动。 */
.bill { animation: riseIn .34s var(--ease) both; }

/* 保存成功后的原地重渲染：抑制入场动画，避免整页"浮入"闪烁（导航渲染时 render() 会移除 no-rise） */
.main.no-rise .bill, .main.no-rise .card, .main.no-rise .card-head, .main.no-rise .metric { animation: none; }

/* ---- 列表页整屏布局 ----
   规则：页面本身不滚，整页恰好铺满一屏；只有列表体在真放不下时才出滚动条，
   表头吸顶、底部合计条钉底，两头都不跟着动；行数少时下方自然留白，页面照样铺满。
   和开单页的 .bill-mode 是同一套路，只是填充的对象从单据换成了列表卡。

   ⚠ 只给**挂了 .card.list-fill 的页面**开 list-mode：.main 在这个模式下是
   overflow:hidden，页面若没有可伸缩的列表卡，超出一屏的内容就彻底够不着了。
   两者必须成对出现，见 app.js 的 LIST_FILL_VIEWS。 */
.main.list-mode {
  display: flex;
  flex-direction: column;
  /* auto 而不是 hidden：正常情况下内容恰好铺满，这里不会出滚动条；
     但窗口矮到「列表之上的固定内容本身就超过一屏」时（资金管理那种上面压了
     一排账户卡 + 一张账户表的页面），hidden 会让超出的部分彻底够不着。
     留一条兜底的整页滚动，比把内容藏起来强。 */
  overflow: auto;
  /* 原来的 80px 下留白是给整页滚动留的余量，整屏布局里反而会白白吃掉一截高度 */
  padding-bottom: 26px;
  scroll-behavior: auto;
}
/* 标题、筛选条、提示语都按自身高度排；只有列表卡去抢剩余空间 */
.main.list-mode > * { flex: none; }
.main.list-mode .card.list-fill {
  flex: 1;
  /* 下限不设 0。.card 是 overflow:clip —— 一旦被上方的固定内容挤扁，
     卡里的表格会被直接裁掉而不是出滚动条，看上去就像整张表被删了（资金管理栽过）。
     给一个底线，挤不下就让整页滚（见上面 .main.list-mode 的 overflow:auto）。 */
  min-height: 228px;
  display: flex;
  flex-direction: column;
  margin-bottom: 0;
}
/* 列表卡外面还包了一层容器的（如实时库存的 #stockListWrap），高度要能传下去 */
.main.list-mode > div:has(> .card.list-fill) {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.main.list-mode .card.list-fill > .card-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* 列表体吃掉剩余高度。整屏布局下 62vh 那个上限不再需要 ——
   高度由布局算出来，写死一个比例反而会在高屏上白留一大块、在矮屏上还是不够。 */
.main.list-mode .card.list-fill .list-sticky {
  flex: 1;
  /* 下限不设 0：上面固定内容太多时（矮屏 + 资金管理那种页面）列表会被挤成 0 高，
     等于这张表凭空消失。给一个底线，挤不下就让整页滚（见上面的 overflow:auto）。 */
  min-height: 132px;
  max-height: none;
}
/* 合计条按自身高度，不参与伸缩（因此永远贴在卡片底边） */
.main.list-mode .card.list-fill > .card-body > .card-total-bar { flex: none; }
/* 空列表：让占位文案撑满并居中。不然它会贴在一张整屏高的空卡最顶上，
   下面空出一大片，看着像是没加载完 */
.main.list-mode .card.list-fill > .card-body > .empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 资金管理：上面的账户卡与账户表按自身高度，底下的资金流水吃掉剩余空间。
   应收应付：两列并排，各自的明细表同时撑满 —— 两张表是对照着看的，
   高度一致才好横着比；原来 align-items:start 会让两列各缩各的。 */
.main.list-mode .arap-cols { flex: 1; min-height: 0; align-items: stretch; }
.main.list-mode .arap-col { min-height: 0; }
.main.list-mode .arap-col .card.list-fill { margin-bottom: 0; }

/* 最后一行的下划线要加回来。
   全局的 `tbody tr:last-child td { border-bottom: none }` 是对的 ——
   表格原来恰好止于卡片底边，收口交给卡片边框。但整屏布局下表格下面是留白，
   末行没有线就像断在半空。表格正好铺满时，这条线与合计条/卡片底边重合，不会多出一道。 */
.main.list-mode .card.list-fill table.grid tbody tr:last-child td {
  border-bottom: 1px solid var(--separator);
}

.main.bill-mode {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
  scroll-behavior: auto;
}
.main.bill-mode .bill {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.bill-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: auto; /* 列宽拖宽后在此横向滚动，动作条不受影响 */
  padding: 18px 32px 48px;
}

/* 顶部动作条：常驻顶部（flex 首行），滚动内容从其下穿过 */
.bill-bar {
  flex-shrink: 0;
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 32px;
  background: rgba(244, 245, 248, .94);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--separator);
}
.bill-bar .bill-title { font-size: 19px; font-weight: 700; letter-spacing: -.4px; color: var(--label); }
.bill-bar .bill-no {
  font-size: 12px;
  font-weight: 500;
  color: var(--label-2);
  background: var(--fill-2);
  padding: 4px 11px;
  border-radius: var(--r-pill);
  font-variant-numeric: tabular-nums;
}
.bill-bar .tag { margin-left: 2px; }
.bill-bar .spacer { flex: 1; }

/* 抬头区 */
/* 开单抬头资料卡：比普通表单更紧凑，减少顶部占用但保持控件统一 34px 高 */
.bill-head .card-body { padding: 10px 16px 4px; }
.bill-head .form-row { gap: 10px; margin-bottom: 8px; }
.bill-head .field { min-width: 138px; }
.bill-head .field label { font-size: 11.5px; margin-bottom: 4px; }
.bill-head .field .sel-add { gap: 6px; }
/* 「联系人 + 联系电话 + 联系地址」这一行不等分：前两个都是短内容（姓名、号码），
   地址可以很长。等分会让地址被截断，而前两格空掉大半。
   前两个各占 1/4，地址吃掉剩下的一半 —— .form-row 的 gap 是 16px，三列两道缝。 */
.bill-head .form-row.contact-addr .field:nth-child(1),
.bill-head .form-row.contact-addr .field:nth-child(2) {
  flex: 0 0 calc((100% - 32px) / 4);
}
.bill-head .form-row.contact-addr .field:last-child { flex: 1; }
/* 抬头「当前欠款」：欠款红、预收/预付绿。它是只读派生值，
   染色是为了一眼分清方向 —— 「−2000」很容易被读反成「我们欠对方」。
   购销单（#b_due）与收付款单（#ss_due）两处抬头同一个字段、同一套染色。 */
#b_due, #ss_due { font-variant-numeric: tabular-nums; font-weight: 500; }
#b_due.due-owing, #ss_due.due-owing { color: var(--red); }
#b_due.due-advance, #ss_due.due-advance { color: var(--green); }

/* 明细表格：单元格输入框常显（清晰可见，不必悬停摸索），聚焦时高亮；
   输入框一律铺满所在列（width:100%），配合固定列宽实现行行严格对齐，不再溢出单元格 */
/* 明细卡片：横向滚动条贴卡片底部；列宽拖宽后表格在圆角框内横移 */
.bill-grid { overflow-x: auto; overflow-y: hidden; }
/* 内层随表格自然宽度伸展：min-width:100% 保证窄表也不塌成内容宽度 */
.bill-grid-x > .bill-grid-inner { display: inline-block; min-width: 100%; vertical-align: top; }
/* 卡片内横向滚动条：细长贴底（悬停加深），不抢视觉 */
.bill-grid-x::-webkit-scrollbar { height: 7px; }
.bill-grid-x::-webkit-scrollbar-track { background: transparent; }
.bill-grid-x::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, .2);
  background-clip: content-box;
  border: 2px solid transparent;
  border-radius: 5px;
}
.bill-grid-x::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, .38); background-clip: content-box; }
/* 合计金额下方的另一口径小字（「不含税 1,000.00」/「价税合计 1,130.00」）。
   它在**合计金额这一格内部**、数字的正下方 —— 挂到旁边的单价格里会被读成
   「单价的不含税」，而它说的是整单金额。
   10.5px 是全表最小的一档（正文 / 表头 13.5，这里 10.5）：它是个附注，
   不参与「合计」那一行的阅读节奏，字号拉开才不会和合计金额抢视线。

   **不能用 nowrap**：nowrap 下放不下就会横着顶出单元格、压到隔壁税金列。
   也**不能用 overflow-wrap: anywhere**：那会把数字从中间劈开（「1,000.」/「00」）。
   只在标签与数字之间那个空格处断，数字永不拆散。

   另外这一格的左右内边距收窄到 6px：合计行的金额格默认 14px/14px，
   96px 的列宽里内容区只剩 67px，而「不含税 1,234,000.00」单行要 91px ——
   于是常规金额也被折成两行。内边距是给「合计」那种短文字留的，
   装两行数字的格子不需要那么宽的留白。
   用显式类 .bill-amtcell 而不是 :has()，旧内核也认。
   为空时不占高度（:empty）。 */
table.bill-table tfoot td.bill-amtcell {
  padding-left: 6px;
  padding-right: 6px;
  /* **合计金额数字本身**要和同行其它数字（「合计」、数量合计、税额合计）
     落在同一条中线上。
     只给 td 设 vertical-align:middle 不够 —— 那把「数字 + 小字」两行作为
     一个整体居中，数字因此被小字往上顶了 7px，一眼看去就是没对齐。
     做法：td 保持单行时的居中基准，小字用绝对定位挂在数字下方、
     **不参与这一格的高度计算**，于是数字自己坐在正中，小字浮在它下面。
     relative 让小字有定位上下文；合计行本身比内容高（44px），放得下。 */
  position: relative;
  vertical-align: middle;
}
table.bill-table tfoot .bill-taxnote {
  /* 绝对定位：不占这一格的高度，所以上面的合计金额仍与同行其它数字对齐。
     贴着数字下方（top:100% 相对于下面那条 margin 之后的位置由 margin-top 微调），
     右对齐到和金额同一条右边线。 */
  position: absolute;
  right: 6px;
  top: 50%;
  margin-top: 9px;           /* 数字半高 + 一点间距，刚好落在它下面 */
  font-size: 10.5px;
  font-weight: 400;
  line-height: 1.25;
  color: var(--label-3);
  white-space: nowrap;       /* 绝对定位后不再受格宽约束，可以单行 */
  font-variant-numeric: tabular-nums;
  pointer-events: none;      /* 纯展示，不挡住底下的点击 */
}
table.bill-table tfoot .bill-taxnote:empty { display: none; }
/* 合计行里的金额格装了两行（合计数 + 小字），行高放开一点，避免挤到边框 */
table.bill-table tfoot td { vertical-align: middle; }
/* 税金列：只读的派生值，比「金额」压浅一档 —— 它是算出来的，不是录入的。
   合计行里的税额合计则保持正常字重（它和金额合计是并列的两个数）。 */
table.bill-table td.it-tax {
  color: var(--label-2);
  font-variant-numeric: tabular-nums;
}
table.bill-table tfoot #billTaxTotal { font-variant-numeric: tabular-nums; }

/* 明细行统一行高（输入框 + 参考价提示高度） */
table.bill-table tbody tr { height: 60px; }
table.bill-table tfoot tr { height: 44px; }
table.bill-table { font-size: 13.5px; }
table.bill-table th { white-space: nowrap; padding: 10px 8px; }
table.bill-table td { padding: 6px 8px; }
/* 只读文字列（规格 / 金额）允许换行：这两列装的是「不可断的长 token」——
   规格如 100*200*300mm、金额如 123456789.99，默认白空格下浏览器找不到断点，
   会横向溢出单元格（实测 scrollWidth 161 > clientWidth 83）而不是换行。
   overflow-wrap:anywhere 让超长 token 也能在任意位置断行，配合 word-break
   兜底旧内核，保证列被拖窄时文字纵向排开、绝不撑破单元格。 */
table.bill-table td.b-spec,
table.bill-table td.it-amt {
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
  line-height: 1.35;
}
/* 明细列分隔：数据行仅在列与列之间画一条几乎不可见的淡线（表头不画，避免与手柄叠成双线；
   备注列右侧已到滚动区尽头，不再画竖线）——只做极轻的分栏提示 */
table.bill-table tbody td,
table.bill-table tfoot td { border-right: 1px solid rgba(0, 0, 0, .035); }
/* 备注列（滚动区最右一列）：右侧不画竖线。
   原来这里除了 :last-child 还写死了 :nth-child(10) —— 明细表加一列（本次加了「仓库」）
   就会指到倒数第二列，备注列反而又画上竖线。:last-child 本身已经够用，删掉写死的那条。 */
table.bill-table tbody td:last-child,
table.bill-table tfoot td:last-child { border-right: none; }

/* 表头标题居中、字重略加强，与居中的数据列形成清晰的栏位感 */
table.bill-table thead th,
table.bill-table thead th.num {
  text-align: center;
  font-weight: 600;
  letter-spacing: .1px;
}
/* 行首序号列：默认显示序号，鼠标悬停该行时序号淡出、删除图标淡入（同位置切换，不抖动） */
table.bill-table .b-idx {
  text-align: center;
  color: var(--label-3);
  font-variant-numeric: tabular-nums;
}
table.bill-table .b-delcell { position: relative; padding-left: 4px; padding-right: 4px; }
table.bill-table .b-idx-no {
  display: block;
  line-height: 1;
  transition: opacity .16s ease;
}
table.bill-table .b-del {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 22px;
  height: 22px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: 6px;
  color: var(--label-3);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity .16s ease, color .16s ease, background-color .16s ease;
}
table.bill-table .b-del svg { width: 15px; height: 15px; display: block; }
table.bill-table tr:hover .b-idx-no,
table.bill-table tr:focus-within .b-idx-no { opacity: 0; }
table.bill-table tr:hover .b-del,
table.bill-table tr:focus-within .b-del { opacity: 1; pointer-events: auto; }
table.bill-table .b-del:hover { color: var(--red); background: var(--red-soft); }
table.bill-table .b-del:active { transform: scale(.92); }
table.bill-table thead th.bill-th-del { color: var(--label-3); font-weight: 600; }

/* ---------------- 触屏（平板）：悬停是拖不出来的 ----------------
   行首的删除图标默认 opacity:0，靠 tr:hover 才显形。平板没有悬停 ——
   手指点上去的那一刻才触发 hover，图标在「按下」之前根本不存在，
   用户看到的是一个空的行号格，删不掉行。
   所以触屏设备上一律常显：hover:none 是标准媒体特性，鼠标设备不受影响。
   顺带把命中区放大到 30px —— 22px 是照着鼠标光标定的，指尖太大点不准。 */
@media (hover: none), (pointer: coarse) {
  table.bill-table .b-del,
  table.bill-table tr:hover .b-del,
  table.bill-table tr:focus-within .b-del {
    opacity: 1;
    pointer-events: auto;
    width: 30px;
    height: 30px;
  }
  table.bill-table .b-del svg { width: 18px; height: 18px; }
  /* 行号让位：常显的图标和行号抢同一个格子 */
  table.bill-table .b-idx-no,
  table.bill-table tr:hover .b-idx-no,
  table.bill-table tr:focus-within .b-idx-no { opacity: 0; }
  /* 其它拖拽手柄同理放大到指尖尺寸（列宽手柄 11px 是给鼠标的） */
  .col-rz { width: 18px; }
}
table.bill-table td input {
  width: 100%;
  box-sizing: border-box;
  min-width: 0;
  border: 1px solid rgba(0, 0, 0, .17);
  background-color: var(--surface);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .025);
  padding: 6px 8px;
  border-radius: 8px;
}
/* 行内下拉和行内输入框必须是同一套尺寸。
   原来这里只写了 input，select 就还吃着全局表单样式（左 10 / 右 28 的内边距）——
   在 96px 宽的仓库列里，select 内容区只剩 39px，而「请选择仓库」要 65px，
   文字直接溢出压到右侧的箭头上（实测，不是估计）。
   收窄内边距 + 把箭头挪近、改小，内容区回到 75px 左右。 */
table.bill-table td select {
  padding: 6px 20px 6px 8px;
  background-position: right 5px center;
  background-size: 13px;
  border: 1px solid rgba(0, 0, 0, .17);
  border-radius: 8px;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .025);
}
table.bill-table td select:hover:not(:focus) { border-color: rgba(0, 0, 0, .3); }
table.bill-table td input:hover:not(:focus) { border-color: rgba(0, 0, 0, .3); background-color: var(--surface); }
table.bill-table td input:focus { background-color: var(--surface); }
table.bill-table td input:disabled {
  color: var(--label-3);
  background: var(--fill-2);
  box-shadow: none;
  border-color: var(--separator);
}
/* 数值列输入右对齐，与右对齐表头、金额列基线一致 */
table.bill-table input.it-qty,
table.bill-table input.it-price,
table.bill-table input.it-pack,
table.bill-table input.it-pack-x,
table.bill-table input.it-pack-y {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* 隐藏数字输入框的步进箭头：避免挤占列宽、上下行边缘参差 */
table.bill-table input[type=number]::-webkit-inner-spin-button,
table.bill-table input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
table.bill-table input[type=number] { -moz-appearance: textfield; appearance: textfield; }
/* 行反馈：只在「鼠标悬停」时给整行一层极淡底，聚焦/编辑时不整行染色。
   之前用 tr.b-active（focus 触发）会把整行刷成蓝底，点进商品检索框选商品时
   整行发蓝、尤其是最左侧没有输入框的 # 列最扎眼，故改为纯悬停提示。 */
table.bill-table tbody tr:hover td { background: var(--row-hover); }
table.bill-table tfoot td {
  font-weight: 600;
  background: var(--surface-2);
  border-top: 1px solid var(--separator);
  border-bottom: none;
  padding: 11px 14px;
}
/* 合计行「合计」标签右对齐，紧贴数量合计数字 */
table.bill-table tfoot td:nth-child(2) {
  text-align: right;
  color: var(--label-2);
  letter-spacing: 2px;
}
.bill-addrow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid var(--separator);
}
/* 「重置列宽」紧挨着「添加一行」放（与收付款明细一致）。
   原来是 margin-left:auto 推到最右边，表格一宽，两个按钮就被扯到屏幕两头。 */

/* 单据锁定态（已保存未审核 / 已审核）：表单不可改——隐藏删除图标与添加行，禁用控件弱化显示 */
.bill.locked .b-del { display: none !important; }
/* 行号让位是为了给删除图标腾位置；锁定态图标已经 display:none，再让位就只剩
   「鼠标扫过 → 行号淡出到 0 → 移开又淡回来」的空闪。把让位规则关掉。
   选择器要带 table.bill-table 才压得过 (0,3,2) 的原规则。 */
.bill.locked table.bill-table tr:hover .b-idx-no,
.bill.locked table.bill-table tr:focus-within .b-idx-no { opacity: 1; }
/* 收付款单与开单页同一套锁定规则：锁定后「添加一行」也要收起来 */
.bill.locked #billAddRow,
.bill.locked #ss_add_row { display: none; }
.bill.locked .bill-scroll input:disabled, .bill.locked .bill-scroll select:disabled,
.bill.locked .bill-scroll button:disabled {
  opacity: .72;
  cursor: default;
  background: var(--fill-2);
}
.bill.locked .bill-scroll .b-hint { opacity: .5; }

/* 明细表头吸顶：在 .bill-scroll 内锁定顶格，紧贴返回列表动作条下方 */
table.bill-table { table-layout: fixed; }
table.bill-table thead th {
  position: sticky;
  top: 0;
  z-index: 12;
  overflow: visible;          /* 让列宽手柄可溢出到右侧邻列之上 */
  box-shadow: 0 1px 0 var(--separator);
}
/* 手柄所在列（非最后一列）提升层级，保证 ⋮ 压在右侧邻列的表头背景之上 */
table.bill-table thead th:hover { z-index: 15; }
/* 表头灰底行两端补圆角，与卡片圆角一致（border-collapse:separate 下 th 圆角可见），
   修复「表格顶部直角、底部圆角」的不一致 */
table.bill-table thead th:first-child { border-top-left-radius: calc(var(--r-card) - 1px); }
/* 右圆角给最后一列（备注）——它的右缘正好压在卡片圆角上（差额由 JS 补给该列） */
table.bill-table thead th:last-child { border-top-right-radius: calc(var(--r-card) - 1px); }

/* 表头右缘 9px 热区：普通列骑在列边界线上（半在左半在右，便于抓取） */
.col-rz {
  position: absolute;
  top: 0;
  right: -4px;
  width: 9px;
  height: 100%;
  cursor: col-resize;
  z-index: 14;
  user-select: none;
  touch-action: none;
}
/* 拖拽手柄：表头右缘常显一枚浅浅的竖排三点「⋮」，落在列边界线上（提示该列可拖）。
   悬停到具体某列时变蓝放大 —— 常显保证随时能发现可拖拽，颜色克制不抢视觉 */
.col-rz::after {
  content: '⋮';
  position: absolute;
  right: -1px;
  top: 50%;
  transform: translateY(-50%);
  width: 11px;
  font-size: 13px;
  font-weight: 400;
  line-height: 1;
  text-align: center;
  color: rgba(0, 0, 0, .26);
  transition: color .14s var(--ease), transform .14s var(--ease);
}
/* 最后一列（备注）：手柄必须完全收在列内，绝不溢出到滚动区右缘（否则卡片右侧会露白） */
table.bill-table thead th:last-child .col-rz { right: 0; }
table.bill-table thead th:last-child .col-rz::after { right: 0; }
table.bill-table thead th:hover .col-rz::after,
body.col-resizing .col-rz::after {
  color: var(--blue);
  transform: translateY(-50%) scale(1.18);
}
body.col-resizing { cursor: col-resize !important; user-select: none; }
body.col-resizing iframe { pointer-events: none; }

/* 开单明细：整件散包「x箱 + y片」并列输入框，flex 铺满整列与列边界对齐 */
table.bill-table td.it-pack-cell { white-space: nowrap; }
table.bill-table .it-pack-inner { display: flex; align-items: center; gap: 3px; width: 100%; }
table.bill-table .it-pack-sep { color: var(--label-3); padding: 0 1px; font-size: 12.5px; flex-shrink: 0; }
table.bill-table .it-pack-inner .it-pack-x { flex: 1; width: auto; }
table.bill-table .it-pack-inner .it-pack-y { flex: 1.18; width: auto; }

/* 开单页选择控件旁「＋ 新增」按钮 */
.sel-add { display: flex; gap: 8px; align-items: center; }
.sel-add select { flex: 1; min-width: 0; }
.sel-add input[readonly] { flex: 1; min-width: 0; cursor: pointer; }
.sel-add .btn-sm { flex-shrink: 0; }

/* 商品搜索下拉（min-width:0 允许该单元格随列宽收缩，不撑破固定列宽布局） */
.b-prodcell { position: relative; min-width: 0; }
.b-prodcell .b-search { width: 100%; }
/* 参考价提示：绝对定位挂在单元格垂直中线下方，不参与排版。
   此前它在流内固定占 16px（14px 高 + 2px 间距），把编码输入框整体往上顶 ——
   编码框的中心比同一行的数量/单价框高出 8px，一行控件根本对不齐。
   行高本来就由 tr{height:60px} 兜底，不需要这块空 div 来撑；去掉之后，
   有参考价的行和没参考价的行高度也彻底一致了。
   margin-top 取「输入框半高 17px + 1px 间距」，所以它永远贴在输入框下缘。 */
.b-prodcell .b-hint {
  position: absolute;
  left: 8px;              /* 与单元格左右内边距对齐 */
  right: 8px;
  top: 50%;
  margin-top: 18px;
  height: 12px;
  font-size: 10px;
  line-height: 12px;
  color: var(--label-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;   /* 纯提示，不挡输入框和下拉的点击 */
}
.combo {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  z-index: 30;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--shadow-pop);
  max-height: 264px;
  overflow-y: auto;
  padding: 5px;
  border: 1px solid var(--separator);
  min-width: 320px; /* 列宽压缩时检索下拉仍保持可读宽度 */
}
.combo-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 11px;
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
  transition: background .12s var(--ease);
}
.combo-item:hover, .combo-item.sel { background: var(--fill-2); }
.combo-item .combo-code { color: var(--label-3); flex-shrink: 0; }
.combo-item .combo-sub { margin-left: auto; font-size: 12px; color: var(--label-3); flex-shrink: 0; }
.combo-empty { padding: 12px; text-align: center; color: var(--label-3); font-size: 12.5px; }

/* 底部结算区 */
/* 收付款明细复用销售单表格的竖线、圆角和三点列宽手柄 */
.settle-loading-card { min-height: 180px; }
.pick-page-bar { margin: 8px 0 0; border: 1px solid var(--separator); border-radius: var(--r-card); }
.pick-page-bar .btn:disabled { opacity: .45; cursor: default; }
.apply-pick-body { display: flex; flex-direction: column; min-height: 0; padding: 0; }
.apply-pick-head-search { display: flex; align-items: center; gap: 8px; margin-left: 12px; min-width: 0; }
.apply-pick-head-search .searchbox { width: min(360px, 32vw); }
.apply-pick-head-search .page-info { white-space: nowrap; }
.apply-pick-table-wrap { flex: 1 1 auto; min-height: 180px; max-height: calc(66vh - 105px); overflow: auto; }
.apply-pick-table { min-width: 900px; }
.apply-pick-table thead th { position: sticky; top: 0; z-index: 12; background: var(--surface-2); box-shadow: 0 1px 0 var(--separator); }
.apply-pick-hint { color: var(--label-2); font-size: 12px; white-space: nowrap; }
.dialog:has(.apply-pick-body) { max-width: 1120px; }
.dialog:has(.apply-pick-body) .dialog-foot { flex: 0 0 auto; }
/* 退货原单选择与收付款选单保持同一布局：标题/检索固定，列表独立滚动，分页放底栏。 */
.origin-pick-body { display: flex; flex-direction: column; min-height: 0; padding: 0; overflow: hidden; }
.origin-pick-head-search { display: flex; align-items: center; gap: 8px; margin-left: 12px; min-width: 0; }
.origin-pick-head-search .searchbox { width: min(360px, 32vw); }
.origin-pick-wrap { flex: 1 1 auto; min-height: 180px; max-height: calc(66vh - 105px); overflow: auto; padding: 0; }
.origin-pick-wrap .ps-listwrap { min-height: 0; max-height: none; overflow: visible; border: 0; border-radius: 0; width: 100%; }
.origin-pick-wrap .ps-listwrap table { width: 100%; min-width: 900px; }
.origin-pick-wrap .ps-listwrap thead th { position: sticky; top: 0; z-index: 12; background: var(--surface-2); box-shadow: 0 1px 0 var(--separator); }
.origin-pick-hint { color: var(--label-2); font-size: 12px; white-space: nowrap; }
.origin-pick-pager { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.origin-pick-pager span { color: var(--label-2); font-size: 12px; }
.dialog:has(.origin-pick-body) { max-width: 1120px; }
.dialog:has(.origin-pick-body) .dialog-foot { flex: 0 0 auto; }
.settle-loading-body {
  min-height: 140px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--label-3);
  font-size: 13px;
}
.settle-items { overflow-x: auto; overflow-y: hidden; }
.settle-items .bill-grid-inner { display: inline-block; min-width: 100%; vertical-align: top; }
.settle-items table.bill-table { table-layout: fixed; min-width: 720px; }
.settle-items table.bill-table th:nth-child(1) { width: 58px; }
.settle-items table.bill-table th:nth-child(2) { width: 190px; }
.settle-items table.bill-table th:nth-child(3) { width: 150px; }
.settle-items table.bill-table th:nth-child(4) { width: 300px; }
.settle-items table.bill-table th { border-right: none; }
.settle-items table.bill-table td { border-right: 1px solid rgba(0, 0, 0, .035); }
.settle-items table.bill-table th:last-child,
.settle-items table.bill-table td:last-child { border-right: none; }
.settle-items table.bill-table .b-delcell { text-align: center; vertical-align: middle; padding-left: 4px; padding-right: 4px; }
.settle-items table.bill-table .b-idx-no { display: inline-block; width: 100%; text-align: center; }
.settle-items table.bill-table .ss-row-remark { width: 100%; }
.settle-items table.bill-table tfoot tr { height: 44px; }
.settle-items .bill-addrow { position: relative; }

/* ---------------- 核销明细 ----------------
   和账户明细表共用 .bill-grid / table.bill-table 这套底盘（吸顶表头、列宽手柄、
   锁定态样式全都白送），只换一份列宽与行高。
   行高压到 44px：这张表只有最后一列可输入，其余都是只读文本，
   按账户明细的 60px 排，一个客户欠十笔就把页脚顶到屏幕外面去了。 */
.writeoff-items { overflow-x: auto; overflow-y: hidden; }
.writeoff-items .bill-grid-inner { display: inline-block; min-width: 100%; vertical-align: top; }
.writeoff-items table.bill-table { table-layout: fixed; min-width: 1086px; }
.writeoff-items table.bill-table th:nth-child(1) { width: 44px; }
.writeoff-items table.bill-table th:nth-child(2) { width: 96px; }
.writeoff-items table.bill-table th:nth-child(3) { width: 160px; }
.writeoff-items table.bill-table th:nth-child(4) { width: 98px; }
.writeoff-items table.bill-table th:nth-child(5) { width: 112px; }
.writeoff-items table.bill-table th:nth-child(6) { width: 100px; }
.writeoff-items table.bill-table th:nth-child(7) { width: 100px; }
.writeoff-items table.bill-table th:nth-child(8) { width: 108px; }
.writeoff-items table.bill-table th:nth-child(9) { width: 108px; }
.writeoff-items table.bill-table th:nth-child(10) { width: 160px; }
/* 核销单只有 9 列（没有优惠那一列）：它一分钱不走资金，每一笔核销按定义
   全是不走资金的，那一列只能永远等于「本次核销」。末列（摘要）相应加宽。 */
.writeoff-items.wo-grid-9 table.bill-table { min-width: 1018px; }
.writeoff-items.wo-grid-9 table.bill-table th:nth-child(9) { width: 200px; }
.writeoff-items.wo-grid-9 table.bill-table th:nth-child(10) { width: auto; }
/* 两张表各自的小标题（来源 / 去向）。放在卡片**外面** ——
   它是这张表的标签，不是表里的一行内容，压在圆角矩形里会像是表格的一部分。
   上边距兼作两张卡之间的间隔，所以卡片本身不再另设 margin-top。 */
.wo-cardtitle,
.settle-wo-title {
  margin: 18px 2px 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--label-2);
}
.settle-wo-title { margin-top: 18px; }
.bill-scroll > .settle-wo-title:first-child { margin-top: 0; }
.bill-scroll > .wo-cardtitle:first-child { margin-top: 0; }
.writeoff-items table.bill-table .wo-note { color: var(--label-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.writeoff-items table.bill-table th { border-right: none; }
.writeoff-items table.bill-table td { border-right: 1px solid rgba(0, 0, 0, .035); }
.writeoff-items table.bill-table th:last-child,
.writeoff-items table.bill-table td:last-child { border-right: none; }
/* 行高 48px：比账户明细的 60px 紧凑（这张表只有一个输入框），
   但要容得下行首的「行号↔删除图标」切换按钮。 */
.writeoff-items table.bill-table tbody tr { height: 48px; }
.writeoff-items table.bill-table tfoot tr { height: 42px; }
.writeoff-items table.bill-table .b-delcell { text-align: center; vertical-align: middle; padding-left: 4px; padding-right: 4px; }
.writeoff-items table.bill-table .b-idx-no { display: inline-block; width: 100%; text-align: center; }
/* 单号格：复用开单页商品格的定位底座，行内下拉绝对定位挂在它下面 */
.writeoff-items table.bill-table .wo-nocell { position: relative; min-width: 0; }
/* 单号用等宽字体，和表格里其它 .mono 单号对得齐（没有 --mono 变量，直接写同一套栈） */
.writeoff-items table.bill-table .wo-search {
  width: 100%;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
  letter-spacing: -.1px;
}
/* 还没选单据的格子用占位灰，一眼看出哪几行是空的 */
.writeoff-items table.bill-table .wo-type,
.writeoff-items table.bill-table .wo-date { color: var(--label-2); }
.writeoff-items table.bill-table .wo-amt,
.writeoff-items table.bill-table .wo-disc { width: 100%; text-align: right; font-variant-numeric: tabular-nums; }
/* 折让列用琥珀色底描边：它和现金不是一回事，录错了后果也不同（写掉的是债权） */
.writeoff-items table.bill-table .wo-disc { border-color: rgba(201, 138, 20, .35); }
.writeoff-items table.bill-table .wo-disc:focus { border-color: var(--amber); box-shadow: 0 0 0 4px rgba(201, 138, 20, .14); }
/* 填了金额的行：左侧一道品牌蓝竖条 + 淡底，一眼扫出这单核销了哪几张 */
.writeoff-items table.bill-table tbody tr.wo-picked td { background: rgba(26, 115, 232, .055); }
.writeoff-items table.bill-table tbody tr.wo-picked td:first-child { box-shadow: inset 2px 0 0 var(--blue); }
.writeoff-items .bill-addrow { position: relative; }
.wo-bar .spacer { flex: 1; }
.wo-tip { font-size: 12px; color: var(--label-3); margin-left: 4px; }
/* 汇总与「本次核销」列左对齐：偏移量由 JS 按表头实测位置写进 --wo-sum-left
   （列宽可以拖，写死对不上）。绝对定位是为了不受左侧按钮宽度影响。 */
.wo-bar { flex-wrap: wrap; }
.wo-bar .wo-sum {
  position: absolute;
  left: var(--wo-sum-left, auto);
  right: auto;
  /* 贴着第一行（按钮那一行）垂直居中，而不是整条 bar 居中 ——
     下面的警告语换行之后，整条 bar 会变高，用 50% 就会飘到两行中间。 */
  top: 10px;
  height: 30px;
  display: flex;
  align-items: center;
  white-space: nowrap;
}
/* 超额警告独占一行：它可能很长，和汇总挤在同一行会压上去 */
.wo-bar .wo-warn { width: 100%; margin-left: 0; }
/* 核销单的页脚只有「整单备注」一项（没有优惠可填），所以是单列；
   其余版式（标签同行、宽度、内边距）与收付款单页脚共用 .settle-footer。
   业务类型的说明原来是一个圆角提示框，现在挂在下拉的 title 上 ——
   那个框每切一次类型就改一次高度，把下面整张表往下顶，很晃眼。 */
.settle-footer.wof-footer { grid-template-columns: minmax(0, 1fr); }
/* 成对业务（应收冲应付 / 应收转应收 / 应付转应付）的两个往来单位：
   并排放在第二行，中间一个 → 点出方向。拆成两行读不出「谁转给谁」，
   而分到第一行末尾又会被业务类型、日期挤扁。
   align-items: flex-end 让箭头落在输入框那一档高度上，而不是跟标签对齐。 */
.wo-pairrow { flex: 1; display: flex; align-items: flex-end; gap: 10px; min-width: 0; }
.wo-pairrow > .field { flex: 1; min-width: 0; }
.wo-pairrow .wo-arrow {
  flex: 0 0 auto;
  height: 34px;            /* 与 input 同高，箭头落在两个输入框的垂直中线上 */
  line-height: 34px;
  font-size: 15px;
  color: var(--label-3);
  user-select: none;
}
/* 购销单列表：关联收付款单（一笔分几次收就列几行）+ 结清状态 */
.settled-cell { line-height: 1.5; }
.settled-line { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
.settled-line + .settled-line { margin-top: 2px; }
.settled-amt { margin-left: auto; color: var(--label-3); font-size: 12px; font-variant-numeric: tabular-nums; }
.settled-none { color: var(--label-3); }
/* 颜色已经把状态说清楚了，字重再加粗就是重复强调，一列红绿黄的粗体很闹。
   500 与表单标签同一档（见 .settle-footer label）。 */
.settle-state { font-weight: 500; font-size: 12.5px; white-space: nowrap; cursor: help; }
.settle-state.done { color: var(--green); }
.settle-state.part { color: var(--amber); }
.settle-state.open { color: var(--red); }
.return-state { font-size: 12.5px; white-space: nowrap; }
.return-state.none { color: var(--label-3); }
.return-state.part { color: var(--amber); }
.return-state.all { color: var(--green); }
/* 应收冲应付的对方一侧原来是一张轻量表格（.wof-pairbox），
   现在统一成和上表一模一样的核销表（来源 / 去向），那套样式随之删掉。 */
.wo-sum { font-size: 12.5px; color: var(--label-2); font-variant-numeric: tabular-nums; }
/* 中文标签和数字之间要留口气：汉字与数字之间没有天然字距，不留就黏成一坨 */
.wo-sum b { color: var(--label); font-weight: 600; margin-left: 5px; }
.wo-warn { font-size: 12.5px; color: var(--red); font-weight: 600; margin-left: 12px; }
/* 锁定态（已审核 / 已保存）把核销区的动作按钮藏掉，与明细表的「添加一行」同一条规矩。
   单独一条规则而不是并进上面那条：那条的选择器被回归测试按字面匹配着。 */
.bill.locked #wo_add_row,
.bill.locked #wo_auto,
.bill.locked #wo_clear,
.bill.locked .wo-tip { display: none; }
/* 往来明细里还没核销完的金额标红：这张表就是用来找「谁还欠着」的 */
.wo-open-left { color: var(--red); font-weight: 600; }
/* 详情弹窗里的小节标题（核销明细） */
.detail-sub {
  margin: 16px 0 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--label-2);
}

/* ---------------- 单据页脚：整单优惠 / 整单备注 ----------------
   购销单只有「整单备注」（.bill-remark），收付款单是「整单优惠 + 整单备注」两列
   （.settle-footer）。两边共用同一套标签与控件外观：
     · 标签与控件同一行，标签在左 —— 页脚只有一两个字段，标签压在上方会白白
       多占一行高度，还和上方明细表的横向节奏对不齐；
     · 标签 = 表单字段那一档（12.5px / 500 / 次级色），不再比输入框本身还抢眼；
     · 控件一律用单行 input（含备注），走全局 input 的白底描边 + 四像素聚焦环，
       页脚不再自带一套灰底 —— 灰底会让可编辑的备注看起来像已经锁定，
       和真正的锁定态分不开。 */
.settle-footer,
.bill-remark .card-body { padding: 14px 18px; }
.settle-footer {
  display: grid;
  grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  margin-bottom: 14px;
}
/* 「标签 + 控件」横排。三处用同一条规则，两边标签宽度才会一致 */
.settle-discount,
.settle-remark,
.bill-remark .card-body {
  display: flex;
  align-items: center;
  gap: 10px;
}
.settle-footer label,
.bill-remark label {
  flex: none;
  width: 4.6em;   /* 「整单备注」四个字刚好一行放下，不会在中间折断 */
  margin: 0;
  color: var(--label-2);
  font-size: 12.5px;
  font-weight: 500;
}
/* 控件吃掉剩余宽度；min-width:0 是必须的，否则 input 的默认最小宽度
   会把 flex 容器顶宽，收付款单右侧又会冒出留白。 */
.settle-footer input,
.bill-remark input { flex: 1; min-width: 0; }
.bill-remark .card-body { padding-top: 10px; padding-bottom: 10px; }
.bill-attachments .card-body { padding: 14px 18px; }
.bill-attach-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.bill-attach-head strong { font-size: 13px; font-weight: 650; color: var(--label); }
.bill-attach-tip { margin-left: 10px; color: var(--label-3); font-size: 12px; }
.bill-attach-readonly { color: var(--label-3); font-size: 12px; }
.bill-attachments-locked { background: var(--surface-2); }
.bill-attachments-locked .bill-attach-tip { color: var(--label-3); }
.bill-attach-pick { position: relative; overflow: hidden; cursor: pointer; }
.bill-attach-pick input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.bill-attachments-grid { display: flex; flex-wrap: wrap; gap: 12px; min-height: 28px; }
.bill-attach-empty { color: var(--label-3); font-size: 12px; padding: 6px 0; }
.bill-attachment { width: 132px; position: relative; }
.bill-attachment a { display: block; height: 96px; overflow: hidden; border: 1px solid var(--separator); border-radius: 8px; background: var(--fill-2); }
.bill-attachment img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bill-attachment-name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; margin-top: 5px; font-size: 11px; color: var(--label-2); }
.bill-attachment-del { width: 100%; margin-top: 5px; height: 26px; min-height: 26px; }
/* 未保存单据的暂存附件：虚线框 + 「待保存」标签，与已落库附件区分开 */
.bill-attachment-pending a { border-style: dashed; border-color: var(--blue); }
.bill-attach-pending-tag { color: var(--blue); font-weight: 600; }
.settle-footer input:disabled { opacity: .72; background: var(--fill-2); }

/* .bill-sum（开单页底部那张「合计数量 / 合计金额 / 有效明细行数」圆角卡片）已删除：
   明细表的 tfoot 里本来就有同样的合计，而且就在对应数字那一列的正下方，对位更清楚。 */

/* ---------------- 商品档案：单位换算表单 ---------------- */
.u-ckline {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
}
.u-ck {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.u-ck input {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--blue);
  cursor: pointer;
}
.conv-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--surface-2);
  border: 1px solid var(--separator);
  border-radius: 12px;
  padding: 10px 14px;
}
.conv-field { display: flex; align-items: center; gap: 7px; }
.conv-field input { width: 110px; text-align: right; }
.conv-unit { font-size: 12.5px; color: var(--label-2); white-space: nowrap; }
.conv-eq { color: var(--label-3); font-weight: 600; }
.conv-hint { font-size: 12.5px; color: var(--label-3); padding: 2px 2px; }

/* ---------------- 开单页：商品超级检索弹窗 ---------------- */
/* 商品超级检索：搜索框与下方「分类 + 列表」之间留足间距（原来贴太近） */
.ps-body {
  display: flex;
  gap: 14px;
  min-height: 320px;
  max-height: 52vh;
  margin-top: 16px;
}
.ps-cats {
  width: 152px;
  flex-shrink: 0;
  overflow-y: auto;
  border-right: 1px solid var(--separator);
  padding-right: 8px;
}
.ps-cat {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
  color: var(--label);
  transition: background .12s var(--ease);
}
.ps-cat span { color: var(--label-3); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.ps-cat:hover { background: var(--fill-2); }
.ps-cat.on { background: var(--blue); color: #fff; font-weight: 600; }
.ps-cat.on span { color: rgba(255, 255, 255, .78); }
.ps-cat-node { justify-content: flex-start; padding-left: calc(8px + var(--cat-depth, 0) * 16px); }
.ps-cat-node > span:last-child, .ps-cat[data-cat-kind="all"] > span:last-child, .ps-cat[data-cat-kind="uncat"] > span:last-child { margin-left: auto; }
.ps-cat-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-cat-toggle, .ps-cat-toggle-empty { width: 16px; height: 20px; flex: 0 0 16px; border: 0; padding: 0; background: transparent; color: currentColor; font-size: 12px; line-height: 20px; text-align: center; }
.ps-cat-toggle { cursor: pointer; }
.ps-cat-children { display: none; }
.ps-cat-children.open { display: block; }
.ps-cat-children .ps-cat { font-size: 12.5px; }
.pcat-tree { max-height: 420px; overflow: auto; border: 1px solid var(--separator); border-radius: 10px; padding: 6px; }
.pcat-tree-row { display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 4px 8px 4px calc(8px + var(--cat-depth, 0) * 22px); border-radius: 7px; }
.pcat-tree-row:hover { background: var(--fill-2); }
.pcat-tree-toggle, .pcat-tree-empty { width: 18px; height: 22px; flex: 0 0 18px; border: 0; padding: 0; background: transparent; color: var(--label-2); text-align: center; cursor: pointer; }
.pcat-tree-name { min-width: 100px; font-weight: 600; color: var(--label); }
.pcat-tree-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--label-3); font-size: 12px; }
.pcat-tree-count { color: var(--label-3); font-size: 12px; white-space: nowrap; }
.pcat-tree-children { display: none; }
.pcat-tree-children.open { display: block; }
.ai-cat-tree { border: 1px solid var(--separator); border-radius: 9px; padding: 5px; }
.ai-cat-row { display: flex; align-items: center; gap: 7px; min-height: 28px; color: var(--label); font-size: 12px; }
.ai-cat-mark { width: 14px; color: var(--blue); }
.ai-cat-path { flex: 1; min-width: 0; color: var(--label-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-cat-count { color: var(--label-3); white-space: nowrap; font-size: 11px; }
.ps-listwrap {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  border: 1px solid var(--separator);
  border-radius: 10px;
}
.ps-listwrap table { margin: 0; }
.ps-listwrap tbody tr { cursor: pointer; user-select: none; }
/* 检索列表：悬停走全局那一层浅遮罩，选中/勾选才加深 —— 顺序即优先级，
   后面的 .sel / .picked 要压过前面的 :hover（同权重，写在后面才生效）。
   同样要带 table.grid 前缀，否则压不过通用的 table.grid tbody tr:hover。 */
.ps-listwrap table.grid tbody tr:hover { background: var(--row-hover); }
.ps-listwrap table.grid tbody tr.sel { background: var(--row-sel); }
/* 多选已勾选行：主题蓝浅底 + 左缘蓝色竖条 + 编码列加粗变蓝（弃用绿色） */
.ps-listwrap table.grid tbody tr.picked { background: var(--row-sel); }
.ps-listwrap tbody tr.picked td:first-child { box-shadow: inset 3px 0 0 var(--blue); }
.ps-listwrap tbody tr.picked .mono { color: var(--blue); font-weight: 600; }
.ps-listwrap table.grid tbody tr.picked:hover { background: var(--row-sel-hover); }
.ps-listwrap table.grid tbody tr.picked.sel { background: var(--row-sel-strong); }
.ps-foot { margin-top: 12px; font-size: 12px; color: var(--label-3); }
/* 检索行与下方列表的间距：搜索框不能贴着列表（与商品检索弹窗 .ps-body 的 16px 同规格） */
.ps-searchrow { margin-bottom: 16px; }
/* 弹窗底部提示行（键盘操作说明），位于列表下方、按钮区上方 */
.ps-hint { margin-top: 12px; font-size: 12px; color: var(--label-3); }
.ps-hint #ps_count { font-variant-numeric: tabular-nums; }
/* 弹窗底部按钮区：支持把左侧按钮与右侧按钮分开（保持右对齐） */
.dialog-foot .spacer { flex: 1; }

/* 多选弹窗：每行勾选框（纯指示用，点击落在行上统一处理） */
.ps-ckcol { width: 38px; }
.ps-ck {
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: var(--blue);
  pointer-events: none; /* 勾选状态由行点击统一驱动，避免双次切换 */
  vertical-align: middle;
}

/* ---------------- 通用文本工具 ---------------- */
.mono {
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
  font-size: 12.5px;
  letter-spacing: -.1px;
}
.link {
  color: var(--blue);
  cursor: pointer;
  text-decoration: none;
  font-weight: 500;
  transition: opacity .15s var(--ease);
}
.link:hover { text-decoration: underline; }
.link:active { opacity: .6; }

/* ---------------- 动效 ---------------- */
@keyframes riseIn {
  from { opacity: 0; transform: translateY(7px); }
  to { opacity: 1; transform: none; }
}
@keyframes billButtonStateIn {
  from { opacity: 0; transform: translateY(-4px) scale(.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes billTagStateIn {
  from { opacity: 0; transform: translateY(-3px) scale(.94); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes maskIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes dialogIn {
  from { opacity: 0; transform: scale(.965) translateY(10px); }
  to { opacity: 1; transform: none; }
}
/* toast 已采用静态呈现，不定义入场动画，避免连续提示出现位移/缩放闪烁 */

/* 尊重系统的「减弱动态效果」设置 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .metric:hover { transform: none; }
  /* 位移/缩放靠 transform，不吃上面那条 duration 压缩 —— 要单独关掉 */
  .cash-cards .card.total.clickable-card:hover,
  .cash-cards .card.total.clickable-card:active,
  .cash-cards .card.total.clickable-card.card-pulse { transform: none; animation: none; }
}

/* ---------------- 窄屏适配 ---------------- */
@media (max-width: 1180px) {
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* 窗口窄到两列挤不下时退回上下排列，否则单位名会被压得换行 */
  .arap-cols { grid-template-columns: minmax(0, 1fr); }
  .arap-cols .card { margin-bottom: 14px; }
}
@media (max-width: 900px) {
  .sidebar { width: 156px; }
  .main { padding: 20px 18px 64px; }
  .page-head h2 { font-size: 23px; }
  .metric .v { font-size: 22px; }
  .bill-bar { padding: 10px 18px; }
  .bill-scroll { padding: 14px 18px 40px; }
}
@media (max-width: 640px) {
  .sidebar { display: none; }
  .topbar { padding: 0 14px; }
  .topbar .period { display: none; }
  .metrics { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------- 打印当前页面（页头「打印」按钮 → window.print） ----------------
   只留表格本身：顶栏、侧栏、按钮、检索框、筛选卡都不进纸面。
   整屏列表平时是「表格自己滚、页面不滚」，打印时要把滚动区全部展开，否则只打出第一屏。
   单张单据的打印不走这里（printDoc 另开一页排成 A4 单据）。 */
@media print {
  .topbar, .sidebar, #toastHost, #dialogHost, .page-head .btn, .toolbar, .filter-bar,
  .card-total-bar .n, .doc-actions, .col-rz { display: none !important; }
  .layout { display: block !important; }
  .main, .main.list-mode { overflow: visible !important; height: auto !important; padding: 0 !important; }
  .card, .card.list-fill { box-shadow: none !important; border: 1px solid #ccc; overflow: visible !important; height: auto !important; }
  .list-sticky { max-height: none !important; overflow: visible !important; height: auto !important; }
  .list-sticky thead th, table.grid.gl-table tfoot td { position: static !important; }
  tr.gl-fill { display: none !important; }
  .rpt-two { grid-template-columns: 1fr 1fr; }
  body { background: #fff !important; }
}

/* 补填参考进价 / 关闭负库存确认 */
.refp-tip { margin: 0 0 8px; line-height: 1.6; }
.refp-note { color: var(--muted, #7a8496); font-size: 12px; }
.refp-spec { color: var(--muted, #7a8496); font-size: 12px; }
.refp-table { width: 100%; margin-top: 4px; }
.refp-table .refp-in { text-align: right; }
.negc-wrap { max-height: 220px; overflow: auto; }
.negc-qty { color: var(--red); font-weight: 600; }
.negc-list { margin: 10px 0 8px; padding-left: 20px; line-height: 1.7; }
/* 销售单列表：出库成本含成本调整单（悬停看拆分） */
table.grid td.cost-adjusted { text-decoration: underline dotted; text-underline-offset: 3px; cursor: help; }
/* ============================================================
   ���ܺ���״̬��ʾ
   ============================================================ */
.smart-costing-status {
  display: inline-flex;
  align-items: center;
  margin: 0 12px;
  padding: 4px 12px;
  background: #f0f4f8;
  border-radius: 4px;
  font-size: 12px;
  color: #64748b;
  white-space: nowrap;
}

.smart-costing-status .status-label {
  color: #94a3b8;
  margin-right: 4px;
}

.smart-costing-status .status-time {
  color: #475569;
  font-weight: 500;
}

.smart-costing-status .status-running {
  color: #f59e0b;
  font-weight: 600;
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.smart-costing-status.running {
  background: #fef3c7;
}

/* ============================================================
   智能核算进度覆盖层
   ============================================================ */
.costing-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.costing-progress-box {
  background: white;
  border-radius: 8px;
  padding: 32px 48px;
  min-width: 500px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}

.costing-progress-title {
  font-size: 18px;
  font-weight: 600;
  color: #1e293b;
  margin-bottom: 24px;
  text-align: center;
}

.costing-progress-bar {
  height: 8px;
  background: #e2e8f0;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 16px;
}

.costing-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #3b82f6, #2563eb);
  border-radius: 4px;
  width: 0%;
  transition: width 0.3s ease;
}

.costing-progress-text {
  font-size: 14px;
  color: #64748b;
  text-align: center;
}

/* ============================================================
   增量模式下「记账凭证」列表的锁
   ------------------------------------------------------------
   增量核算只重算受影响的单据，它产出的凭证是**中间态**：
   凭证号可能为空（号是全量引擎排的），内容也会被下一次全量重建替换。
   那不是能给人看的账，所以整张列表盖住，中间只留一个「展示凭证」按钮 ——
   点它 = 显式跑一次全量核算（成本引擎全量重算 + 凭证全量重建），
   跑完凭证才是真的，这时才展示。

   一举解决两件事：展示出去的一定是全量引擎的产物（准确），
   日常审核又不必为了「凭证好看」付全量重算的代价（性能）。
   ============================================================ */
.vch-lock {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 半透明 + 轻微模糊：既挡住内容，又让人看得出底下是张列表 */
  background: rgba(244, 245, 248, .74);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  border-radius: var(--r-card);
}
.vch-lock-box {
  max-width: 480px;
  padding: 24px 28px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
}
.vch-lock-box h3 {
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 500;
  color: var(--label);
}
.vch-lock-box p {
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--label-2);
}
/* 锁上的原因（最近一次改动是什么）—— 只说「锁了」不说为什么，用户会当成 bug */
.vch-lock-box .vch-lock-why {
  margin: 0 0 16px;
  padding: 8px 10px;
  background: var(--amber-soft);
  border-radius: 8px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--label-2);
  text-align: left;
}
.vch-lock-box .vch-lock-note {
  margin: 12px 0 0;
  font-size: 12px;
  line-height: 1.7;
  color: var(--label-3);
}

/* ============================================================
   账务自检里的「嫌疑手工凭证」
   ------------------------------------------------------------
   只报「差 2 元」用户是查不动的 —— 2 万张凭证里翻，谁也翻不出来。
   差额的来源基本只有手工凭证（业务凭证都由单据生成，改单据就能改回来），
   所以自检把落在被对账科目上的手工分录直接列出来，并给一个「查看」直达。
   ============================================================ */
.verify-suspects {
  margin: 6px 0 0;
  padding: 8px 10px;
  background: var(--amber-soft);
  border-radius: 8px;
  font-size: 12px;
  line-height: 1.6;
  text-align: left;
}
.verify-suspects > b { color: var(--amber-ink); font-weight: 500; }
.verify-suspect {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
  color: var(--label-2);
}
.verify-suspect .vs-date { color: var(--label-3); }
.verify-suspect .vs-amt { color: var(--label); }
.verify-suspect .vs-sum { color: var(--label-3); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.verify-suspects .set-hint { color: var(--label-2); }

/* ============================================================
   智能助手（右下角悬浮球 + 对话面板）
   ------------------------------------------------------------
   球贴在右下角；面板从球的位置往上展开。
   左下角是「成本引擎」状态条，两边不打架。
   配色走全站令牌，球用品牌蓝渐变（--grad-blue），跟主按钮同一套。
   ============================================================ */
.ai-ball {
  position: fixed;
  right: 16px;
  bottom: 16px;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--grad-blue);
  box-shadow: 0 6px 20px rgba(47, 91, 215, .38), 0 2px 6px rgba(22, 30, 60, .18);
  cursor: pointer;
  z-index: 300;
  transition: transform .16s ease, box-shadow .16s ease;
}
.ai-ball:hover { transform: translateY(-2px) scale(1.04); box-shadow: 0 10px 26px rgba(47, 91, 215, .45), 0 3px 8px rgba(22, 30, 60, .2); }
.ai-ball:active { transform: translateY(0) scale(.98); }
.ai-ball svg { width: 24px; height: 24px; fill: #fff; }
/* 有未读时右上角一个小红点 */
.ai-ball-dot {
  position: absolute;
  top: 2px; right: 2px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--red);
  border: 2px solid var(--surface);
}

.ai-panel {
  position: fixed;
  right: 16px;
  bottom: 72px;
  width: 380px;
  max-width: calc(100vw - 32px);
  height: 520px;
  max-height: calc(100vh - 110px);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  z-index: 300;
  overflow: hidden;
}
.ai-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--separator);
  background: var(--surface-2);
}
.ai-logo { color: #3C5DFF; display: inline-flex; align-items: center; flex: none; }
.ai-logo svg { width: 20px; height: 20px; fill: currentColor; }
.ai-title { display: inline-flex; align-items: center; gap: 6px; color: #3C5DFF; font-size: 14px; font-weight: 700; letter-spacing: -0.01em; font-family: "Arial Rounded MT Bold", "SF Pro Rounded", "Nunito", "Quicksand", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
.ai-title .ai-title-cn { font-size: 12px; font-weight: 600; letter-spacing: .1px; color: var(--label); }
.ai-sub { font-size: 11px; color: var(--label-3); }
.ai-head .spacer { flex: 1; }
.ai-x {
  border: 0; background: none; cursor: pointer;
  font-size: 12px; color: var(--label-3); padding: 3px 6px; border-radius: 6px;
}
.ai-x:hover { background: var(--fill-2); color: var(--label); }

.ai-msgs { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.ai-msg { max-width: 86%; font-size: 13px; line-height: 1.65; white-space: pre-wrap; word-break: break-word; }
.ai-msg.user {
  align-self: flex-end;
  background: var(--grad-blue);
  color: #fff;
  padding: 8px 12px;
  border-radius: 12px 12px 3px 12px;
}
.ai-msg.bot {
  align-self: flex-start;
  background: var(--surface-2);
  color: var(--label);
  padding: 8px 12px;
  border-radius: 12px 12px 12px 3px;
}
.ai-msg.bot.err { background: var(--red-soft); color: var(--red-ink); }
.ai-msg.bot.wait { color: var(--label-3); }
/* 工具调用轨迹：折叠起来，平时不占地方，排查时才看 */
.ai-trace { font-size: 11px; color: var(--label-4); margin-top: 4px; }
.ai-trace summary { cursor: pointer; }

/* 草稿卡片 —— 助手产出的「待确认单据」 */
.ai-draft {
  align-self: stretch;
  border: 1px solid var(--separator-strong);
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(22, 30, 60, .04);
}
.ai-draft-head {
  position: relative;
  display: flex; align-items: center; gap: 6px;
  padding: 8px 12px;
  background: var(--blue-soft);
  font-size: 12.5px; font-weight: 600; color: var(--label);
}
/* 左侧一道品牌色条：一眼分清「这是待确认的草稿」 */
.ai-draft-head::before {
  content: '';
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--grad-blue);
}
.ai-draft-head .tag { margin-left: auto; }
.ai-draft-body { padding: 10px 12px; font-size: 12px; color: var(--label-2); }
.ai-draft-body .row { display: flex; gap: 8px; padding: 2.5px 0; }
.ai-draft-body .row .k { color: var(--label-3); flex: none; width: 56px; }
.ai-draft-body .row > span:last-child { color: var(--label); }
.ai-draft-body table { width: 100%; border-collapse: collapse; margin: 8px 0 0; }
.ai-draft-body th, .ai-draft-body td { padding: 4px 5px; font-size: 12px; text-align: left; border-bottom: 1px solid var(--separator); }
.ai-draft-body th { color: var(--label-3); font-weight: 500; background: var(--surface-2); }
.ai-draft-body th:first-child { border-top-left-radius: 6px; }
.ai-draft-body th:last-child { border-top-right-radius: 6px; }
.ai-draft-body td.num, .ai-draft-body th.num { text-align: right; font-variant-numeric: tabular-nums; }
.ai-draft-body .total {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 8px; padding: 8px 0 2px;
  border-top: 1px dashed var(--separator-strong);
  font-size: 13px; color: var(--label);
}
.ai-draft-body .total b {
  color: var(--blue); font-size: 15px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ai-draft-foot {
  padding: 6px 12px; border-top: 1px solid var(--separator);
  display: flex; gap: 8px; align-items: center;
  background: var(--surface-2);
}
/* 按钮后面的注释用纯文字（不带框）：全局 .hint 是提示条样式（背景+边框+内边距），
   这里全部覆盖掉，只留文字，行高与 .btn-sm 对齐 */
.ai-draft-foot .hint {
  font-size: 12.5px; color: var(--label); line-height: 28px;
  background: none; border: 0; border-radius: 0;
  padding: 0; margin: 0;
}

.ai-foot {
  display: flex; gap: 8px; align-items: flex-end;
  padding: 10px 12px;
  border-top: 1px solid var(--separator);
}
.ai-foot textarea {
  flex: 1;
  resize: none;
  max-height: 96px;
  padding: 8px 10px;
  font-size: 13px;
  line-height: 1.5;
  border: 1px solid var(--separator-strong);
  border-radius: 10px;
  font-family: inherit;
  color: var(--label);
  background: var(--surface);
}
.ai-foot textarea:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-tint); }
.ai-tip { padding: 0 12px 9px; font-size: 11px; color: var(--label-4); }

/* 密钥设置行：密码框 + 「清除」并排。
   输入框里**不放真值**（后端只回掩码），靠 placeholder 说明配没配、
   留空是什么意思 —— 这样既不会把密钥送进浏览器，用户也看得懂当前状态。 */
.set-secret { display: flex; align-items: center; gap: 8px; flex: none; }
.set-secret input { width: 260px; }
.set-secret-clear { flex: none; color: var(--red); border-color: var(--separator-strong); }
.set-secret-clear:hover { background: var(--red-soft); }

/* 基础资料草稿卡片：和单据草稿同一个壳，只换一下头部底色区分。
   绿底=已创建（点完按钮后把卡片改成这个状态，防重复点）。 */
.ai-draft-master .ai-draft-head { background: var(--green-soft); }
.ai-draft-body .hint { font-size: 11px; color: var(--label-3); }

/* 改档案 / 改设置草稿卡片：展示「原值 → 新值」。
   头部用琥珀底（待确认），与「新建」（蓝底）区分开。 */
.ai-draft-update .ai-draft-head,
.ai-draft-setting .ai-draft-head { background: var(--amber-soft); }
.ai-draft-body .ai-from { color: var(--label-4); text-decoration: line-through; }
.ai-draft-body .ai-arrow { color: var(--label-4); margin: 0 6px; }
.ai-draft-body .ai-to { color: var(--label); font-weight: 500; }

/* 查单据的结果卡片：每行可直接打开 / 打印。
   ------------------------------------------------------------------
   模型只会说「查到 5 张」，用户要的是能点开 —— 所以把结果渲染成表格。

   ⚠ 表格很宽（单号 + 类型 + 日期 + 往来单位 + 状态 + 金额 + 两个按钮），
   而对话面板只有 380px。原来的写法是整张表 `white-space: nowrap` 且
   没有横向滚动容器，列一多就把右边的「打开 / 打印」**挤出面板外**被裁掉
   —— 用户看得到按钮的位置却点不到。
   修法：
     · 表格外面包一层 `.ai-doclist-scroll`（overflow-x: auto）——
       列宽不够时出现横向拖动条，两个按钮永远够得着；
     · **表头 sticky 在左**（left: 0）—— 横移到右边时表头不会跟着跑掉；
     · 「操作」列也可以在窄面板里被推出去，所以**首列（单号）sticky 更实用**：
       横移时始终知道自己在看哪一行。
   面板自身仍是 overflow: hidden（靠 scroll 容器滚动，不是整块溢出）。 */
.ai-doclist {
  border: 1px solid var(--separator-strong);
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(22, 30, 60, .04);
}
.ai-doclist-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--separator);
  font-size: 12px; color: var(--label-2);
}
.ai-doclist-head b { color: var(--blue); font-weight: 700; }
.ai-doclist-head .hint { font-size: 11px; color: var(--label-4); }
/* 横向滚动容器：列超出宽度时出现拖动条（系统自带的细滚动条即可） */
.ai-doclist-scroll { overflow-x: auto; overflow-y: hidden; }
.ai-doclist-scroll::-webkit-scrollbar { height: 8px; }
.ai-doclist-scroll::-webkit-scrollbar-thumb { background: var(--separator-strong); border-radius: 4px; }
.ai-doclist-scroll::-webkit-scrollbar-track { background: transparent; }
.ai-doclist table { width: 100%; border-collapse: separate; border-spacing: 0; }
.ai-doclist th, .ai-doclist td {
  padding: 4px 6px; font-size: 12px; text-align: left;
  border-bottom: 1px solid var(--separator); white-space: nowrap;
}
.ai-doclist th {
  color: var(--label-3); font-weight: 500; background: var(--surface-2);
  border-bottom: 1px solid var(--separator-strong);
}
.ai-doclist td.num, .ai-doclist th.num { text-align: right; font-variant-numeric: tabular-nums; }
.ai-doclist td.num { color: var(--label); }
.ai-doclist tbody tr:last-child td { border-bottom: 0; }
.ai-doclist tbody tr:hover td { background: var(--fill-2); }
/* 首列（单号）横移时固定在左边，始终知道在看哪一行。
   ⚠ sticky 列背景**必须不透明**：--fill-2 是半透明 rgba，横移后滚到单号下面
   的其他列会透过它露出来，跟单号文字重叠。所以用不透明的 surface / surface-2。 */
.ai-doclist th:first-child, .ai-doclist td:first-child {
  position: sticky; left: 0; z-index: 1;
  background: var(--surface);
}
.ai-doclist thead th:first-child { background: var(--surface-2); }
.ai-doclist tbody tr:hover td:first-child { background: var(--surface-2); }
.ai-doclist-act { white-space: nowrap; }
.ai-doclist-act .btn + .btn { margin-left: 4px; }

/* 报价单卡片（AI 面板）：表格预览 + 下载 Word */
.ai-quote-scroll { overflow-x: auto; overflow-y: hidden; }
.ai-quote-scroll::-webkit-scrollbar { height: 8px; }
.ai-quote-scroll::-webkit-scrollbar-thumb { background: var(--separator-strong); border-radius: 4px; }
.ai-quote-scroll::-webkit-scrollbar-track { background: transparent; }
.ai-quote-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.ai-quote-table th, .ai-quote-table td {
  padding: 4px 6px; font-size: 12px; text-align: center;
  border-bottom: 1px solid var(--separator); white-space: nowrap;
}
.ai-quote-table th {
  color: var(--label-3); font-weight: 500; background: var(--surface-2);
  border-bottom: 1px solid var(--separator-strong);
}
.ai-quote-table tbody tr:last-child td { border-bottom: 0; }

/* 联网结果卡片：每条来源一个可点标题 + 摘要。
   外站链接一律 target="_blank" + rel="noopener noreferrer"（防标签页劫持）。 */

/* 「展开更多」——长列表不一次全塞进 380px 的面板。
   每页 10 条，点一次翻一页；翻到底按钮自己消失。
   翻页走 /api/ai/more-docs（原样条件 + page），不重跑模型。 */
.ai-doclist-more {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 10px;
  border-top: 1px solid var(--separator);
  background: var(--surface-2);
}
/* 「还有 N 张没显示」用正文样式（不再是小号浅灰 hint），和「展开更多」按钮文字对齐 */
.ai-doclist-more .more-txt { font-size: 12px; color: var(--label-2); line-height: 28px; }
.ai-doclist-more .btn { margin-left: auto; }

/* ===== 成本计算器（喷涂工程板 · 新版） ===== */
.paint-cost-page { display: flex; flex-direction: column; gap: 14px; padding: 4px; }

/* 顶部横幅 */
.paint-head { background: linear-gradient(135deg, #17395f, #3f6f9f); color: #fff; border-radius: 16px; padding: 18px 22px; display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; box-shadow: 0 10px 28px -14px rgba(20,45,100,.5); }
.paint-head .pht { font-size: 19px; font-weight: 800; letter-spacing: .2px; text-shadow: 0 1px 2px rgba(0,0,0,.18); }
.paint-head .phs { font-size: 12px; opacity: .85; margin-top: 3px; }
.paint-head-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.paint-hbtn { border: 1px solid rgba(255,255,255,.42); background: rgba(255,255,255,.14); color: #fff; padding: 7px 16px; border-radius: 999px; cursor: pointer; font-size: 13px; font-weight: 600; transition: background .15s; }
.paint-hbtn:hover { background: rgba(255,255,255,.26); }
.paint-hbtn.ghost { background: rgba(255,255,255,.05); }

/* 卡片（毛玻璃） */
.paint-card { background: rgba(255,255,255,.72); border: 1px solid #e5e8ef; border-radius: 16px; box-shadow: 0 1px 1px rgba(30,50,90,.04), 0 14px 34px -26px rgba(30,60,130,.28), inset 0 1px 0 rgba(255,255,255,.8); overflow: hidden; }

/* 分区标题 */
.paint-sec-head { display: flex; align-items: center; gap: 8px; padding: 14px 18px; border-bottom: 1px solid var(--separator); position: relative; }
.paint-sec-head::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 17px; border-radius: 2px; background: linear-gradient(180deg, #3a68e6, #2f5bd7); }
.paint-sec-head b { font-size: 15px; font-weight: 650; color: var(--label); }
.paint-sec-sub { font-size: 12px; color: var(--label-3); }
.paint-tag { font-size: 11px; padding: 2px 9px; border-radius: 999px; font-weight: 600; }
.paint-tag.req { background: #fdecea; color: #c0392b; }
.paint-tag.opt { background: var(--blue-soft); color: var(--blue); }
.paint-clear { margin-left: auto; border: 1px solid #e0e5ee; background: #fff; color: var(--label-3); border-radius: 8px; padding: 3px 12px; font-size: 12px; cursor: pointer; transition: all .12s; }
.paint-clear:hover { border-color: #ff3b30; color: #ff3b30; background: #fff5f5; }

/* 核心参数速算：左必填大卡 + 右结果卡 */
.paint-hero-cols { display: grid; grid-template-columns: 1.15fr .85fr; gap: 14px; padding: 16px 18px 4px; }
@media (max-width: 1100px) { .paint-hero-cols { grid-template-columns: 1fr; } }
.paint-bigs { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 10px; }
@media (max-width: 640px) { .paint-bigs { grid-template-columns: 1fr; } }
.paint-big { background: #fff; border: 1px solid #e1e7ef; border-radius: 14px; padding: 12px 14px; display: flex; flex-direction: column; gap: 6px; transition: border-color .15s, box-shadow .15s; }
.paint-big:focus-within { border-color: rgba(0,113,227,.7); box-shadow: 0 0 0 3px rgba(0,113,227,.14); }
.paint-big label { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 13px; font-weight: 600; color: var(--label); }
.paint-big label em { color: #ff3b30; font-size: 10px; border: 1px solid #ffd1cf; background: #fff3f2; border-radius: 6px; padding: 1px 7px; font-weight: 600; font-style: normal; }
.pbig-inp { display: flex; align-items: center; gap: 8px; }
.pbig-inp input { flex: 1; min-width: 0; height: 42px; font-size: 20px; font-weight: 600; color: var(--label); text-align: right; border: 1px solid #d5dde7; border-radius: 10px; padding: 0 12px; background: #fff; outline: none; font-variant-numeric: tabular-nums; transition: border-color .15s, box-shadow .15s; }
.pbig-inp input:focus { border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.16); }
.pbig-inp input::placeholder { color: #aebccd; font-weight: 400; font-size: 15px; }
.pbig-inp span { font-size: 12px; color: var(--label-3); white-space: nowrap; }
.pbig-tip { font-size: 11px; color: var(--label-3); line-height: 1.5; }
.pbig-tip b { color: var(--label); font-variant-numeric: tabular-nums; }

/* 结果卡 */
.paint-ress { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 560px) { .paint-ress { grid-template-columns: 1fr; } }
.paint-res { border-radius: 13px; padding: 13px 16px; position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: center; min-height: 78px; }
.paint-res::after { content: ''; position: absolute; right: -22px; top: -22px; width: 86px; height: 86px; border-radius: 50%; background: rgba(255,255,255,.07); pointer-events: none; }
.paint-res .pr-t { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 11.5px; opacity: .85; font-weight: 600; letter-spacing: .3px; white-space: nowrap; position: relative; z-index: 2; }
.paint-res .pr-v { font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15; letter-spacing: -.5px; }
.paint-res .pr-v small { font-size: 12px; font-weight: 400; opacity: .8; margin-left: 3px; }
.paint-res .pr-d { font-size: 11px; opacity: .78; margin-top: 3px; line-height: 1.45; }
.paint-res.primary { background: linear-gradient(135deg, #17395f, #3f6f9f); color: #fff; box-shadow: 0 10px 26px -14px rgba(20,45,100,.5); }
.paint-res.secondary { background: linear-gradient(135deg, #332457, #6a58a3); color: #fff; box-shadow: 0 10px 26px -14px rgba(70,50,120,.5); }
.paint-res.lite { background: #fff; border: 1.5px solid #d8e6f5; color: #0f3d63; }
.paint-res.lite .pr-t { color: #5a84a8; opacity: 1; }
.paint-res.lite .pr-v { color: #0f3d63; }
.paint-res.lite .pr-v small { color: #64748b; opacity: 1; }
.paint-res.lite .pr-d { color: #64748b; opacity: 1; }

/* 单板规格 + 保护膜 */
.paint-board { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px 18px 4px; font-size: 12.5px; color: var(--label-2); }
.paint-specs { display: flex; gap: 6px; }
.paint-spec { border: 1.5px solid #d5dde7; background: #fff; color: var(--label-2); border-radius: 9px; padding: 5px 13px; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .15s; }
.paint-spec:hover { border-color: #0071e3; color: #0071e3; background: #f2f8ff; }
.paint-spec.on { border-color: #0071e3; background: linear-gradient(135deg, #3a68e6, #2a4fbe); color: #fff; box-shadow: 0 4px 12px -6px rgba(42,79,190,.6); }
.paint-board input { width: 76px; height: 34px; padding: 0 8px; font-size: 13.5px; border: 1px solid #d5dde7; border-radius: 8px; outline: none; background: #fff; color: var(--label); font-variant-numeric: tabular-nums; }
.paint-board input:focus { border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.14); }
.paint-board em { font-style: normal; color: var(--label-4); font-size: 11.5px; }
.paint-film { display: inline-flex; align-items: center; gap: 5px; margin-left: 6px; padding: 4px 11px; border: 1.5px solid #d5dde7; border-radius: 9px; font-size: 12px; color: var(--label-2); cursor: pointer; background: #fff; transition: all .15s; }
.paint-film:hover { border-color: #0071e3; }
.paint-film input { accent-color: #0071e3; width: 14px; height: 14px; margin: 0; }

/* 单位胶囊 */
.paint-unit { display: inline-flex; border: 1px solid #d5dde7; border-radius: 8px; overflow: hidden; background: #fafbfc; }
.paint-unit button { border: 0; background: transparent; color: var(--label-3); padding: 3px 9px; font-size: 11px; cursor: pointer; white-space: nowrap; transition: all .12s; }
.paint-unit button + button { border-left: 1px solid #e5e9f0; }
.paint-unit button.on { background: #0071e3; color: #fff; font-weight: 600; }
.paint-res .paint-unit { position: static; flex: 0 0 auto; border-color: rgba(255,255,255,.4); background: rgba(255,255,255,.14); }
.paint-res.lite .paint-unit { border-color: #d5dde7; background: #fff; }
.paint-res .paint-unit button { color: rgba(255,255,255,.85); padding: 2px 4px; font-size: 9px; line-height: 1.3; }
.paint-res .paint-unit button + button { border-left-color: rgba(255,255,255,.25); }
.paint-res .paint-unit button.on { background: rgba(255,255,255,.28); color: #fff; font-weight: 700; }
.paint-res.lite .paint-unit button { color: #5a84a8; }
.paint-res.lite .paint-unit button + button { border-left-color: #e5e9f0; }
.paint-res.lite .paint-unit button.on { background: #0071e3; color: #fff; }

/* 获取铝价按钮 */
.paint-getprice { display: inline-block; margin-left: 6px; padding: 2px 9px; border: 1px solid #bcd3ea; background: #f2f8ff; color: #0071e3; border-radius: 7px; font-size: 11px; font-weight: 600; cursor: pointer; transition: all .12s; }
.paint-getprice:hover { background: #e2efff; border-color: #0071e3; }
.paint-getprice:disabled { opacity: .5; cursor: default; }
.paint-alquote { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 5px; font-size: 11px; color: var(--label-3); }
.paint-alquote .aq-line { white-space: nowrap; }
.paint-alquote .aq-line b { color: var(--label); }
.paint-alquote .aq-change { font-weight: 700; padding: 1px 7px; border-radius: 6px; }
.paint-alquote .aq-change.up { color: #c0392b; background: #fdecea; }
.paint-alquote .aq-change.down { color: #1a7f3c; background: #e8f7ee; }

/* 报价单制作 */
.paint-quote { margin-top: 2px; }
.paint-quote .paint-sec-head { flex-wrap: wrap; }
.paint-quote .paint-sec-sub { margin-right: auto; }
.paint-quote-btn { border: 1px solid #d5dde7; background: #fff; color: var(--label-2); border-radius: 8px; padding: 5px 14px; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .12s; }
.paint-quote-btn:hover { border-color: #0071e3; color: #0071e3; }
.paint-quote-btn.primary { background: linear-gradient(135deg, #3a68e6, #2a4fbe); color: #fff; border-color: transparent; }
.paint-quote-btn.primary:hover { opacity: .92; color: #fff; }
.paint-quote-btn:disabled { opacity: .5; cursor: default; }
.paint-quote-table-wrap { overflow-x: auto; padding: 0 16px 8px; }
.paint-quote-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 560px; }
.paint-quote-table th { text-align: left; padding: 9px 8px; color: var(--label-3); font-weight: 600; font-size: 12px; border-bottom: 1px solid var(--separator); white-space: nowrap; }
.paint-quote-table td { padding: 5px 6px; border-bottom: 1px solid #eef1f5; }
.paint-quote-table td:first-child { padding-left: 0; }
.paint-quote-table input { width: 100%; min-width: 56px; height: 32px; padding: 0 8px; font-size: 13px; border: 1px solid #e0e5ee; border-radius: 7px; background: #fff; color: var(--label); outline: none; font-variant-numeric: tabular-nums; box-sizing: border-box; }
.paint-quote-table input:focus { border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.13); }
.paint-quote-table td:first-child input { min-width: 90px; }
.paint-quote-config { border: 1px solid #cbd5e1; background: #f8fafc; color: #334155; border-radius: 6px; padding: 5px 9px; cursor: pointer; font-size: 12px; }
.paint-quote-config:hover { border-color: #0071e3; color: #0071e3; }
.quote-recipe-head { display:flex; align-items:center; gap:12px; padding:14px 16px; margin:-8px -8px 12px; background:linear-gradient(135deg,#eef3ff,#f8fbff); border-bottom:1px solid #e2e9f8; }
.quote-recipe-icon { width:34px; height:34px; display:grid; place-items:center; border-radius:10px; background:#e1eaff; color:#2f6fe0; font-size:18px; }
.quote-recipe-head b { display:block; color:#172033; font-size:16px; }
.quote-recipe-head span { display:block; margin-top:3px; color:#7b879a; font-size:11px; }
.quote-recipe-tip { padding:9px 11px; margin-bottom:14px; border:1px solid #e7edf7; border-radius:9px; background:#fbfcff; color:#6b778c; font-size:11px; line-height:1.6; }
.quote-recipe-form { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; padding:0 2px 4px; }
.quote-recipe-form label { position:relative; display:flex; flex-direction:column; gap:6px; color:#39465a; font-size:12px; font-weight:600; }
.quote-recipe-unit { position:absolute; right:10px; top:29px; color:#9aa6b8; font-size:11px; font-weight:400; }
.quote-recipe-form input:not([type="checkbox"]) { width:100%; height:38px; padding:0 42px 0 10px; border:1px solid #d9e1ed; border-radius:9px; background:#fff; color:#172033; font-size:14px; outline:none; transition:border-color .15s,box-shadow .15s; }
.quote-recipe-form input:not([type="checkbox"]):focus { border-color:#4d6bfe; box-shadow:0 0 0 3px rgba(77,107,254,.12); }
.quote-recipe-film { grid-column:1 / -1; flex-direction:row !important; align-items:center; gap:9px !important; padding:11px 12px; border:1px solid #e7edf7; border-radius:9px; background:#fbfcff; }
.quote-recipe-film input { width:17px; height:17px; accent-color:#4d6bfe; }
.quote-recipe-film b { display:block; color:#39465a; font-size:12px; }
.quote-recipe-film small { display:block; margin-top:2px; color:#9aa6b8; font-size:10px; font-weight:400; }
@media (max-width:700px) { .quote-recipe-form { grid-template-columns:1fr; } }
.paint-quote-del { border: none; background: transparent; color: #c0c6d0; font-size: 18px; line-height: 1; cursor: pointer; padding: 4px 6px; border-radius: 6px; }
.paint-quote-del:hover { color: #ff3b30; background: #fff5f5; }
.paint-quote-hint { padding: 2px 16px 14px; font-size: 11.5px; color: var(--label-4); }

/* 报价利润 */
.paint-sale-grid { display: flex; align-items: flex-start; gap: 14px 24px; padding: 14px 18px 16px; }
.paint-si { flex: 1; min-width: 0; }
.paint-si .si-row { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
.paint-si label { flex: 0 0 auto; font-size: 12.5px; color: var(--label-2); font-weight: 600; white-space: nowrap; }
.paint-si input { flex: 0 1 92px; width: 92px; min-width: 0; height: 34px; border: 1px solid #d5dde7; border-radius: 9px; padding: 0 10px; text-align: right; font-size: 15px; font-weight: 600; color: var(--label); background: #fff; outline: none; font-variant-numeric: tabular-nums; }
.paint-si input:focus { border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.14); }
.paint-si em { font-style: normal; font-size: 11.5px; color: var(--label-4); white-space: nowrap; }
.paint-si .paint-unit { flex: 0 0 auto; }
.paint-si .paint-unit button { padding: 3px 8px; }
.paint-profit { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 13px; color: var(--label-3); margin-top: 6px; }
.paint-si-hint { font-size: 11px; color: var(--label-4); margin-top: 6px; white-space: nowrap; }
.paint-profit b.pos { color: #0071e3; }
.paint-profit b.neg { color: #ff3b30; }
.pr-rate { background: #e8f2ff; border: 1px solid #d6e4f5; border-radius: 9px; padding: 1px 9px; color: #0071e3; font-weight: 700; font-size: 12px; white-space: nowrap; }
.pr-rate.neg { background: #fff3f2; border-color: #ffd1cf; color: #ff3b30; }

/* 两栏 */
.paint-cols { display: grid; grid-template-columns: 430px 1fr; gap: 14px; align-items: start; }
@media (max-width: 1100px) { .paint-cols { grid-template-columns: 1fr; } }
.paint-panel-in, .paint-panel-out { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* 涂层切换 */
.paint-coat { display: flex; align-items: center; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--separator); font-size: 13px; color: var(--label-2); }
.paint-coat-switch { display: flex; border: 1px solid var(--separator); border-radius: 10px; overflow: hidden; background: #fafbfc; }
.paint-coat-switch button { border: 0; background: transparent; color: var(--label-2); padding: 7px 18px; font-size: 13px; cursor: pointer; transition: all .15s; }
.paint-coat-switch button.on { background: linear-gradient(135deg, #3a68e6, #2a4fbe); color: #fff; font-weight: 600; }

/* 折叠组 */
.paint-group { border-top: 1px solid var(--separator); }
.paint-group:first-of-type { border-top: 0; }
.paint-group > summary { cursor: pointer; padding: 12px 18px; font-weight: 600; color: var(--label); font-size: 13.5px; user-select: none; list-style: none; display: flex; align-items: center; }
.paint-group > summary::-webkit-details-marker { display: none; }
.paint-group > summary::after { content: '▾'; color: var(--label-4); font-size: 12px; margin-left: auto; }
.paint-group:not([open]) > summary::after { content: '▸'; }
.paint-save-row { display: flex; justify-content: center; padding: 2px 18px 14px; }
.paint-save { border: 1px solid #bcd3ea; background: #f2f8ff; color: #0071e3; border-radius: 8px; padding: 5px 16px; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .12s; }
.paint-save:hover { background: #e2efff; border-color: #0071e3; }
.paint-save.dirty { border-color: #ff9a3d; background: #fff3e0; color: #ff7a00; }
.paint-save.dirty:hover { background: #ffe8c8; border-color: #ff7a00; }

/* 字段 */
.paint-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; padding: 14px 18px 16px; }
@media (max-width: 700px) { .paint-fields { grid-template-columns: 1fr; } }
.paint-field { display: grid; gap: 4px; font-size: 12.5px; color: var(--label-2); }
.paint-field > span:first-child { font-weight: 500; }
.paint-field > span b { color: #c0392b; font-weight: 600; }
.pf-row { display: flex; align-items: center; gap: 8px; }
.pf-row input { flex: 1; min-width: 0; height: 34px; padding: 0 12px; font-size: 14px; font-family: inherit; color: var(--label); background: #fafbfc; border: 1px solid #d5dde7; border-radius: 9px; outline: none; transition: border-color .15s, background .15s, box-shadow .15s; font-variant-numeric: tabular-nums; }
.pf-row input:hover { border-color: #c7cedd; }
.pf-row input:focus { border-color: #0071e3; background: #fff; box-shadow: 0 0 0 3px rgba(0,113,227,.14); }
.pf-row input:disabled { opacity: .45; }
.pf-row em { font-style: normal; font-size: 11.5px; color: var(--label-4); white-space: nowrap; }
.paint-sheetcost { padding: 14px 18px 0; }
.paint-sheetcost .paint-unit { flex: 0 0 auto; }
.paint-coef { margin: 0 18px 16px; padding: 10px 14px; border-radius: 9px; background: #e8f2ff; font-size: 12.5px; color: var(--label-2); }
.paint-coef b { color: #0071e3; font-family: ui-monospace, Menlo, monospace; }

/* 核算链路（横向步骤卡） */
.paint-steps { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; padding: 14px 18px 16px; }
@media (max-width: 900px) { .paint-steps { flex-direction: column; } }
.paint-step { flex: 1; min-width: 140px; border: 1px solid #d8e6f5; border-radius: 13px; padding: 11px 13px 10px; position: relative; overflow: hidden; background: rgba(255,255,255,.8); box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 2px 10px -8px rgba(30,60,120,.18); }
.paint-step::before { content: ''; position: absolute; left: 0; top: 0; right: 0; height: 3px; background: linear-gradient(90deg, #8aa9c9, #4c7095); }
.paint-step .ps-n { font-size: 11px; color: var(--label-3); font-weight: 600; }
.paint-step .ps-v { font-size: 18px; font-weight: 800; color: var(--label); font-variant-numeric: tabular-nums; margin: 2px 0; }
.paint-step .ps-c { font-size: 10.5px; color: var(--label-4); line-height: 1.45; }
.paint-step.fin { background: linear-gradient(150deg, #3f6aa0, #1f4370 58%, #10233f); border-color: rgba(255,255,255,.4); box-shadow: 0 14px 34px -18px rgba(10,45,110,.5); }
.paint-step.fin::before { background: linear-gradient(90deg, rgba(255,255,255,.55), rgba(255,255,255,.18)); }
.paint-step.fin .ps-n { color: #cfe4ff; }
.paint-step.fin .ps-v { color: #fff; }
.paint-step.fin .ps-c { color: #d6e9ff; }
.paint-arrow { align-self: center; color: #b5b5bb; font-size: 15px; font-weight: 700; }
@media (max-width: 900px) { .paint-arrow { align-self: center; transform: rotate(90deg); } }

/* 成本构成条形图 */
.paint-barline { padding: 6px 18px; }
.paint-barline .pbt { display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap; font-size: 12.5px; margin-bottom: 3px; }
.paint-barline .pnm { color: var(--label-2); }
.paint-barline .pnm span { color: var(--label-4); font-size: 11px; }
.paint-barline .pnum { color: var(--label); font-variant-numeric: tabular-nums; white-space: nowrap; }
.paint-barline .pbar { height: 7px; background: rgba(110,130,165,.16); border-radius: 5px; overflow: hidden; }
.paint-barline .pbar i { display: block; height: 100%; border-radius: 5px; min-width: 1px; }
#paintBars { padding: 4px 0 14px; }

/* 结构重量与板型 */
#paintBoard { padding: 14px 18px 16px; }
.paint-thbar { display: flex; height: 13px; gap: 2px; margin-bottom: 10px; cursor: help; }
.paint-thbar i { display: block; height: 100%; border-radius: 4px; min-width: 0; flex: 0 0 auto; }
.paint-wgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; margin-bottom: 10px; }
.paint-wcell { border: 1px solid #e4ebf4; background: #fff; border-radius: 12px; padding: 7px 12px 8px; }
.paint-wcell .pw-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.paint-wcell .pwnm { font-size: 12px; color: var(--label-2); font-weight: 700; display: flex; align-items: center; gap: 5px; white-space: nowrap; }
.paint-wcell .pwth { font-size: 10.5px; color: var(--label-4); white-space: nowrap; }
.paint-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
.paint-wcell .pwv { font-size: 18px; font-weight: 800; color: var(--label); font-variant-numeric: tabular-nums; line-height: 1.35; margin-top: 1px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.paint-wcell .pwv small { font-size: 10px; font-weight: 400; color: var(--label-4); margin-left: 3px; }
.paint-wcell .pwv .pw-ton { font-size: 13px; font-weight: 700; color: var(--label-3); }
.paint-wcell .pwv .pw-ton small { margin-left: 2px; }
.paint-wcell .pwcost { margin-top: 3px; font-size: 11.5px; color: var(--label-3); line-height: 1.4; }
.paint-wcell .pwcost b { color: var(--label); font-variant-numeric: tabular-nums; font-weight: 700; }
.paint-wsum { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; font-size: 12px; color: var(--label-2); padding: 8px 12px; background: rgba(255,255,255,.6); border: 1px solid #e4ebf4; border-radius: 10px; margin-bottom: 10px; }
.paint-wsum b { color: var(--label); font-variant-numeric: tabular-nums; }
.paint-wsum .sep { color: #d6d6db; }
.paint-boardrow { display: flex; flex-wrap: wrap; align-items: stretch; border: 1px dashed #bcd3ea; border-radius: 14px; background: rgba(255,255,255,.6); overflow: hidden; }
.paint-boardrow .pbl { padding: 9px 14px; flex: 1; min-width: 0; }
.paint-boardrow .pbl-t { font-size: 11px; color: #3d5d80; font-weight: 700; margin-bottom: 3px; }
.paint-boardrow .pbl-dim { font-size: 13px; color: var(--label-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.paint-boardrow .pbl-dim b { color: var(--label); }
.paint-boardrow .pbl-area { font-size: 11px; color: var(--label-4); margin-top: 2px; }
.paint-boardrow .pbr { background: linear-gradient(160deg, #3f6aa0, #1f4370 62%, #10233f); color: #fff; padding: 9px 16px; display: flex; flex-direction: column; justify-content: center; align-items: flex-end; min-width: 0; }
.paint-boardrow .pbr-t { font-size: 10.5px; opacity: .88; }
.paint-boardrow .pbr-v { font-size: 22px; font-weight: 800; line-height: 1.25; font-variant-numeric: tabular-nums; white-space: nowrap; }
.paint-boardrow .pbr-v small { font-size: 11px; font-weight: 400; opacity: .85; margin-left: 2px; }
.paint-note { padding: 6px 18px 14px; font-size: 12px; color: var(--label-3); line-height: 1.6; }

