/* KBP — Kümago Business Platform base styles */

/* 0.85.0-ListUX — 宽屏模式（Wolfgang 2026-09-26）。
   全站宽度上限只有这一个数：**默认 1440px**（他的笔记本 2880x1920@200% = 1440 CSS，
   正好铺满 —— 所以这条默认值一个字都不能动）。
   外接屏是 3840x1260@100% → 下面那条媒体查询生效，`has-wide` 的页面（列表 / 工作台 /
   单据页）加宽到 `min(2600px, 100% - 48px)`：两侧基本铺满，2600px 封顶免得正文行太长。
   ⚠️ A4 打印视图**不受影响**（`.doc-body .doc { width: 210mm }`，与这里无关）。 */
:root {
  --app-max: 1440px;
  --ink: #1f2430;
  --ink-soft: #5c6470;
  --line: #e3e6ea;
  --paper: #f4f5f7;
  --card: #ffffff;
  --brand: #1f4e79;      /* deep corporate blue */
  --brand-dark: #173a5a;
  --brand-tint: #eef3f8;
  --ok: #1c7a4a;
  --danger: #b3352c;
  --danger-tint: #fbeeed;
  /* 0.68.0-LeadTime — "进入 1 周"的黄色。用**压深的琥珀**而不是亮黄：
     它要当正文色印在白色表格里，亮黄(如 #e8a33d)对比度不够，看不清。 */
  --warn: #a8620a;
  --radius: 10px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: system-ui, "Segoe UI", Arial, sans-serif;
  font-size: 15px;
  color: var(--ink);
  background: var(--paper);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ---------- top bar ---------- */
.topbar {
  background: var(--brand);
  color: #fff;
}
/* 0.86.0-ListLayout（Wolfgang 2026-09-26，第 ③ 条）—— 顶栏**不再按容器宽收口**：
   整条铺满视口。于是 logo 与导航从最左开始（不随页面宽窄移动），
   右上角用户菜单贴在**屏幕右缘**（而不是"容器右端"）—— 它也不会跳了。
   ⚠️ 这就是"顶部入口不来回移动"的实现：宽度上限只留给内容区。 */
.topbar-inner {
  margin: 0;
  padding: 10px 24px;
  display: flex;
  align-items: center;
  /* 0.87.0-Topnav（Wolfgang 2026-09-27）—— **不再是 `space-between`**。
     0.86 把顶栏改成"铺满视口"之后，`space-between` 会把整组导航（应用名 + 五个模块 +
     Library▾ + ⚙▾ + 分隔线 + 用户菜单）推到**屏幕最右**，与左边的 logo 之间空出一大片：
     他的原话是 *"除了logo以外，其他的成为一组，都一起变成右对齐了，集中到网页的最右方"*。
     改成 `flex-start` → **两者都从左往右排**，导航组与 logo 之间只留一个**固定**距离
     （写在 `.topnav` 的 `margin-left`，见下；这里不再用 `gap` 以免两处各出一半距离）。
     ⚠️ 屏幕越宽、原来看起来越空；现在无论多宽，logo 与导航组都不动。 */
  justify-content: flex-start;
}
.topbar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: #fff;
}
.topbar-logo {
  height: 30px;
  width: auto;
  /* M4.8: topbar now uses the white (reverse) logo on the blue bar —
     no white background, no padding, no border-radius. */
}
.brand {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: .5px;
}
.brand-sub {
  display: block;
  font-size: 11px;
  font-weight: 400;
  opacity: .75;
  letter-spacing: .3px;
}
.topnav {
  display: flex;
  align-items: center;
  /* 1.75.1-NavClocks-2（Wolfgang 2026-10-16）—— 各标题之间的间距 **14px → 8px**
     （他：*"缩小各标题的间距"*）。顶栏现在有 5 个一级入口 + 4 个下拉，14px 显得散。
     ⚠️ 只动这一个数：`.topnav-title` 的 `margin-right` / `padding-right`（KBP 那条竖线）
     和 `.topbar-brand` 那一侧的 40px 固定距离都**不动**。 */
  gap: 8px;
  /* 0.87.0-Topnav —— 导航组与 logo 之间的**固定**呼吸距离（唯一一处）。
     他要求"与 Logo 保持一个**明显和固定**的距离" → 40px 足够明显，又不至于把
     应用名推进"另一个区域"的观感。 */
  margin-left: 40px;
}
.nav-user { opacity: .9; font-size: 14px; text-decoration: none; color: inherit; }
.nav-user:hover { text-decoration: underline; }

/* ---------- layout ---------- */
/* 0.86.0-ListLayout（第 ③ 条）—— **左对齐**：`margin: 0 auto` → `margin: 0`。
   内容区的左边缘从此固定在 `24px`，页面之间不再左右挪动；
   宽度上限仍由 `--app-max` 决定（1440 默认，五个列表页在 ≥1900px 视口上加宽）。 */
.container {
  width: 100%;
  max-width: var(--app-max);
  margin: 0;
  padding: 32px 24px;
  flex: 1;
}
.container.wide { max-width: var(--app-max); }
/* 宽屏：只对带 `has-wide` 的页面生效，而且只在 ≥1900px 的视口上（笔记本不触发）。 */
@media (min-width: 1900px) {
  body.has-wide { --app-max: min(2600px, 100% - 48px); }
}

.footer {
  text-align: center;
  color: var(--ink-soft);
  font-size: 12px;
  padding: 18px 0 24px;
}

/* ---------- cards / panels ---------- */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px;
  box-shadow: 0 2px 10px rgba(16, 24, 40, .06);
}
.card h1 {
  margin: 0 0 6px;
  font-size: 22px;
}
.card .subtitle {
  margin: 0 0 24px;
  color: var(--ink-soft);
}

/* ---------- forms ---------- */
.bank-col { border: 1px solid var(--border, #e3e6ea); border-radius: 8px; padding: 12px 14px; }
.bank-col-title { font-size: 13px; font-weight: 600; margin: 0 0 10px; color: var(--text-secondary, #5c6470); }
label {
  display: block;
  font-weight: 600;
  font-size: 13px;
  margin: 0 0 6px;
}
input[type="text"], input[type="password"], input[type="email"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  font-size: 15px;
  font-family: inherit;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--brand);
  outline-offset: -1px;
}
.field { margin-bottom: 18px; }

/* 0.48.0-DocLine3 (#4) — a read-only value sitting where an input used to be
   (the Order's two PI-derived dates). Deliberately looks like a disabled field
   so nobody tries to type in it, and keeps the same metrics as an input so the
   surrounding grid row does not change height. */
.readonly-date {
  padding: 10px 12px;
  font-size: 15px;
  line-height: 1.35;
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: #f7f8fa;
  color: var(--ink);
  min-height: 21px;
}

/* 0.48.0-DocLine3 (#6) — the "PI is issued → create the order draft?" prompt on
   the New-order page. Highlighted so it reads as a decision, not as part of the
   form below it. */
.ask-card {
  border-left: 4px solid var(--brand) !important;
  background: #f6f9ff !important;
  margin-bottom: 18px;
}
.ask-card .form-section { margin-top: 0; font-size: 17px; }

/* ---------- buttons ---------- */
.btn {
  display: inline-block;
  padding: 10px 20px;
  font-size: 15px;
  font-family: inherit;
  font-weight: 600;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  text-decoration: none;
  background: var(--brand);
  color: #fff;
}
.btn:hover { background: var(--brand-dark); }
.btn-sm { padding: 6px 14px; font-size: 13px; }
.btn-ghost {
  background: transparent;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, .5);
}
.btn-ghost:hover { background: rgba(255, 255, 255, .12); }
.btn-block { width: 100%; }

/* ---------- messages ---------- */
.msg {
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 20px;
  font-size: 14px;
}
.msg-error {
  background: var(--danger-tint);
  color: var(--danger);
  border: 1px solid #edc9c5;
}
.msg-ok {
  background: #edf7f1;
  color: var(--ok);
  border: 1px solid #cbe7d6;
}
.msg-info {
  background: #eaf3fb;
  color: #1f6fb2;
  border: 1px solid #c5dcef;
}

/* ---------- login page ---------- */
.login-wrap {
  max-width: 400px;
  margin: 8vh auto 0;
  padding: 0 24px;
}
.login-brand {
  text-align: center;
  margin-bottom: 24px;
}
.login-brand .logo {
  font-size: 34px;
  font-weight: 800;
  color: var(--brand);
  letter-spacing: 1px;
}
.login-brand .tagline {
  color: var(--ink-soft);
  font-size: 13px;
  margin-top: 4px;
}

/* ---------- misc ---------- */
.kv {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.kv th, .kv td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}
.kv th { color: var(--ink-soft); font-weight: 600; width: 40%; }
.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: var(--brand-tint);
  color: var(--brand);
}
.badge-ok { background: #edf7f1; color: var(--ok); }
.muted { color: var(--ink-soft); }
a { color: var(--brand); }

/* ================= M1 additions ================= */

/* ---------- top navigation links ---------- */
.nav-link {
  color: rgba(255, 255, 255, .85);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 6px;
}
.nav-link:hover { color: #fff; background: rgba(255, 255, 255, .12); }
.nav-link-active { color: #fff; background: rgba(255, 255, 255, .18); }
.nav-sep {
  width: 1px;
  height: 22px;
  background: rgba(255, 255, 255, .25);
  display: inline-block;
  margin: 0 2px;
}

/* ---------- page header (title + action button) ---------- */
.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.page-head h1 { margin: 0; font-size: 24px; }
.page-head .subtitle { margin: 4px 0 0; color: var(--ink-soft); font-size: 14px; }
/* 0.88.0-PoReceive (Wolfgang 2026-09-27) —— 页头动作区里的**成组**控件。
   PO 详情页的「收货日期 + Mark received」是**一件事**，但 0.86 之后动作区是一行 flex、
   每个孩子只隔 `gap: 10px` —— 那个日期框看起来就属于**它左边**的「New Revision」
   （他的原话：*"与Mark receive更接近，远离New revision按键"*）。
   这个类把成组的控件从**左边**隔开：`margin-left` 给距离、`padding-left` + 竖线
   标明"这一组从这里开始"；组内保持紧凑（`gap: 8px`）。
   ⚠️ `margin: 0` 是给 `<form>` 用的（浏览器默认给它外边距，页面上其余 form 都在内联
   写 `margin:0` —— 这里收进 CSS，免得两种写法并存）；`margin-left` **单独写一行**，
   不用简写：那个数字是"远离左边那个按钮"的唯一来源，写成简写判据就读不到它了。 */
.action-group {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  margin-left: 18px;
  padding-left: 14px;
  border-left: 1px solid var(--line);
}

/* ---------- data tables ---------- */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  background: var(--card);
}
.table th, .table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.table thead th {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--ink-soft);
  background: var(--brand-tint);
}
.table tbody tr:hover { background: #fafbfc; }
.table .cell-main { font-weight: 600; }
.table .cell-sub { font-size: 12px; color: var(--ink-soft); }
/* ---------- 0.86.0-ListLayout：列表表格（`.table.list`）---------- */
/* 第 ⑤ 条（Wolfgang 2026-09-26）—— **每个列宽由该列的最大内容宽度决定**，
   从左往右排到最后一列，**不扩展到容器满宽**。所以这里 `width: auto`
   （普通 `.table` 仍是 `width: 100%`：工作台的明细表 / 详情页的小表不是"列表"，
   它们继续铺满，不受本批影响）。
   ⚠️ 副作用（他已确认）：表格窄于容器时，表头灰底 / 行 hover 高亮 / 单元格下边线
   都只到**表格宽度**，右侧是白底留白 —— 这正是"不拉伸"的含义。 */
.table.list { width: auto; }
/* 默认**一格一行**（列宽 = 该列最长内容）。要换行的长文本列挂 `.cell-wrap`。 */
.table.list th,
.table.list td { white-space: nowrap; }
.table.list .cell-wrap,
.table.list .cell-wrap .cell-main,
.table.list .cell-wrap .cell-sub { white-space: normal; }
/* 长文本列的单列上限（第 ⑤ 条的"允许换行 + 上限"）：超过就折行，不再撑宽整张表。 */
.table.list .cell-wrap { max-width: 320px; min-width: 180px; }
/* --- 0.90.0-ColWrap：手工定过列宽之后，内容改为折行（最多两行 + 省略号）--------------
   Wolfgang 2026-09-27：*"在list列表状态下，如果手工拖拽调整例宽，会发生两列内容被重叠的
   情况。针对这种情况，应该处理为活动列的文字自动换为两行比较合理。"*
   根因：`assets/cols.js` 的 `paint()` 把整张表冻结成 `table-layout: fixed` + 每列一个 px 宽，
   而上面那条 `white-space: nowrap` 不让它折 → 内容画到**相邻格之上**。
   实测（`_measure_coloverlap.py`）：Customer 列拖到 64px、内容 339px → 溢出 273px；折行后归零。
   ⚠️ 只在冻结态（`cols.js` 加的 `.cols-sized`）生效 —— **没拖过的页面一个字都不改**
   （0.86 的"一格一行、列宽 = 该列最长内容"保留）。 */
.table.list.cols-sized th,
.table.list.cols-sized td { white-space: normal; overflow-wrap: anywhere; }
/* ⚠️ 0.92.0-Fix5 —— `.cell-no`（0.56 的"单号不折行"）是**直接**挂在内层 div 上的
   （`kbp_cell()` 的 `$cls`），而**直接规则赢过继承** → 上面那条只改 `td` 的规则对
   OC / Order / PO 那几列**无效**（他 2026-09-28 报的"仍然会重叠"就是这个）。
   冻结态必须再让这一档；默认态仍是 `nowrap`（0.56 的承诺不动）。 */
.table.list.cols-sized .cell-no { white-space: normal; }
.table.list.cols-sized td > .cell-main,
.table.list.cols-sized td > .cell-lead,
.table.list.cols-sized td > .cell-sub {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* 冻结态的宽度已经由用户定：`.cell-wrap` 的上下限让位，否则拖到 180px 以下它仍不肯缩 */
.table.list.cols-sized .cell-wrap { max-width: none; min-width: 0; }
/* 两行排布（第 ④ 条）——主值与副值各占一行；副值贴紧一点，一对看起来是一格。
   `.cell-main` = **加粗**的第一行（身份类：单号 / Model / 客户 / 文件名）;
   `.cell-lead` = **不加粗**的第一行（日期 / 数量 / 计数 / 版本这类"陪衬"值）——
   方案 A 样张里就是这样：日期那一格首行是正常字重、第二行才是灰小字。 */
.table.list td > .cell-sub { margin-top: 2px; }

/* ---------- 0.93.0-Shipments：物流记录（进 / 出两个方向）---------- */
/* Wolfgang 2026-09-28 —— 物流从"一个方向一条"改成"多条批次"（`shipments` 表）。
   唯一实现 = `lib/shipments.php`（`shipment_card_html()` / `shipment_row_html()`），
   Order 详情页与 PO 详情页共用。这里**只定形状**：状态文字、方向徽章、
   一行操作按钮的排布。颜色一律走变量，页面里不许内联。 */

/* 汇总行（`Inbound: ✓ Delivered 26.09.2026 · Outbound: 🚚 In Transit`）——
   卡片标题下面一行，两段之间靠 gap 分开（不是写死的中点字符）。 */
.ship-summary {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  margin: 2px 0 8px; font-size: 12px; color: var(--ink-soft);
}
.ship-summary b { color: var(--ink); }
.ship-summary .ship-sum { white-space: nowrap; }

/* 物流表：详情页卡片里的小表（不是列表页那种大表）。行紧凑、操作列不折行。 */
.ship-table th,
.ship-table td { vertical-align: top; }
.ship-table .ship-track { margin-top: 2px; }
.ship-table .ship-ops { white-space: nowrap; }
.ship-table .ship-ops form { display: inline; }

/* 方向徽章 —— 进 / 出要**一眼分得开**：同一张订单上现在同时挂着两条线，
   看错方向比看错日期更贵。 */
.ship-dir {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  border: 1px solid var(--line);
}
.ship-dir-inbound  { background: var(--brand-tint); border-color: var(--brand); color: var(--brand); }
.ship-dir-outbound { background: #eef7ea; border-color: #b5e3c4; color: #1d7a3a; }

/* 状态三档（`shipment_status()` 是唯一判据，值域只有这三个）。 */
.ship-badge { display: inline-block; padding: 1px 6px; border-radius: 10px; font-size: 11px; }
.ship-badge-pending   { background: #f2f3f5; color: var(--ink-soft); }
.ship-badge-transit   { background: #fff5e0; color: #8a5a00; }
.ship-badge-delivered { background: #e7f6ec; color: #1d7a3a; }

/* 「+ Record a shipment」的折叠表单（原生 `<details>`，零 JS）。 */
.ship-add { margin-top: 10px; }
.ship-add > summary { cursor: pointer; font-size: 12px; color: var(--brand); }
.ship-add .ship-form { margin-top: 8px; }
.ship-form-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ship-form-row .ship-lbl { flex: none; }
/* 1.06.0-PlWaybills — PL 工作台的运单行（复用 `.ship-form-row` 的 flex 排布 + 同一个
   加行/删行脚本 `assets/ship-multiline.js`）。这里只补"行与行之间留一点缝"，
   ⚠️ 别给 `.ship-row` 加 margin —— Order / PO 页的行是表格里的一行，加了下半页会散。
   ⚠️ 宽度走**类**、不写内联 `style="max-width:…"`：`guard_edit_panel` 钉着
   "编辑区的宽度只有 .w-sm / .w-md 一个来源"（这条规则对新增控件一样有效）。 */
.doc-work-panel .wb-row { margin-bottom: 6px; }
.doc-work-panel .wb-row:last-child { margin-bottom: 0; }
.doc-work-panel .wb-row .wb-car { max-width: 160px; }
.table.list td > .cell-lead { font-weight: 400; }
.table a { text-decoration: none; }
.table a:hover { text-decoration: underline; }

/* ---------- filter bar above tables ---------- */.filter-bar {
  display: flex;
  gap: 10px;
  margin-bottom: 16px;
  flex-wrap: wrap;
  align-items: center;
}
.filter-bar input[type="text"], .filter-bar select {
  width: auto;
  min-width: 150px;
  padding: 8px 10px;
  font-size: 14px;
}

/* ---------- badge variants ---------- */
.badge-role-customer  { background: #e8f0fb; color: #1f4e79; }
.badge-role-supplier  { background: #f3ecfb; color: #6b3fa0; }
.badge-role-both      { background: #e6f4f1; color: #0f6e62; }
.badge-role-partner   { background: #efe9e2; color: #8a6d3b; }
.badge-status-active    { background: #edf7f1; color: var(--ok); }
.badge-status-prospect  { background: #fdf3e3; color: #a06a00; }
.badge-status-inactive  { background: #f0f1f3; color: #6c757d; }

/* ---------- two-column form grid ---------- */
.grid2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 20px;
}
@media (max-width: 640px) { .grid2 { grid-template-columns: 1fr; } }

/* ---------- form section headings ---------- */
.form-section {
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .8px;
  color: var(--brand);
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
  margin: 28px 0 18px;
}
.form-section:first-child { margin-top: 0; }
.form-hint { font-size: 12px; color: var(--ink-soft); margin: -12px 0 18px; }

/* ---------- contact blocks inside forms ---------- */
.contact-block {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 16px 18px;
  margin-bottom: 14px;
  background: #fafbfc;
}
.contact-block-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.contact-block-head .muted { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; }
.remove-contact {
  background: none;
  border: none;
  color: var(--danger);
  font-size: 13px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  font-weight: 600;
}
.remove-contact:hover { background: var(--danger-tint); }

/* ---------- inline checkbox row ---------- */
.check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  margin-bottom: 18px;
}
.check-row input { width: auto; }
.check-row label { margin: 0; font-weight: 500; }

/* ---------- 1.107.0-CheckPick：可滚动的勾选清单 ----------
   他 2026-10-27：*"related order 这种选择方式不友好，根本不知道是否被选定，
   换为 check 控件会比较清晰"*。原生 `<select multiple>` 要 Ctrl+点击、选中项只是
   一行浅蓝底，触屏与键盘基本用不了 ⇒ 换成一批 checkbox。

   ⚠️ `.check-row` 自带 `margin-bottom: 18px`（那是给"表单里孤零零一个勾选框"用的），
      在清单里会散得没法看 ⇒ 这里必须压回 0。同族：那条 `label` 规则也是为单勾选框写的，
      清单里文字是**行内容**不是标签，所以只保字体权重、不接管对齐。
   ⚠️ 高度是给 `.doc-inv` 的建单页写的（订单最多列 200 张）；内容少时不会撑出空白
      （`max-height` 不是 `height`）。 */
.checklist {
  max-height: 240px;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 12px;
  background: var(--card);
}
.checklist .check-row {
  margin-bottom: 0;
  padding: 4px 0;
  align-items: flex-start;
  gap: 8px;
  font-weight: 400;
  line-height: 1.45;
  cursor: pointer;
}
.checklist .check-row + .check-row { border-top: 1px solid var(--line); }
.checklist .check-row > span:last-child { flex: 1; }
.checklist .check-row input { margin-top: 2px; flex: 0 0 auto; }
/* 清单里一条都不剩时 JS 会取消 hidden —— 那句话本身要有呼吸空间。 */
.checklist + .field-hint { margin-top: 6px; }

/* ---------- detail page: contact cards ---------- */
.contact-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 16px 18px;
  margin-bottom: 12px;
  background: var(--card);
}
.contact-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
}
.contact-card-name { font-weight: 700; font-size: 15px; }
.contact-card-name .star { color: #d99a00; margin-left: 4px; }
/* 0.47.0-DocLine2 (#1) — the contact's gender, shown on the Partner card only.
   It is a record-keeping field (so we know whether to write "Mr." or "Ms." in an
   e-mail) and is deliberately NEVER printed on a document — see migration 045. */
.contact-gender {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 9px;
  background: #eef1f4;
  color: #4a5560;
  font-size: 11.5px;
  font-weight: 400;
  vertical-align: 1px;
}
.contact-card-rows { font-size: 13px; color: var(--ink-soft); line-height: 1.7; margin-top: 6px; }
.contact-card-rows b { color: var(--ink); font-weight: 600; }
.contact-card-actions { display: flex; gap: 12px; align-items: center; }
.contact-card-actions a { font-size: 13px; }

/* ---------- form action row ---------- */
.actions {
  display: flex;
  gap: 12px;
  margin-top: 24px;
  align-items: center;
}
.btn-danger {
  background: transparent;
  color: var(--danger);
  border: 1px solid #edc9c5;
}
.btn-danger:hover { background: var(--danger-tint); }
.btn-secondary {
  background: #fff;
  color: var(--ink);
  border: 1px solid var(--line);
}
.btn-secondary:hover { background: var(--paper); }

/* ---------- empty states ---------- */
.empty {
  text-align: center;
  color: var(--ink-soft);
  padding: 48px 20px;
  font-size: 15px;
}
.empty .empty-title { font-weight: 700; color: var(--ink); margin-bottom: 6px; }

/* ---------- dashboard stat cards ---------- */
/* 1.06.0-StatChip（Wolfgang 2026-09-30：*"projects follow-up上面的统计卡片太大了"*）——
   Dashboard 的统计卡片改成**数字与名称同排的一行小方块**（他看三档样张挑的"档 B"）。
   实测（`_probe_statsize.py` + `_measure_dash.py`，真渲染 + headless Chrome）：
     改前 每张 **254.1 × 86.7px**（数字 28px / 内边距 16-18），改后 **≈ 40px** 高（数字 18px / 内边距 8-12）。
   ⚠️ 三件事**刻意没动**（清单在此，免得下次"顺手"）：
     · `.stat-grid` 的 `auto-fit + minmax(150px, 1fr)` **保持原样** —— 它让张数少的那几处被拉宽
       （Purchase Orders 只有 1 格 ⇒ 1326.7px 宽）。我实测到并把这条单独抛给他，**他没选**，
       所以本批不动宽度；哪天要收，就在这一处加列宽上限。
     · 五处统计卡片（Projects / Orders / Quotation / Partners / Purchase）用的是**同一套类**，
       所以这一处改动**同时**作用于它们 —— 不是五处各改一遍。
     · 数字/标签的**颜色、字重、大写**一字未动，只改尺寸与排布。 */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
  margin: 12px 0;
}
.stat {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 12px;
  display: flex;              /* 1.06.0-StatChip — 数字与名称同排（原为上下两行） */
  align-items: baseline;
  gap: 8px;
}
.stat .stat-num { font-size: 18px; font-weight: 800; color: var(--brand); }
.stat .stat-label { font-size: 11px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .5px; }

/* ================= M1.1 additions ================= */

/* ---------- visible form frames (fieldset-like) ---------- */
.form-frame {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 20px 24px 6px;     /* bottom padding small; fields have own margin */
  margin-bottom: 20px;
  background: var(--card);
  box-shadow: 0 1px 4px rgba(16,24,40,.04);
}
.form-frame-title {
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .6px;
  color: var(--brand);
  margin: 0 0 16px;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--brand-tint);
  display: flex;
  align-items: center;
  gap: 8px;
}
.form-frame-title .frame-badge {
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--brand-tint);
  color: var(--brand);
  text-transform: none;
  letter-spacing: 0;
}

/* ---------- 3-column grid (for contacts) ---------- */
.grid3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 0 16px;
}
@media (max-width: 900px) { .grid3 { grid-template-columns: 1fr; gap: 14px; } }
@media (max-width: 640px) { .grid3 { grid-template-columns: 1fr; } }

/* ---------- multi-column company info grid ---------- */
.grid-company {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0 20px;
}
@media (max-width: 900px) { .grid-company { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .grid-company { grid-template-columns: 1fr; } }

/* 0.48.0-DocLine3 (#7) — Wolfgang 2026-09-16: PO 工作台左栏 "Payment terms 另起
   一行；order date 和 delivery date 放在一行".
   通用 `.grid-company` 是三列，四个字段会排成 3+1 —— 日期与付款条件同排、交期
   被挤到第二排，正是他看到的样子。这里给 PO 的条款区单独两列，两个 Terms 各自
   占满一行，于是 Order date / Delivery time 自然落在同一行。 */
.grid-po-terms { grid-template-columns: 1fr 1fr; }

/* 1.41.0-HoldLog — 需求单㉖（2026-10-09）Wolfgang：
   *"The payment 信息做一上聚合，现在分 5 行太占地方了"* ⇒ 登记表单压成两行。

   为什么新加一个类而不是复用 `.grid-company`：那个是三列（`repeat(3,1fr)`），
   我这里每行正好两个 field —— 用三列会空出第三格，反而把 Date/Amount 撑得很怪。
   🔴 教训（本项目真踩过）：**别挂 CSS 不存在的类** —— 我第一版在 HTML 里写了
      `field-row` / `input-suffix`，`style.css` 里**一个都没有** ⇒ 页面照样渲染、
      但一点样式都没有（静默）。所以这两个类**必须真的落在这里**。 */
.pay-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 20px;
}
@media (max-width: 640px) { .pay-two-col { grid-template-columns: 1fr; } }

/* Amount 后面那个行内的 `EUR`。
   ⚠️ 1.41.0 起 `Currency` 那个 field 整个删掉了（他："Currency 整个都可以去除"），
      "只认 EUR"这件事就靠这个后缀来说 —— 语义没丢，少一个 field。 */
.pay-amount-wrap { display: inline-flex; align-items: baseline; gap: 8px; }
.grid-po-terms .po-term-full { grid-column: 1 / -1; }
@media (max-width: 640px) { .grid-po-terms .po-term-full { grid-column: auto; } }

/* ---------- compact field (less margin inside frames) ---------- */
.field-compact { margin-bottom: 14px; }

/* ---------- EU badge ---------- */
.badge-eu { background: #eef3f8; color: #1f4e79; border: 1px solid #c3d6e8; }
.badge-non-eu { background: #f4f5f7; color: #6c757d; border: 1px solid #e3e6ea; }
.badge-eu-auto {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 6px;
  margin-left: 8px;
  vertical-align: middle;
}
.badge-eu-auto.eu { background: #e8f5ed; color: var(--ok); }
.badge-eu-auto.non-eu { background: #f4f5f7; color: var(--ink-soft); }

/* ---------- field-level error hint ---------- */
.field-error { border-color: var(--danger) !important; }
.field-err-text { font-size: 12px; color: var(--danger); margin: -8px 0 10px; }

/* ---------- phone input with leading + hint ---------- */
.phone-field {
  position: relative;
}
.phone-field .phone-hint {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 11px;
  color: var(--ink-soft);
  pointer-events: none;
}

/* ---------- back button (page-head) ---------- */
.page-head .back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--brand);
  text-decoration: none;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
}
.page-head .back-link:hover { background: var(--paper); }

/* ---------- contact block in 3-col layout ---------- */
.contact-block-3 {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
  background: #fafbfc;
  height: fit-content;
}
.contact-block-3 .contact-block-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}
.contact-block-3 .contact-block-head .muted {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
}
.contact-block-3 .field { margin-bottom: 12px; }
.contact-block-3 label { font-size: 12px; }
.contact-block-3 input { padding: 8px 10px; font-size: 14px; }

/* ---------- customer-no field highlight ---------- */
.field-customer-no input {
  border-color: var(--brand);
  border-width: 2px;
  background: #fbfcfe;
}

/* ---------- spacing improvements ---------- */
.form-frame .form-section {
  margin-top: 20px;
}
.form-frame .form-section:first-child {
  margin-top: 0;
}
.actions {
  position: sticky;
  bottom: 0;
  background: linear-gradient(transparent, var(--paper) 30%);
  padding: 12px 0 4px;
  margin-top: 20px;
}

/* ============================================================
 * M2 — Project module
 * ============================================================ */

/* ---------- pipeline summary strip (list page) ---------- */
.pipeline-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 8px;
  margin: 16px 0;
  padding: 12px 14px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.pipeline-total {
  display: flex;
  align-items: center;
  padding: 0 14px 0 4px;
  color: var(--ink-soft);
  font-size: 14px;
}
.pipeline-total b { font-size: 20px; color: var(--ink); margin-right: 6px; }
.pipeline-step {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--paper);
  text-decoration: none;
  min-width: 78px;
  transition: border-color .15s, background .15s;
}
.pipeline-step:hover { border-color: var(--brand); }
.pipeline-step-active { border-color: var(--brand); background: var(--brand-tint); }
.pipeline-step-name { font-size: 11px; font-weight: 600; color: var(--ink-soft); }
.pipeline-step-n { font-size: 16px; font-weight: 700; color: var(--ink); }

/* ---------- month picker on the money strip (1.11.0-MonthPick) ----------
   Wolfgang 2026-10-02 — "否则当月统计无法看到历史月份"。
   `margin-left: auto` 把它推到统计条的右端（`.pipeline-strip` 是 flex + wrap）。 */
.month-nav {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  padding-left: 10px;
}
.month-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--paper);
  color: var(--ink);
  font-size: 16px;
  line-height: 1;
  text-decoration: none;
}
.month-step:hover { border-color: var(--brand); }
.month-step.is-off { color: var(--ink-soft); opacity: .45; }
.month-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.month-now {
  margin-left: 2px;
  font-size: 12px;
  color: var(--brand);
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
}

/* ---------- pipeline progress bar (detail page) ---------- */
.pipeline {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 18px 0 22px;
}
.pipeline-node {
  position: relative;
  flex: 1 1 90px;
  text-align: center;
  padding: 10px 4px 12px;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--paper);
  border-top: 3px solid var(--line);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pipeline-node:first-child { border-radius: 8px 0 0 8px; }
.pipeline-node:last-child  { border-radius: 0 8px 8px 0; }
.pipeline-node-current {
  background: var(--brand-tint);
  color: var(--brand);
  border-top-color: var(--brand);
}
.pipeline-node-terminal.pipeline-node-current { border-top-color: var(--ok); color: var(--ok); background: #eef7f1; }
.pipeline.lost .pipeline-node-current { border-top-color: var(--danger); color: var(--danger); background: var(--danger-tint); }

/* ---------- status change / add-entry inline forms ---------- */
.status-form {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  flex-wrap: wrap;
}
.status-form .field { margin-bottom: 0; }

/* ---------- timeline (append-only entries) ---------- */
.timeline { margin-top: 16px; }
.tl-entry {
  border: 1px solid var(--line);
  border-left: 4px solid var(--line);
  border-radius: 8px;
  background: #fff;
  padding: 10px 14px;
  margin-bottom: 10px;
}
.tl-entry.tl-followup { border-left-color: var(--brand); }
.tl-entry.tl-problem  { border-left-color: var(--danger); }
.tl-entry.tl-note     { border-left-color: var(--ink-soft); }
.tl-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}
.tl-meta { font-size: 12px; color: var(--ink-soft); }
.tl-content { font-size: 14px; line-height: 1.5; }

/* ---------- badges: project status ---------- */
.badge-pstatus-lead         { background: #f4f5f7; color: var(--ink-soft); }
.badge-pstatus-understanding{ background: #eef3f8; color: var(--brand); }
.badge-pstatus-solution     { background: #eef3f8; color: var(--brand); }
.badge-pstatus-quoting      { background: #fff4e0; color: #96601a; }
.badge-pstatus-sample       { background: #f0ecfa; color: #5a46a8; }
.badge-pstatus-testing      { background: #e8f0fa; color: #1c5a94; }
.badge-pstatus-won          { background: #edf7f1; color: var(--ok); }
.badge-pstatus-lost         { background: var(--danger-tint); color: var(--danger); }

/* ---------- badges: note kinds + levels ---------- */
.badge-kind-followup { background: var(--brand-tint); color: var(--brand); }
.badge-kind-note     { background: #f4f5f7; color: var(--ink-soft); }
.badge-kind-problem  { background: var(--danger-tint); color: var(--danger); }
.badge-level-low     { background: #edf7f1; color: var(--ok); }
.badge-level-medium  { background: #fff4e0; color: #96601a; }
.badge-level-high    { background: var(--danger-tint); color: var(--danger); }

/* ---------- due / overdue ---------- */
.due-date { font-weight: 600; }
/* 0.68.0-LeadTime — 交期预警**只用字体颜色**。
   Wolfgang 2026-09-23："OC和PO的时间预警，不再使用徽章，都改为用日期字体的颜色来表示，
   进入1周为黄，超期为红"。判级在 lib/ui.php 的 kbp_due_level()（唯一实现，阈值 1 周），
   上色在 kbp_due_span()。⚠️ 加粗写进这两条里：交期那两列**不加** `.due-date`
   （那条是给"到期日"类格子的），但红/黄本身就该在表格里第一眼跳出来。 */
.due-overdue { color: var(--danger); font-weight: 600; }
.due-soon { color: var(--warn); font-weight: 600; }
.badge-due {
  background: var(--danger-tint);
  color: var(--danger);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: .5px;
}

/* ---------- column visibility toggle (list pages) ---------- */
.cols-control { position: relative; display: inline-block; }
.cols-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  display: none;
  min-width: 190px;
  padding: 10px 14px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
}
.cols-panel.open { display: block; }
.cols-panel label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.cols-panel input[type=checkbox] { accent-color: var(--brand); }
.col-hidden { display: none !important; }

/* ---------- M3 quotation (DocStd 批4 + 1.76.0-QuotStages + 1.97.0-SentSplit: **9 states**)
   批4 的 7 态 + 1.76.0 的 `negotiating` + 1.97.0 的 `sent`。
   ⚠️ 值域三同步：**迁移 095** 的 ENUM（087 是历史起点） ≡ `QUOTATION_STATUSES` ≡
   下面这一组 `.badge-qstatus-*`（少一条徽章就没底色，而且**不报错** ——
   `guard_quotationstages` 会逐条比对）。
   🔄 1.97.0-SentSplit —— `issued` 的标签回到 `Issued`、`sent` 是**独立的一档**
   （从前它是 034 之前的历史别名）。底色按值名走、文案由 PHP 出，所以这里两条都要在。
   ⚠️ 两档刻意**同色系但不同名**：`issued` 是"纸面已签发"（内部完成），
   `sent` 是"客户手上有了"（人工判断）—— 语义相邻，配色不必拉开。 */
.badge-qstatus-draft        { background: #f4f5f7; color: var(--ink-soft); }
.badge-qstatus-issued       { background: #eef3f8; color: var(--brand); }
.badge-qstatus-sent         { background: #e6f0fb; color: #1f5fa8; }
.badge-qstatus-following_up { background: #fdf6e3; color: #9a6a00; }
/* 1.76.0-QuotStages —— 客户回价 / 要求改条件。冷色（紫），与 following_up 的黄
   **一眼可辨**（那两格在列表里相邻，都是"还在谈"，但含义不同：一个是催、一个是回价）。 */
.badge-qstatus-negotiating  { background: #ece7fb; color: #5b46a8; }
.badge-qstatus-accepted     { background: #edf7f1; color: var(--ok); }
.badge-qstatus-rejected     { background: var(--danger-tint); color: var(--danger); }
.badge-qstatus-expired      { background: #fdf3e3; color: #a06a00; }
.badge-qstatus-void         { background: #f0f1f3; color: #6c757d; }
/* 0.63.0-Void — 已签发单据的第三态（draft / issued / void）。灰色中性底 +
   大写，和报价单的 void 同观感：一眼看出"这张纸已经不在用了"。 */
.badge-void { background: #f0f1f3; color: #6c757d; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }

/* ---------- M4 order type + status (DocStd 批3: 8-state machine) ---------- */
.badge-otype-sample  { background: #f0ecfa; color: #5a46a8; }
.badge-otype-regular { background: #eef3f8; color: var(--brand); }
/* 0.69.0-SplitOrder — 分期单（一张 OC 分几期交货时每期一张 Order）。
   ⚠️ 这个类必须存在：`badge_order_type()` 会拼 `badge-otype-<type>`，
   少一条就只有文字、没有底色（`guard_order_type_values` 会拦）。 */
.badge-otype-split   { background: #fdf3e0; color: #8a5a00; }

.badge-ostatus-draft               { background: #f4f5f7; color: var(--ink-soft); }
.badge-ostatus-confirmed           { background: #eef3f8; color: var(--brand); }
.badge-ostatus-in_production       { background: #fff4e0; color: #96601a; }
.badge-ostatus-supplier_delivering { background: #e6f4f1; color: #1f7a68; }
.badge-ostatus-goods_received      { background: #e6f4f1; color: #146c5c; }
.badge-ostatus-shipped             { background: #eef0fa; color: #3a3f9e; }
.badge-ostatus-delivered           { background: #e6f7ec; color: #1d7a3f; }
.badge-ostatus-cancelled           { background: var(--danger-tint); color: var(--danger); }

/* DocStd 批3 — trigger-source chip next to the order status badge
   ("→ In Production · by PO issue") */
.ostatus-trigger { font-size: 12px; color: var(--ink-soft); margin-left: 8px; }

/* right-aligned numeric cells (amounts) */
.num { text-align: right !important; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* item entry table inside the form */
.table-wrap { overflow-x: auto; }
.item-table input[type="text"] {
  width: 100%;
  min-width: 90px;
  padding: 6px 8px;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #fff;
  color: var(--ink);
}
.item-table .item-desc input { min-width: 220px; }
.item-table .item-qty, .item-table .item-price { text-align: right; }
.item-table .item-pos { width: 34px; color: var(--ink-soft); }
.item-table .item-amount { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 0.38.0-DocPoPI — item note column (order form / order document workbench). */
.item-table .item-note { width: 140px; }
.item-table td.item-note input { min-width: 120px; font-size: 11px; }
/* 0.38.0-DocPoPI — the PO boilerplate block printed above the items table
   needs a little air after the two-column head area. */
.doc-notes-po-top { margin-top: 3mm; }
.item-table tfoot td { font-weight: 700; border-top: 2px solid var(--brand); }
.items-total-label { text-align: right !important; text-transform: uppercase; font-size: 12px; letter-spacing: .5px; color: var(--ink-soft); }
.items-total-value { font-size: 16px; color: var(--brand); white-space: nowrap; }
.item-del { color: var(--danger); font-size: 16px; line-height: 1; }

/* notes block on the detail page */
.card-note {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
  font-size: 14px;
  white-space: pre-line;
}

/* ---------- print view (quotation.php?print=1) ---------- */
.print-doc { max-width: 760px; margin: 0 auto; color: #111; font-size: 13px; }
.print-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  border-bottom: 3px solid var(--brand);
  padding-bottom: 14px;
  margin-bottom: 18px;
}
.print-brand { font-size: 20px; font-weight: 800; color: var(--brand); }
.print-brand span { font-size: 12px; font-weight: 600; color: var(--ink-soft); margin-left: 8px; }
.print-doc-type {
  font-size: 26px;
  font-weight: 800;
  color: #111;
  margin-top: 4px;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.print-meta { text-align: right; line-height: 1.6; }
.print-block { margin: 12px 0 20px; }
.print-kv th { width: 110px; color: var(--ink-soft); }
.print-items th {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .5px;
  color: #444;
  background: #f0f2f4;
  border-bottom: 1px solid #bbb;
}
.print-items td { border-bottom: 1px solid #ddd; padding: 7px 10px; }
.print-items tfoot td { border-top: 2px solid #111; font-weight: 700; }
.print-total-label { text-align: right !important; text-transform: uppercase; font-size: 12px; }
.print-total-value { font-size: 15px; white-space: nowrap; }
.print-notes { margin-top: 10px; white-space: pre-line; color: #333; }

@media print {
  body { background: #fff; }
  .topbar, .footer, .btn, .no-print { display: none !important; }
  main.container { max-width: none !important; padding: 0; }
  .print-doc { max-width: 100%; }
}

/* ============================================================
 * M3.1 — Quotation document (A4 template)
 * Mirrors the Excel quotation template the team uses:
 * brand header + centred title, customer block + grey info box,
 * grey item header, LT/Ref, grey Term bar + table, blue notes,
 * 3-column footer. Printed via browser (Ctrl+P → Save as PDF).
 * ============================================================ */

/* ---------- A4 page shell ---------- */
.doc {
  position: relative;
  width: 210mm;
  min-height: 297mm;
  margin: 0 auto;
  padding: 10mm 10mm 10mm;   /* 0.36 L1-v2: bottom 10mm keeps the footer's last line 10mm off the page edge */
  display: flex;             /* M4.5: footer pushed to the bottom via margin-top:auto; */
  flex-direction: column;    /* when content overflows the page it stays visible below it (no overlap) */
  background: #fff;
  color: #111;
  font-family: "Liberation Sans", Arial, sans-serif;
  font-size: 10pt;
  line-height: 1.45;
  box-shadow: 0 2px 18px rgba(16, 24, 40, .12);
}

/* ---------- header: logo left, No./Date right (fixed) ---------- */

.doc-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8mm;
  padding-bottom: 5px;
  border-bottom: 1px solid #111;
  width: 100%;               /* 0.35.2 — keep full .doc width even on the screen fallback */
  box-sizing: border-box;   /* padding/border stay inside so the divider spans full width */
}
.doc-logo,
.doc-logo-img {
  width: 50mm;                 /* 0.30.0: absolute 5 cm width */
  max-width: 50mm;
  max-height: none;
  height: auto;
}
.doc-head-right {
  display: flex;
  align-items: flex-start;
  gap: 5mm;
}
.doc-qr {
  width: 18mm;
  height: 18mm;
  flex: 0 0 auto;
  border: 0;
}
.doc-head-meta {
  font-size: 9.5pt;
  text-align: left;            /* M4.11: No./Rev./Date left-aligned in the corner */
  line-height: 1.5;
  white-space: nowrap;
}
.doc-head-meta .doc-page-num { text-align: right; }  /* page number stays right */
.doc-head-label { font-weight: 700; }

/* ---------- centred title below the header (no logo overlap) ---------- */
.doc-title {
  text-align: center;
  margin: 7mm 0 5mm;
  font-size: 17pt;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #111;
}

/* 0.29.0 (#2) — a blank line above the Shipping Address block, and a
   slightly tighter items-table title (the .doc-title above). */
.doc-shipment-label { margin-top: 14px; }

/* ---------- top: customer block (left) + info box (right) ---------- */
.doc-top {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6mm;
  margin-top: 4mm;
  align-items: start;
}
.doc-customer { font-size: 10pt; }
.doc-cust-name { font-weight: 700; font-size: 11pt; margin-bottom: 2px; }
.doc-cust-line { line-height: 1.4; }

.doc-infobox {
  width: 100%;
  border-collapse: collapse;
  /* 0.41.0 (#1) — Wolfgang 2026-09-13: the whole contact panel is 10pt.
     Two later blocks ALSO set this (the legacy M4.16 `.doc-infobox td`
     and `.doc-seller .doc-info-value`) — all of them carry the same
     10pt now, so no generation can win by accident. */
  font-size: 10pt;
}
.doc-infobox td {
  border: none;              /* KBP M3.3.3: no border lines */
  padding: 1px 0;
}
.doc-info-label {
  font-weight: 700;
  white-space: nowrap;
  width: 42%;
  padding-right: 8px;
  vertical-align: top;
}
.doc-info-value { color: #111; }
.doc-info-value, .doc-info-label { font-family: "Liberation Sans", Arial, sans-serif; }  /* M4.11 */
.doc-info-section {
  background: none;
  color: #555;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .4px;
  font-size: 10pt;   /* 0.41.0 (#1) — was 8pt; the panel is uniformly 10pt now */
  padding: 0 0 1px;
}

/* ---------- greeting ---------- */
/* KBP7-⑧（Wolfgang 2026-10-14）—— 开场白**上面再加一个空行**（他：*"Thank you for...
   上面再加一个空行"*）。原值 5mm 保留，再加一个全站统一的 `--doc-blank-line`（4mm）。
   🔵 `doc-greeting` **全仓只有报价单一个输出点**（`quotation-document.php`）⇒ 这条天然只影响这张纸。 */
.doc-greeting { margin-top: calc(5mm + var(--doc-blank-line)); font-size: 10pt; }

/* ---------- items table (grouped by product code) ---------- */
/* DocStd1.3: table-layout fixed + explicit thead widths → long Model /
   Description text never squeezes the Qty / Unit / Price / Amount columns
   (was compressed when the sheet spilled and the browser re-laid the table
   across viewport width). */
.doc-items {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  margin-top: 4mm;
  font-size: 9pt;
}
.doc-items th {
  background: #cccccc;        /* KBP M3.3.3: 20% gray header */
  border: none;
  border-bottom: 1.5px solid #111;   /* only a bottom rule under the header */
  padding: 4px 8px;
  text-align: left;
  font-size: 8.5pt;
  font-weight: 700;
  text-transform: capitalize;        /* only first letters capitalised */
  letter-spacing: .2px;
}
.doc-items td {
  border: none;               /* KBP M3.3.3: no vertical / inner lines */
  padding: 3px 8px;
  vertical-align: top;
  overflow-wrap: break-word;  /* DocStd1.3: never overflow a fixed column */
}
/* group boundaries: a single horizontal rule between items (6.4) */
.doc-item-inner td { border-top: none; border-bottom: none; }
.doc-item-last td { border-bottom: 1px solid #999; }
.doc-item-no { color: #333; white-space: nowrap; }
.doc-code { font-weight: 700; }
.doc-item-name { font-weight: 600; }
.doc-item-note {
  display: inline-block;
  /* 🔴 1.73.0-DocTypeScale（批 3／3 · 已定项 5）—— 去**浏览器合成倾斜**。
     这里原来写的是 `font-style: italic`，而本项目**只嵌了 Regular 与 Bold 两个字型**
     （`@font-face` 见文件末尾的 1.67.0-DocFont 块）⇒ 斜体是浏览器**自己斜切**出来的
     （"合成的"），既不整齐又和将来可能的真斜体字形不是一回事。
     他 2026-10-15 拍板：*"所有'浏览器合成的倾斜'改成正常字体；italic 字形暂不嵌入"*。
     ⚠️ **显式写 `normal`**（不靠"删掉 italic"）—— 删掉的话，任何一条更早/更宽的
        `font-style: italic` 都会重新生效，而且从 CSS 原文上看不出"这里是有意不斜的"。
     ⚠️ `font-size: 8.5pt` **保留**（实际值由下面的权威块给 9pt；这里不动它，
        免得把"两代并存"的既有事实改成第三种）。 */
  font-style: normal;
  font-size: 8.5pt;
  color: #333;
}
.doc-item-ref {
  font-size: 8.5pt;
  color: #333;
}
.doc-item-ref a { color: #0432FF; text-decoration: none; }
.doc-qty { text-align: right; }
.doc-unit { text-align: center; }
.doc-price { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.doc-amount { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.doc-th-item { width: 7%; }
.doc-th-desc { width: 47%; }
.doc-th-qty { width: 10%; text-align: right !important; }
.doc-th-unit { width: 10%; text-align: center !important; }
.doc-th-price { width: 13%; text-align: right !important; }
.doc-th-amount { width: 13%; text-align: right !important; }
/* 🔴 1.73.0-DocTypeScale（批 3／3 · 已定项 5）—— `No line items.` 也去倾斜
   （与 `.doc-item-note` / `.doc-ref` 同批；三处都是"浏览器合成倾斜"）。 */
.doc-empty { padding: 12px 8px !important; color: #777; font-style: normal; }

/* product Model (first, bold) + Description (second) + Note (third) + Ref (fourth)
   lines — compact, each one its own row; empty rows collapse (M4.3 + M4.7) */
.doc-item-name { display: block; font-weight: 400; line-height: 1.25; }
.doc-model-line { font-weight: 700; display: block; font-size: 9pt; line-height: 1.25; }
/* 🔴 1.73.0-DocTypeScale（批 3／3 · 已定项 11）—— `line-height` **1.25 → 1.45**。
   1.68.0 只给**报价单**的档位对齐做过这件事（`.doc-quotation-table … .doc-line { line-height: 1.45 }`），
   而这条是**六张共用**的 ⇒ 另外五张纸的备注行仍比 Description 行矮一档。
   ⇒ 平到与 Description 同一个值（`.doc` 上的 `1.45`）⇒ 一个产品块里
      「Model / Description / Note」三行**行高一致**（验收：六张的描述块逐行对齐）。
   ⚠️ 与去斜体**同批**：只去斜体不平行高，两行之间仍差一条行距（他 11:5x 明确"一起平"）。 */
/* 🔄 1.77.0-ItemText —— item note 现在**可以含真换行**（编辑器是 textarea）。
   这一句把 `\n` 印成真换行；没有它，HTML 只在宽度边界折行、把换行折叠成一个空格。
   🔵 老单据不受影响：线上实测 487 行明细与 243 张冻结快照里**一处 `\n` 都没有**。
   🔵 多行 note 仍整块住在 item 自己的单元格里 ⇒ "下一个 item 排在它之后"由表格结构保证。 */
.doc-item-note { line-height: 1.45; display: block; white-space: pre-line; }
.doc-item-ref  { display: block; margin-top: 3px; line-height: 1.25; }

/* ---------- meta rows: LT Estimated / Ref ---------- */
.doc-meta {
  width: 100%;
  border-collapse: collapse;
  margin-top: 3mm;
  font-size: 9pt;
}
.doc-meta td { padding: 2px 0; }
.doc-meta-label { font-weight: 700; width: 30%; white-space: nowrap; }
/* 🔴 1.73.0-DocTypeScale（批 3／3 · 已定项 5）—— Ref **值**去倾斜（第三处，也是最后一处）。
   ⚠️ 与它相邻的 `.doc-item-ref`（明细行里那个）本来就**不斜**，别顺手给它加 `normal`
      —— 那条规则在 1343 行，加了只会让"谁在管这件事"变模糊。
   ⚠️ **不动** 屏幕 UI 那一处 `font-style: italic`（选择器是 `pipeline-info` 的 `summary`）：
      它是 pipeline 的 20px ⓘ 圆圈，`font-family: Georgia` 的**真斜体**，不是合成倾斜。
      ⚠️ 这里**不写行号** —— 上一版写的 `2938` 已经漂到 2970 了；行号会烂，选择器不会。 */
.doc-ref { font-style: normal; color: #333; }

/* ---------- Term bar + table ---------- */
.doc-term { margin-top: 4mm; }
.doc-term-title {
  background: #dcdcdc;
  border: 1px solid #111;
  padding: 4px 10px;
  font-weight: 700;
  font-size: 9.5pt;
  letter-spacing: .4px;
  /* KBP M3.3.3: shown as "Term" (no forced uppercase) */
}
.doc-term-table {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid #111;
  font-size: 9pt;
}
.doc-term-table td {
  border: 1px solid #111;     /* KBP M3.3.3: inner + outer borders identical thin solid */
  padding: 4px 8px;
}
.doc-term-label { background: #f0f0f0; font-weight: 700; width: 22%; white-space: nowrap; }
/* 0.39.0-DocUnify (#6) — "Table reference": the one shared reference of the
   whole item table, printed as the last row of the item body. */
/* 0.41.0 (#5) — "Ref: <file>" reads as part of the table body: no bold,
   explicit LEFT alignment (`.doc-table tbody td:nth-child(1)` centres every
   first cell, and this row's single colspan=8 cell is exactly that). */
.doc-items-ref td {
  font-size: 9.5pt;
  line-height: 1.45;
  text-align: left !important;
}
.doc-items-ref .doc-ref-label { font-weight: 400; padding-right: 4px; }

/* 0.41.0 (#6) — the Packing List packaging block.
   0.80.0-PlLayout — 位置从"item 表上方、整幅宽度"搬进**右栏联系人方框之下**
   （Wolfgang 2026-09-25），形态也从 4 列表格改成**文字形式的键值对**：
   右栏只有半页宽，4 列会挤成一团。用 `grid-template-columns: max-content 1fr`
   让标签列自己取最宽的那一项 —— **不下载体量**，所以字号/字体变了也不会错位。 */
.doc-pack {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 4px;
  width: 100%;
  margin: 0 0 4mm;
  font-size: 10pt;          /* 0.41.0 (#1) — same size as the rest of the body */
  /* 1.88.0-PlPolish（Wolfgang 2026-10-18）—— *"包装和物流信息，与联系人表格的左列的文字
     对齐，增加行间距"*。
     🔴 `padding-left` 补的是**真实的 8px 偏移**：联系人表格的 `.doc-infobox td` 有
        `padding: 3px 8px` ⇒ 它的标签从 8px 起；而本块是 grid、标签从 x=0 起
        ⇒ 两块在同一栏里左边缘差 8px（截图里 `Package:` 比 `Your contact` 明显偏左）。
     🔴 `row-gap` 补的是**行距**：原先是 0，行高由 line-height 决定 ⇒ 比联系表格
        （`td` 有 3px 上下内边距）紧约 6px/行。 */
  padding-left: 6px;   /* = `.doc-infobox td` 的 padding-left —— 两块用同一个值才对得齐 */
  row-gap: 3px;
}
/* 1.88.0-PlPolish —— 标签从 **700 → 400**：联系面板 1.71.0 起标签一律不加粗
   （`doc_contact_panel()` 的 "全不加粗"），包装块留着粗体会让同一栏里两块重量不同。
   直接改在这里而不是追加覆盖：同特异性靠书写顺序决胜，搬一次文件就静默失效。 */
.doc-pack .doc-pack-label { font-weight: 400; white-space: nowrap; }
.doc-pack .doc-pack-value { min-width: 0; }

/* 0.40.0-ListUnify (#2) — the Freight line: the last row of the item body,
   before the table-reference row. Kept visually quiet but in-band with the
   item rows so it reads as part of the goods table, not as a footnote. */
.doc-freight td { border-top: 1px solid #dcdcdc; }
.doc-freight-label { font-weight: 600; }
/* 1.51.0-ItemOrder（Wolfgang 2026-10-12）—— 纸面的 Freight 标签与明细的 Model **一样加粗**。
   ⚠️ 选择器为什么是**两个类**：0.27.0-DocFmt 那条「item content never bold」
   （`.doc-item-name { font-weight: 400 }` —— 与 `.doc-freight-label` 特异性相同、且写在
   **更后面**）会把字重压回 400，所以只把 600 改成 700 是**没用的**。
   `.doc-freight .doc-freight-label`（2 个类 > 1 个类）与文件位置无关地赢回来 ——
   这是本项目"多代 CSS 并存"的常规解法：**比特异性，不比先后**。 */
.doc-freight .doc-freight-label { font-weight: 700; }

/* ---------- blue notes (one per line) ---------- */
.doc-notes {
  margin-top: 5mm;
  /* 0.40.0-ListUnify (#3) — Wolfgang 2026-09-13: notes at 10pt, the same size
     as the item table body/header (L2 v2.4). They used to be set smaller and
     read as a footnote; they are boilerplate the customer must actually read. */
  font-size: 10pt;
  color: #0432FF;
  line-height: 1.5;
}
.doc-note { padding: 1px 0; }

/* ---------- footer: company info, pushed to the bottom (M4.5: static —
   follows the content when it overflows the page, never overlapped) ---------- */
.doc-footer {
  display: flex;
  gap: 5mm;
  position: static;
  left: auto;
  right: auto;
  bottom: auto;
  margin-top: auto;          /* pinned to the bottom when space allows */
  border-top: 1px solid #9a9a9a;   /* L1-v4: footer divider — solid line */
  padding-top: 1.5mm;
  font-size: 6.5pt;              /* 0.36 L1-v3: footer 6.5pt minimum legible size */
  line-height: 1.02;         /* 0.36 L1-v2: keep footer tight (was 1) */
  color: #999;               /* faint watermark tone */
  opacity: .6;
  page-break-inside: avoid;
  break-inside: avoid;
}
.doc-footer-col {
  flex: 1 1 0;
  min-width: 0;
  /* 0.66.0-ListUI — 这一行原来还有 `white-space: pre-line`，而页脚标记走的是
     `nl2br()`（lib/doc-render.php 的 doc_chrome_footer）：两者**重复生效** —— 每个
     换行先变一个 `<br>`，源码里那个换行又被 pre-line 当成第二个断行 → 6 行占 11 个
     行盒，页脚块 77.95pt（27.5mm）而不是设计意图的 16mm。
     单一实现 = `nl2br()`（HTML 里看得见断行）。pre-line 已移除。

     0.70.0 批（DocPolish）— Wolfgang：*"页脚公司信息的行间距增大一点，太挤了"*。
     行距 1 → **1.25**（6.5pt 字号 → 每行 8.125pt；左/中栏各 6 行 = 48.75pt）。
     ⚠️ 页脚变高 = 正文少高度：内容让位数字必须**同进同退**（见下面 0.57 块的
     `padding-bottom: 31mm`），两者成对由 `guard_footer_single_break` 双向往返拦。 */
  line-height: 1.25;
  text-align: left;             /* 0.36 L1-v2: every footer column left-aligned */
}
.doc-logo-link { display: inline-block; }
.doc-topnav {
  display: inline-block;
  margin: 0 0 6px 2mm;
  font: 600 9pt system-ui, "Segoe UI", Arial, sans-serif;
  color: var(--brand, #1f4e79);
  text-decoration: none;
}
.doc-topnav:hover { text-decoration: underline; }

/* ---------- order documents (PI / Invoice / PL, KBP M4) ---------- */
.doc-warn {
  background: #fff7e6;
  border: 1px solid #f0c36d;
  color: #8a6d1d;
  padding: 8px 12px;
  margin-bottom: 6mm;
  font-size: 9.5pt;
  border-radius: 4px;
}
.doc-total {
  margin-top: 3mm;
  text-align: right;
  font-weight: 700;
  font-size: 10pt;
  font-variant-numeric: tabular-nums;
}

/* ---------- paginated sheets (doc-pages.js, KBP M4.1) ----------
 * When JS runs, the .doc is split into fixed-height .doc-page sheets so the
 * header (logo + QR + No./Date + page number) repeats at the same position on
 * every page and the company footer appears only on the LAST page. */
.doc-pages { width: 210mm; margin: 0 auto; }
.doc-page {
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  width: 210mm;
  height: 276.5mm;               /* A4 297mm − 10mm top − 10mm bottom */
  margin: 0 auto 6mm;
  padding: 10mm 10mm 20mm;       /* M4.9: 20mm bottom = footer zone; content
                                    stops above it, per-page footer never clipped */
  background: #fff;
  color: #111;
  font-family: "Liberation Sans", Arial, sans-serif;
  font-size: 10pt;
  line-height: 1.45;
  box-shadow: 0 2px 18px rgba(16, 24, 40, .12);
  overflow: visible;          /* M4.6: never clip the footer on long quotes */
}
.doc-page .doc-head { flex: 0 0 auto; }
.doc-page .doc-footer {
  position: static;
  left: auto;
  right: auto;
  bottom: auto;
  margin-top: auto;              /* pinned to the bottom of the LAST page only */
  opacity: .6;
}
.doc-page-num {
  margin-top: 2px;
  font-size: 9pt;
  font-weight: 400;
  color: #444;
}

/* ---------- multi-page support: keep blocks together ---------- */
.doc-head, .doc-title, .doc-top, .doc-greeting { break-inside: avoid; page-break-inside: avoid; }
.doc-items tr { break-inside: avoid; page-break-inside: avoid; }
.doc-term, .doc-meta, .doc-footer { break-inside: avoid; page-break-inside: avoid; }

/* 🔴🔴 1.77.3-TailClip（Wolfgang 2026-10-16 报障：*"只到 payment terms 就结束了，
   note 内容不见了，也没有分第二页"*）—— **尾部四块必须允许被拆开**。

   真事故（CDP 实测量出来的，不是推理）：`.doc-notes` 原来在上一行那条 `avoid` 里。
   正文流一旦**比内容盒高一点点**（他的报价单 #110：两条 item note 共 10 行 ≈ 61mm，
   流高 ≈242mm vs 盒 226mm），paged.js 会：
     ① 把放不下的那一块整块挪走（实测它被搬到**页面内容区顶部** y=151，压在页眉克隆下面）；
     ② 却**不新建第 2 页** ⇒ 页码仍印 `Page 1 of 1` ⇒ 那一块**静默消失**。
   实测阈值（同一张单，只改 note 行数）：2/5/7 行 ✅ 尾在 · **9/11 行 ❌ 尾丢** · 14 行 ✅ 正常分 2 页
   （14 行时**表格自己**超高 ⇒ paged.js 在表内断页 ⇒ 反而正常）。

   ⇒ 正解 = **让尾部能断**（`auto`）：paged.js 会把这一块拆到两页上，**内容一个字不丢**；
     实测：他的单（7+3 行）、9 行、11 行**尾部全部保住**，2/5/7 行与 14 行的行为不变。
   🔴 A4Split 的承诺**不受影响**：`Term 表不许被拆` 由 `.doc-term-table tbody` 那条
      `avoid`（下面文件内）承担 —— 那是 paged.js 表格分支**真会读**的一层。
   ⚠️ 头部四块（`.doc-head/.doc-title/.doc-top/.doc-greeting`）**保持 avoid**：它们永远排在
      页首、只有"页眉本身高过一整页"才可能触发同一毛病（另一类，尚未发生）。 */
.doc-term, .doc-notes, .doc-meta, .doc-footer { break-inside: auto; page-break-inside: auto; }

/* ---------- clean print body (no KBP shell) ---------- */
.doc-print-body { background: #fff; }
.doc-print-body .doc { margin: 0 auto; min-height: auto; box-shadow: none; }

/* ---------- work view: form panel + live A4 preview ---------- */
/* 1.53.0-EditPanel2（需求单㊸ · Wolfgang 2026-10-12）—— 左栏 420 → **560px**。
   他的原话：*"把左栏编辑区宽度调大一些"*，随后拍板 *"改到560px"*。
   1.53.1-EditPanel2-2（同一天，他看到实物的反馈）—— *"左栏有点过宽了，一是用不了这么宽，
   二是右边A4需要左右滚动了。左侧可以缩减25%"* ⇒ 拍板 *"不能回420，可以改为480"*。
   ⇒ 560 → **480px**（比他说的 -25% = 420 宽 60px：他明确否掉了 420）。
   🔴 只动这一个数：`.w-sm` / `.w-md` 两个**控件档一个字没动** —— 它们是
      "输入框有多宽"的承诺，与"栏有多宽"是两件事（`tests/m65:123-125` 钉着那两个值）。 */
.doc-work {
  display: grid;
  grid-template-columns: 480px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}
/* 1.45.0-WorkFold（需求单㉞ · Wolfgang 2026-10-10）—— **PDF 面板正在显示预览**时，
   把下面这整块 A4 工作台收起来（编辑列 + 右侧实时 A4 一起），页面上只留一份"A4 的样子"。
   他的原话：*"Generate后自动生成PDF的预览界面，此时下面的A4编辑页面仍然存在。
   预览前关闭编辑页面会比较好"* ⇒ 拍板：预览态（review / ok）就收、收整块、
   并给一个开关随时翻回来。

   🔴 **为什么是属性选择器，而不是 `hidden`**：`.doc-work` 自己的 `display: grid` 是**作者样式**，
   会盖过浏览器 UA 的 `[hidden] { display: none }`（作者样式永远赢 UA）
   ⇒ 给元素加 `hidden` 属性**根本不会隐藏**（这个坑本项目记过）。
   本选择器多一个属性 = 特异性更高（0,2,0 > 0,1,0）⇒ 稳稳赢。
   🔴 **折叠只有一个实现**：`assets/doc-pdf.js` 的 `applyWorkFold()`。页面里不许再写第二处。 */
.doc-work[data-kbp-work-folded="1"] { display: none; }
.doc-work-panel {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 18px 20px;
  position: sticky;
  top: 16px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}
/* 0.90.0-EditPanel —— 左侧编辑区**唯一**的一套字号。
   Wolfgang 2026-09-27："对所有的 A4 页面左侧编辑区做一次格式改进，目标为将字体，字号统一…"
   从前这里是三条各写各的：`label` 12px、`input`/`textarea` 13px，而 **`select` 掉在外面**
   （沿用全局那条 `font-size: 15px`）→ 同一个字段里标签 12、文本框 13、下拉 15。
   现在四类元素五条规则、全部从这一个地方出；**页面里不许再写 `style="font-size:…"`**（守卫钉）。 */
.doc-work-panel .form-frame-title,
.doc-work-panel label { font-size: 12px; }
.doc-work-panel input,
.doc-work-panel select,
.doc-work-panel textarea { font-size: 13px; }
.doc-work-panel .field { margin-bottom: 12px; }
.doc-work-panel .field-hint,
.doc-work-panel .form-hint { font-size: 11px; color: var(--ink-soft); margin: 4px 0 0; }

/* ---- 0.92.0-Fix5 —— 字段说明：**短的小字直接显示，长的收成一枚 ⓘ 便签** ----
   Wolfgang 2026-09-28：*"打了很多的？不美观也不实用。重新修改如下：文本短的直接小字体
   显示，文本长的用带圈i号隐藏，点击时出弹出便签显示。就像在OC列表统计栏最右侧的i提示符
   设计一致。"*
   形体与 `doc_pipeline_strip()` 那个 ⓘ **同一形状**（`<details><summary>` + 绝对定位的便签），
   **零 JS**：展开状态由 `<details>` 自己管。长短由 `edit_label()` 按字数判（见 lib/ui.php）。 */
.field-head { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; }
.field-head label { margin: 0; }
/* 短说明：就是原来 `.field-hint` 的位置，只是不再隐藏 */
.field-note, .doc-work-panel .field-hint {
  font-size: 11px; color: var(--ink-soft); margin: 4px 0 0;
}
.hint-note { position: relative; flex: none; margin: 0; }
.hint-note > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%;
  border: 1px solid var(--line); background: #fff;
  font-size: 11px; font-weight: 700; line-height: 1; color: var(--ink-soft);
}
.hint-note > summary::-webkit-details-marker { display: none; }
.hint-note > summary:hover,
.hint-note[open] > summary { background: var(--brand); border-color: var(--brand); color: #fff; }
/* 🔴 1.23.0-PayFix（Wolfgang 2026-10-05 报障）—— 锚点从 `right:0` 改成 `left:0`。
   报障原话：*"在Register Payment页面，有很多！在点击时弹出的标签靠左出了窗口而无法看到"*。

   根因：`right:0` 是照 **OC 列表统计条最右端那个 ⓘ** 抄的（那条注释自己写着"形体与
   `doc_pipeline_strip()` 那个 ⓘ 同一形状"）—— 可**统计条那个 ⓘ 其实是另一个类**
   （`.pipeline-info` / `.pipeline-info-body`，见本文件 :2831），跟这一条没有任何关系。
   而 `.hint-note` 的**唯一渲染者**是 `edit_hint_note()`（`lib/ui.php`），它只出现在
   **字段标签右侧**，也就是**页面左侧** ⇒ "右对齐锚点 + 固定 320px 向左铺"必然把面板推到窗口外。

   ✅ **实测（不是估计）**：探针页（真实 `.card > .form-frame > .field` 嵌套 + 本文件）
   在 1280 / 1440 / 1600 三种视口下量 `getBoundingClientRect()`：
     · 改前：**7 个面板全部出界**，最坏 **185px**，第一个矩形 `L=-185 / R=135`（左边被切掉）；
     · 改后：**0 个出界**，第一个矩形 `L=119 / R=439`（完整可见）。
   ⚠️ 想再动这一行之前先重量一遍 —— 别按"看起来该生效"推理。 */
.hint-note-body {
  position: absolute; left: 0; right: auto; top: 22px; z-index: 20;
  width: 320px; max-width: 88vw; padding: 10px 12px;
  background: #fff; border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 6px 20px rgba(16, 24, 40, .14);
  font-size: 12px; line-height: 1.5; color: var(--ink-soft); text-align: left;
}

/* ---- 控件宽度的两个档（唯一来源）：从前是散落各处的 `style="max-width:110px"` ---- */
.doc-work-panel .w-sm { max-width: 130px; }
.doc-work-panel .w-md { max-width: 210px; }
/* 左栏自己的两列网格：列间距收窄一点（`.grid2` 在别处仍是 20px）。
   1.53.0-EditPanel2（需求单㊸）—— 12 → 14px（栏宽从 420 放到 560）。
   1.53.1-EditPanel2-2 —— **回到 12px**：栏宽收回到 480 之后，14px 的间距在这个宽度上
   显得松（观感与 420 时代一致即可）。
   ⚠️ "短的同类项排一行"用**这个** `.grid2` 就够（Freight│VAT、Columns│VAT rate、
   Buyer│Seller…）：按纸面次序看，**没有任何三连的短字段**，
   所以**没有**再加一个 `.grid3`（不加没人用的规则）。 */
.doc-work-panel .grid2 { gap: 0 12px; }
/* 🔴 1.54.0（Wolfgang 2026-10-12 现场纠正）—— 这里曾短暂有过第三档 `.w-half`
   （"两个条款框加长到50%"）。他看过实物后澄清：*"条款框被改为了50%，重新改为100%，
   我本意是想让框长一点能显示多点字"* ⇒ **撤回**：这条规则与 5 个页面上的 `class="w-half"`
   一起删掉（条款框回到满宽 = 100%）。
   ⚠️ 留着它就是"没人用的影子规则"（本项目已为影子规则付过账）⇒ 一并删除，不留空壳。
   ⚠️ 注释里**不要写出完整选择器**（`.doc-work-panel .` + 那个类名）——
      `tests/m130` 判"这条规则不存在"时扫的是**原文**，散文里写全了会把它顶红（老坑）。 */
/* 0.25.1-POwb.1 — compact draft line-item editor (PO workbench left panel):
   tighter cells + inputs so more rows / the terms below stay visible. */
.doc-work-panel .po-edit-table { font-size: 13px; }
.doc-work-panel .po-edit-table th,
.doc-work-panel .po-edit-table td {
  padding: 3px 4px;
  vertical-align: middle;
  border-bottom: 1px solid var(--line);
}
.doc-work-panel .po-edit-table thead th {
  padding: 4px 4px 5px;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .4px;
}
.doc-work-panel .po-edit-table input {
  padding: 4px 6px;
  font-size: 13px;
  height: 28px;
  line-height: 1;
  min-height: 0;
}
.doc-work-panel .po-edit-table .btn-del {
  width: 22px;
  height: 22px;
  padding: 0;
  font-size: 11px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.doc-work-preview {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}
.doc-work-preview-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  font-weight: 700;
}
#doc-preview {
  display: block;
  width: 100%;
  height: 82vh;
  border: none;
  background: #fff;
}
/* 0.39.0-DocUnify (#3) — defensive twin of the rule above: the PL / Invoice
   workbench shipped its iframe without id="doc-preview", so it fell back to the
   browser default width (300px) and the A4 preview looked cut off. Matching on
   the container keeps every workbench correct even if the id is forgotten. */
.doc-work-preview > iframe {
  display: block;
  width: 100%;
  height: 82vh;
  border: none;
  background: #fff;
}

/* ---------- work view: item editor cards ---------- */
.doc-work-section-title {
  margin: 22px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--ink-soft);
}
.doc-work-section-title:first-child { margin-top: 0; }
.doc-item-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 12px;
  background: #fff;
}
.doc-item-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  font-size: 13px;
}
.doc-item-card .field-compact { margin-bottom: 10px; }
/* 1.53.0-EditPanel2（需求单㊸ · Wolfgang 2026-10-12）—— 卡片头右侧的 ▲▼。
   报价单编辑页的明细是"一条一表单"的卡片，另外五个工作台的行表那套
   `doc_item_move_buttons()`（搬 DOM）在这里用不上 ⇒ 这两枚是**提交按钮**
   （`name="move_up"` / `move_down`），由服务端交换相邻两组。
   `margin-left: auto` 把它们顶到卡片头最右边，与 `Item 1 · 2 tiers` 拉开。 */
.doc-item-card-move {
  margin-left: auto;
  display: inline-flex;
  gap: 4px;
}
.doc-item-card-move .btn[disabled] { opacity: .35; cursor: default; }
/* 0.39.0-DocUnify (#7) — the item card is the scroll target after "Add tier";
   highlight it briefly so the user sees where the page landed. */
.doc-item-card:target {
  border-color: var(--accent, #1f6feb);
  box-shadow: 0 0 0 3px rgba(31, 111, 235, .15);
}
/* 0.39.0-DocUnify (#1) — read-only Amount / VAT / Total Sum strip in the Order
   workbench left column (mirrors the A4 footer built from the source PI). */
.wb-money {
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 12px;
  background: var(--card);
}
.wb-money-row { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; font-size: 13px; }
.wb-money-row span { color: var(--ink-soft); }
.wb-money-total { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 5px; }
.doc-ref-current { margin-bottom: 6px; font-size: 12px; word-break: break-all; }
.doc-ref-current a { color: var(--accent, #1f6feb); }
.doc-ref-remove { margin-left: 8px; color: var(--ink-soft); font-size: 11px; }
.doc-tier-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  margin: 4px 0 10px;
}
.doc-tier-table th {
  text-align: left;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .3px;
  color: var(--ink-soft);
  padding: 2px 4px;
}
.doc-tier-table td { padding: 2px 4px; vertical-align: middle; }
.doc-tier-table input {
  width: 100%;
  min-width: 0;
  padding: 4px 6px;
  font-size: 12px;
}
.tier-amt {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  white-space: nowrap;
  padding-right: 8px !important;
}
.doc-item-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.doc-item-add { margin-top: 4px; }

@media (max-width: 1100px) {
  .doc-work { grid-template-columns: 1fr; }
  .doc-work-panel { position: static; max-height: none; }
}

/* ---------- print rules ---------- */
/* KBP DocStd1.3 (0.21.3): @page margin MUST be 0. Paged.js 0.4.3 with
   PagedConfig.content = a custom flow element IGNORES @page margin and sizes
   every .pagedjs_page to the full 297mm A4 height. Chrome, however, honours
   @page margin — with margin:10mm each 297mm sheet spilled 20mm beyond the
   277mm printable area and the browser split every Paged.js page into TWO
   physical A4 pages (PDF showed "Page 1 of 2" but produced 4 blank-padded
   pages). With margin:0 the physical page == the Paged.js page; all document
   margins come from .pagedjs_page_content padding (26mm/10mm/20mm/10mm). */
@page { size: A4; margin: 0; }

@media print {
  body { background: #fff !important; }
  .topbar, .footer, .btn, .no-print,
  .doc-work, .doc-work-panel, .doc-work-preview { display: none !important; }

  /* Paginated sheets (doc-pages.js) — the primary print path. */
  .doc-pages { width: auto; margin: 0; }
  .doc-page {
    width: auto;
    height: 276.5mm;          /* small buffer so browsers never spill a blank page */
    margin: 0;
    padding: 10mm 10mm 20mm;  /* M4.9: bottom 20mm = footer zone in print too */
    box-shadow: none;
    page-break-after: always;
    break-after: page;
  }
  .doc-page:last-child { page-break-after: auto; break-after: auto; }

  /* M4.7: same fixed header / footer on EVERY page (browser repeats
     position:fixed elements per page in print mode — that's the
     primitive that makes "every page same chrome" work without JS). */
  .doc-page .doc-head {
    position: fixed;
    top: 0; left: 0; right: 0;
    margin: 0;
    background: #fff;
    padding-bottom: 2mm;
    border-bottom: 1px solid #eee;
  }
  .doc-page .doc-footer {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    margin: 0;
    padding-top: 2mm;
    border-top: 1px solid #d8d8d8;
  }
  /* @page corner boxes — page numbers (Firefox + Chrome 79+). */
  @page :top-right   { content: ""; }  /* leave a slot for the right-aligned header */
  @page :bottom-right { content: ""; }

  /* Fallback when JS did not run: .doc stays in the flow, footer only on the
     last page (pinned by flex), header only on page 1.
     DocStd1.3: @page margin is 0 (see above), so give the raw .doc its own
     10mm margins in print to keep a safe frame when the Paged.js pipeline
     did not run. */
  .doc {
    width: auto;
    min-height: auto;
    margin: 0;
    padding: 10mm;
    box-shadow: none;
    display: flex;
    flex-direction: column;
  }
  .doc-logo-img { max-height: none; }
  .doc-footer {
    position: static;         /* M4.1: footer ONLY on the final page, never repeated */
    left: auto;
    right: auto;
    bottom: auto;
    margin-top: auto;
    opacity: .55;
  }
  .doc-items tr, .doc-term, .doc-note, .doc-footer { page-break-inside: avoid; }
}

/* ---------- M4.3 file library ---------- */
.tag-strip { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.tag-chip {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid #d3d1c7;
  background: #f1efe8;
  color: #444441;
  font-size: 12px;
  text-decoration: none;
  line-height: 1.4;
}
.tag-chip:hover { border-color: #888780; }
.tag-chip-active { background: #185fa5; border-color: #185fa5; color: #fff; }
.badge-doctype-datasheet { background: #e1f5ee; color: #0f6e56; border-radius: 4px; padding: 2px 8px; font-size: 12px; }
.badge-doctype-solution  { background: #e6f1fb; color: #185fa5; border-radius: 4px; padding: 2px 8px; font-size: 12px; }
.badge-doctype-other     { background: #f1efe8; color: #5f5e5a; border-radius: 4px; padding: 2px 8px; font-size: 12px; }
/* 1.75.1-NavClocks-2（Wolfgang 2026-10-16）—— 发布徽章在**报价单列表**里不要加粗。
   `.badge` 自带 `font-weight: 600`，而这一格是 12px、旁边 `Ver. N` 是常规体（400），
   600 那颗徽章在整行里显得突兀（他：*"Quotation list中，Released.Rev0去除加粗"*）。
   ⚠️ 只在列表那一个调用点传 `$plain = true`（`kbp_release_badge()` 的第 2 个参数）⇒
   详情页 h1 旁边那颗**保持 600**（默认值不变）。 */
.badge-rev-plain { font-weight: 400; }
.btn-danger-ghost { color: #b3261e; border-color: #e6b4b0; }
.btn-danger-ghost:hover { background: #fdecea; }

/* 1.75.0-NavGroup — the standalone username dropdown that used to live here is
   gone: the user name is now the first line INSIDE the gear dropdown, and the
   old block (menu / toggle / dropdown / item) had no other reader. */

/* M4.9: To Do priority chips + status lamps */
.prio-group, .lamp-group { display: inline-flex; gap: 3px; }
.todo-flag { margin: 0; display: inline-flex; }
.prio-chip {
  border: 1px solid #c9c9c9;
  background: #fff;
  color: #555;
  font-size: 11px;
  font-weight: 700;
  min-width: 22px;
  padding: 2px 5px;
  border-radius: 4px;
  cursor: pointer;
}
.prio-chip:hover { border-color: #1f4e79; color: #1f4e79; }
.prio-chip.prio-active { background: #1f4e79; border-color: #1f4e79; color: #fff; }
.lamp {
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.18);
  cursor: pointer;
  padding: 0;
}
.lamp-green { background: #2e9e44; }
.lamp-amber { background: #f0a500; }
.lamp-red   { background: #c92a2a; }
.lamp:hover { box-shadow: 0 0 0 2px rgba(0,0,0,.15); }
.lamp-active { box-shadow: 0 0 0 2px #1f4e79; }

/* M4.9: topbar — logo stands alone; app title block next to the nav links */
.topnav-title {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.15;
  margin-right: 10px;
  padding-right: 14px;
  border-right: 1px solid rgba(255,255,255,.22);
}
.topnav-title .app-name {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: .5px;
  color: #fff;
}
.topnav-title .app-sub {
  font-size: 9px;
  letter-spacing: .3px;
  color: rgba(255,255,255,.72);
}

/* M4.10: nav dropdowns (Library / Settings)
   1.75.0-NavClocks —— 三个分组组名（QT·PF·OC / PO·PL·CI / Library）**加粗**：
   `.nav-link` 本身是 600，这里写到 700 让"组"比"单个入口"重一档。
   ⚠️ 必须写在 `font: inherit` **之后** —— 那条 shorthand 会把 font-weight 重置掉。
   1.75.1-NavClocks-2 —— 组名里的连字符改成了居中点（`·`），是 layout.php 的字面量；
   这里一个字不用动（粗细规则按类走，不看文字）。 */
.nav-drop { position: relative; display: inline-flex; }
.nav-drop-toggle { background: transparent; border: 0; cursor: pointer; font: inherit; font-weight: 700; }
.nav-drop-toggle .caret { font-size: 9px; opacity: .7; margin-left: 3px; }
.nav-ico { vertical-align: -2px; display: inline-block; }
.nav-drop-panel {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 180px;
  background: #fff;
  border: 1px solid #d8d8d8;
  border-radius: 4px;
  box-shadow: 0 4px 14px rgba(0,0,0,.12);
  z-index: 210;
  padding: 4px 0;
}
.nav-drop:hover .nav-drop-panel,
.nav-drop:focus-within .nav-drop-panel { display: block; }
.nav-drop-item {
  display: block;
  padding: 8px 14px;
  color: #1f2937;
  text-decoration: none;
  font-size: 13px;
}
.nav-drop-item:hover, .nav-drop-item:focus { background: #f3f4f6; }
.nav-drop-item.nav-drop-active { font-weight: 700; }

/* 1.75.0-NavGroup — a group toggle (QT-PF-OC / PO-PL-CI / Library / gear) lights
   up while the page inside it is open; same palette as the active nav link. */
.nav-drop-toggle-active { color: #fff; background: rgba(255,255,255,.18); }
/* 1.75.0-NavGroup — the user name is the first, non-clickable line of the gear
   menu; it replaced the standalone username chip that used to sit after the
   separator. */
.nav-drop-head {
  display: block;
  padding: 7px 14px 5px;
  color: #6b7280;
  font-size: 12px;
  letter-spacing: .3px;
  border-bottom: 1px solid #eceef1;
  margin-bottom: 4px;
}
/* 1.75.0-TopClocks — the top bar's world clock (one set for the whole company,
   edited by an admin on the Preference page). It is rendered only when
   configured, so there is never an empty box. tabular-nums keeps the digits from
   jittering the bar every minute.
   1.75.1-NavClocks-2 —— **4 格改 2 格、上下分布**（他：*"时钟由4个改为2个，上下分布，
   且按时间对齐"*）：容器仍是网格，但只留 **2 条轨**（第 1 轨 = 地名、第 2 轨 = HH:MM），
   两个时钟各占一行 ⇒ 两行的标签轨与时间轨各自共享 ⇒ **两个 HH:MM 左缘对齐**。
   轨道取 max-content：地名可长可短，轨道按最宽的那个撑开，**不截断**。 */
.topbar-clocks {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  column-gap: 0;
  row-gap: 0;
  font-size: 12px;
  line-height: 1.35;
  color: rgba(255,255,255,.9);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.topbar-clocks .tc-lbl { padding-right: .45em; }
.topbar-clocks .tc-t { display: inline-block; }
/* ⚠️ 上一版给"左右两对之间"留距离的那条 nth-child 补齐规则（`padding-right: 1.15em`）
   随 4 格布局一起删除 —— 现在一行只有一对，再留那段 padding 只会把时间列推歪、
   破坏"上下对齐"。
   （注释里不写带点的选择器、也不写那条规则的字面选择器：本文件的判据是按**原文**跑的，
   写进去会被当成"那条规则又回来了"。） */

/* M4.10: document timeline chain */
.tl-chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 4px 2px 10px;
}
.tl-node { display: inline-flex; align-items: center; gap: 5px; }
.tl-dot {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: #d8d8d8;
  border: 1px solid #b9b9b9;
  display: inline-block;
  flex: 0 0 auto;
}
.tl-dot.tl-done { background: #2e9e44; border-color: #238636; }
.tl-arrow { color: #9aa3ad; font-size: 14px; }
.tl-tag {
  display: inline-block;
  font-size: 11px;
  background: #eef3fa;
  border: 1px solid #c9d8e8;
  color: #1f4e79;
  border-radius: 3px;
  padding: 0 5px;
  margin-left: 2px;
  text-decoration: none;
}

/* M4.10: To Do single-lamp (empty state). */
.lamp-none {
  background: #fff;
  border: 1.5px dashed #b9b9b9;
}
/* 1.08.0-AlarmWords — 警示由 ⚠ 图标改成**词**（`due` = 交期前一周，`delay` = 已过承诺日）。
   颜色沿用交期那一套（与 `.due-soon` / `.due-overdue` 同一个 `--warn` / `--danger`），
   不再放大字号（从前的 16px 是给图标用的，现在要和表格正文一致）。
   ⚠️ `.alarm-link` 连同它的 `#f0a500` 一起退休 —— 打包守卫钉着不许它回来。 */
.alarm {
  display: inline-block;
  font-weight: 600;
  text-decoration: none;
}
.alarm-due   { color: var(--warn); }
.alarm-delay { color: var(--danger); }
/* M4.11: producing / delivering status badges */
.badge-pstatus-producing  { background: #e8f0fe; color: #1a56db; }
.badge-pstatus-delivering { background: #fef3c7; color: #92400e; }

/* M4.12: single-line timeline view */
.tl2 { display: flex; flex-direction: column; gap: 0; }
.tl2-node {
  position: relative;
  padding: 0 0 14px 22px;
  border-left: 2px solid #d8e2ee;
}
.tl2-node:last-child { border-left-color: transparent; }
.tl2-head { display: flex; align-items: center; gap: 8px; }
.tl2-title { font-weight: 700; font-size: 13px; color: var(--ink); }
.tl2-body { margin: 3px 0 0 19px; font-size: 13px; color: var(--ink-soft); }
.tl2-files { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 4px; }
.tl2 .tl-dot {
  position: absolute;
  left: -6px;
  top: 2px;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: #d8d8d8;
  border: 1px solid #b9b9b9;
}
.tl2 .tl-dot.tl-done { background: #2e9e44; border-color: #238636; }

/* M4.13: To Do column sort links */
.sort-link { color: var(--ink); text-decoration: none; font-weight: 700; }
.sort-link:hover { color: var(--brand); }
.sort-active { color: var(--brand); }
.sort-arrow { font-size: 11px; }

/* M4.14: snapshot doc frame + print body */
.doc-frame { background:#fff; padding:20px; border:1px solid #d8d8d8; border-radius:6px; margin-top:16px; }
.doc-body { background:#f2f3f5; }
.doc-body .doc { background:#fff; width:210mm; min-height:297mm; margin:12px auto; padding:10mm; box-shadow:0 2px 10px rgba(0,0,0,.08); box-sizing:border-box; }
@media print { .doc-body .doc { margin:0; box-shadow:none; padding:0; } }

/* M4.16: Order Confirmation PI layout */
.pi-artno { color:#666; font-size:11px; margin-top:2px; }
.doc-topinfo { width:100%; border-collapse:collapse; margin:10px 0 12px; }
.doc-topinfo td { vertical-align:top; }
.doc-buyer { width:52%; padding-right:14px; }
.doc-infobox { width:48%; border:1px solid #bbb; }
/* 0.82.0-PlFix — `.doc-rcol` = `doc_top_grid()` 给右栏加的**外层**。
   `.doc-topinfo` 是 flex 一行 → 直接子元素各算一个 flex item；0.80 把 PL 的包装块
   当作 `.doc-infobox` 的兄弟塞进来，它就成了第 4 个 item，于是"放在联系框下面"
   变成了"摆在联系框右边"（Wolfgang 2026-09-26 截图）。右栏只能有一个子元素。
   尺寸照抄原来的 `.doc-infobox`，所以只放一个联系框的那五份文档**无变化**。 */
.doc-rcol { width:48%; }
.doc-rcol > .doc-infobox { width:100%; }
.doc-infobox table { width:100%; border-collapse:collapse; }
.doc-infobox td { padding:3px 8px; font-size:10pt; border-bottom:1px solid #eee; }  /* 0.41.0 (#1) — was 11px; the panel is uniformly 10pt now */
.doc-infobox tr:last-child td { border-bottom:none; }
.doc-taxnote { margin:10px 0; padding:6px 10px; background:#f6f7f9; border-left:3px solid #0432FF; font-size:11px; line-height:1.5; }
.doc-term { margin:10px 0; font-size:11px; line-height:1.7; }
.doc-term-label { font-weight:700; }
.doc-table .pi-item td { padding:4px 6px; }

/* DocStd1.3: PI (pi_render_html) and PO share this 8-column table —
   Line / Model&Description×3 / Qty / Unit / Unit price / Total(Amount).
   Fixed layout + widths keep the numeric columns stable when the
   description column holds long text (never squeezed / never over-wide).
   nth-child counts ELEMENTS: 1=Line, 2=Model&Desc (colspan 3), 3=Qty,
   4=Unit, 5=Unit price, 6=Total. Widths total ~53%; the description
   columns (2-4) share the remaining width. */
.doc-table { width: 100%; table-layout: fixed; }
.doc-table thead th:nth-child(1) { width: 5%; }
.doc-table thead th:nth-child(3) { width: 8%; text-align: right !important; }
.doc-table thead th:nth-child(4) { width: 9%; text-align: center !important; }
.doc-table thead th:nth-child(5) { width: 15%; text-align: right !important; }
.doc-table thead th:nth-child(6) { width: 16%; text-align: right !important; }
.doc-table td { overflow-wrap: break-word; }

/* DocStd 批2 (#103): SINGLE workbench shell — .doc-work / .doc-work-panel
   (+ .doc-work-preview / -bar on the right) is shared by every document
   node (Quotation, PI, Order, PO). The old PI-only .doc-workbench /
   .doc-workbench-left classes are gone; form-frames inside a
   .doc-work-panel are flattened into one continuous card with divider
   lines so all four workbenches look the same. */
.doc-work-panel .form-frame {
  background: transparent;
  box-shadow: none;
  border: none;
  border-radius: 0;
  padding: 14px 0 2px;
  margin-bottom: 0;
}
.doc-work-panel .form-frame + .form-frame {
  border-top: 1px solid var(--line);
  margin-top: 6px;
  padding-top: 20px;
}
.doc-bank { margin:12px 0; font-size:11px; line-height:1.6; padding:8px 12px; border:1px solid #ddd; background:#fafbfc; }
.doc-bank .doc-term-label { display:block; margin-bottom:4px; }
/* 1.54.0-BankBlock（Wolfgang 2026-10-12）—— 银行信息在 A4 上**独立成块**（不再混进 Terms
   那三行）：它自己就是一张 kv 表，排在 Terms 区块**之后**。只加一条上边距把两块分开 ——
   行内形状沿用同一套 `.doc-term-table` ⇒ 与从前那一行**逐字同形**，只是位置独立。
   ⚠️ **有意不套用上面那个 `.doc-bank` 盒子样式**（0.48 的旧 helper 留下的死样式）：
      OC / PF 要的是"和 Terms 一眼看出同族、但独立成块"，不是一个带边框的灰盒子。 */
.doc-bank-block { margin-top: 10px; }

/* M4.16.3: PI layout refinements */
.doc-cust-name { font-weight:700; font-size:12px; margin-bottom:3px; }
.doc-seller { margin:8px 0 12px; padding-top:8px; border-top:1px solid #ccc; }
.doc-seller .doc-info-value { font-size:10pt; line-height:1.5; }   /* 0.41.0 (#1) — was 11px */
.doc-table thead th { background:#e6e6e6; border-bottom:2px solid #999; }
.doc-table tbody tr { border-top:1px solid #ccc; }
.doc-table tbody tr:first-child { border-top:none; }
.doc-infobox td { border-bottom:1px solid #e5e5e5; }
.doc-infobox tr:last-child td { border-bottom:none; }
.doc-attn-gap { height:8px; }

/* M4.16.6: shipment label compact; foot-label hugs the Total column */
.doc-shipment-label { font-weight:700; font-size:11px; margin-top:8px; }
.doc-foot-right { text-align:right; padding-right:8px; }
.btn-del { color:#c0392b; border:1px solid #c0392b; background:transparent; }
.btn-del:hover { background:#c0392b; color:#fff; }

/* M4.16.11: PI — quotation-style items, bold lines */
.pi-model { font-weight:700; }
.pi-desc { margin-top:2px; }
.doc-attn-strong { font-weight:700; }

/* M4.16.11: PI — quotation-style items, bold lines */
.pi-model { font-weight:700; }
.pi-desc { margin-top:2px; }
.doc-attn-strong { font-weight:700; }

/* M4.17: To Do + CSV import */
.todo-done td { color:#999; text-decoration: line-through; }
.import-dupe td { background:#fff5f0; }
.stat-due { color:#c0392b; }
.doc-cust-first { font-weight:700; }

/* M4.20: PI workbench — compact Items editor (more columns visible) */
#pi-items { font-size:12px; }
#pi-items td, #pi-items th { padding:4px 5px; font-size:12px; }
#pi-items input, #pi-items select { padding:4px 6px; font-size:12px; border-radius:5px; }
#pi-items input[type="number"] { text-align:right; }
#pi-items .btn { padding:2px 8px; font-size:12px; }
#pi-items .amt { white-space:nowrap; }

/* ============================================================
 * DocStd-批1 (KBP 0.21.0-DocStd1): Paged.js-driven pagination
 * Replaces assets/doc-pages.js (M4.1 hand-coded 276.5mm page break
 * + per-page fixed header/footer). Paged.js (CSS Paged Media
 * polyfill 0.4.3) handles column balancing, widow/orphan control,
 * table row splitting — much more robust on long item tables.
 * Loaded by assets/doc-paged.js which sets PagedConfig and clones
 * the .doc-head / .doc-footer into every .pagedjs_page (header
 * logo + page number / footer 3-col company info).
 * ============================================================ */

/* DocStd1.3 (0.21.3): this MUST stay `margin: 0`. CSS applies the LAST
   matching @page rule (source order, not @media specificity) — a later
   `@page { margin: 26mm 10mm 20mm 10mm }` here silently overrode the
   @media print margin:0 in 0.21.2 and made Chrome print each 297mm
   Paged.js sheet into TWO physical A4 pages (PDF "Page 1 of 2" but 4
   pages, pages 2/4 blank). All document margins are provided by
   .pagedjs_page_content padding (26mm/10mm/20mm/10mm) instead. */
@page { size: A4; margin: 0; }

/* screen: card view of the rendered pages */
body.doc-print-body { background: #e9eaed; }
body.doc-print-body .paged-host { padding: 0; }
.pagedjs_pages { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.pagedjs_page { box-shadow: 0 2px 16px rgba(16,24,40,.18); background: #fff;
                overflow: hidden; position: relative; }

/* Paged.js wraps each page's flowing content in .pagedjs_page_content;
   inside that, .doc-flow is the container that doc-paged.js built from
   the original .doc children.  Kill the .doc flex / min-height / shadow
   so the content sits cleanly inside the pagebox grid. */
.pagedjs_page_content .doc-flow { display: block !important; width: auto;
                min-height: 0; padding: 0; margin: 0; background: transparent;
                box-shadow: none; color: #111; font-size: 10pt; line-height: 1.45; }

/* DocStd1.3 (KBP 0.21.3-DocStd1.3): with @page margin now 0 (see print rules),
   .pagedjs_page_content padding is the ONLY source of document margins.
   Paged.js 0.4.3 IGNORES `@page margin` when PagedConfig.content is set to a
   custom flow element (CDP-verified: .pagedjs_page_content fills the full
   794×1123 A4 page). The 26mm/10mm/20mm/10mm box reserves the top/bottom
   zones for the absolute .kbp-head (top:5mm) / .kbp-foot (bottom:5mm) and
   the left/right 10mm gutters, so .doc-flow content never collides with the
   per-page chrome. Keeping this padding on .pagedjs_page_content (NOT on
   .doc-flow) lets Paged.js account for it while splitting content across
   sheets — each sheet stays exactly one 297mm physical page. */
.pagedjs_page_content { padding: 26mm 10mm 20mm 10mm; box-sizing: border-box; }

/* DocStd1.2 (KBP 0.21.2-DocStd1.2): page-chrome sits in the @page margin
   zones — kbp-head at top:5mm, kbp-foot at bottom:5mm — measured from the
   pagebox edge.  Paged.js positions absolute elements within the pagebox
   CONTENT (the 26mm-10mm-20mm-10mm box), so absolute top:5mm puts the
   header 5mm down from the pagebox top, in the reserved top margin zone
   (26mm).  DocStd1.1 had this exact CSS — the "messy header" bug came
   from a different cause (see KBP_改进计划 DocStd1.2 entry). */

/* per-page header / footer clones — absolutely positioned in the
   @page margin zones.  Original .doc-head / .doc-footer are removed from
   the flowing content; their templates are cloned per page in after(). */
.kbp-head { position: absolute; left: 10mm; right: 10mm; top: 5mm; z-index: 5;
            display: flex; justify-content: space-between; align-items: flex-start;
            gap: 8mm; padding-bottom: 2mm; border-bottom: 0.5pt solid #111; }
.kbp-head .doc-logo-img { max-height: 13mm; max-width: 58mm; width: auto; height: auto; }
.kbp-head .doc-head-right { display: flex; align-items: flex-start; gap: 6mm; }
.kbp-head .doc-head-meta { font-size: 9.5pt; line-height: 1.55; white-space: nowrap; text-align: left; }
.kbp-head .doc-head-meta .doc-label { font-weight: 700; }
/* DocStd1.2: Page X of Y sits beside Date on the SAME line (compact right column). */
.kbp-head .doc-head-meta .doc-page-num { display: inline-block; margin-left: 4mm;
                font-size: 9pt; font-weight: 400; color: #444; }
.kbp-head .doc-head-meta .doc-page-num::before { content: " | "; color: #999; }
.kbp-foot { position: absolute; left: 10mm; right: 10mm; bottom: 10mm; z-index: 5;   /* L1-v4 */
            display: flex; gap: 6mm; font-size: 6.5pt; line-height: 1.02; color: #999;
            border-top: 1px solid #9a9a9a;   /* L1-v4: solid footer divider */
            padding-top: 2mm; border-top: 0.5pt solid #d0d0d0; }
/* 0.66.0-ListUI — 同上的 pre-line 移除（页脚的断行只由 nl2br() 提供）。 */
.kbp-foot .doc-footer-col { flex: 1 1 0; min-width: 0; }

/* print: force @page margin 0 so the .pagedjs_page block fills the full
   A4 sheet and prints as exactly one physical page per .pagedjs_page.
   Paged.js's own print CSS adds .pagedjs_page { height:100%!important;
   break-after:page } — together this guarantees 1 PDF page per card. */
@media print {
  @page { margin: 0; }
  body { background: #fff !important; }
  .paged-host { padding: 0; }
  .pagedjs_pages { display: block; gap: 0; }
  .pagedjs_page { box-shadow: none; margin: 0; }
}


/* =====================================================================
   0.26.0-Dashboard — dark theme, floating Memo drawer, Preference page.
   ===================================================================== */

/* ---------- dark theme: variable override + surface tweaks ---------- */
html[data-theme="dark"] {
  color-scheme: dark;
  --ink: #e7eaf1;
  --ink-soft: #96a1b1;
  --line: #2b3342;
  --paper: #10141b;
  --card: #1a202b;
  --brand: #66a0d6;
  --brand-dark: #7fb0de;
  --brand-tint: #213349;
  --ok: #5cb489;
  --danger: #e2706a;
  --danger-tint: #3b2422;
  --warn: #e0a355;
}
html[data-theme="dark"] a { color: #7fb2de; }
html[data-theme="dark"] .card { box-shadow: 0 2px 12px rgba(0, 0, 0, .35); }
html[data-theme="dark"] .table tbody tr:hover,
html[data-theme="dark"] .kv tr:hover { background: rgba(255, 255, 255, .045); }
html[data-theme="dark"] input[type="text"],
html[data-theme="dark"] input[type="password"],
html[data-theme="dark"] input[type="email"],
html[data-theme="dark"] input[type="date"],
html[data-theme="dark"] input[type="number"],
html[data-theme="dark"] select,
html[data-theme="dark"] textarea {
  background: var(--card);
  color: var(--ink);
}
html[data-theme="dark"] .doc-work-panel,
html[data-theme="dark"] .memo-add input[type="text"],
html[data-theme="dark"] .memo-add input[type="date"] { background: var(--card); }
html[data-theme="dark"] .filter-bar { background: var(--card); }
html[data-theme="dark"] .empty-cell,
html[data-theme="dark"] .muted { color: var(--ink-soft); }

/* app-name in the top bar becomes a link to https://kbp.kuemago.com */
.topnav-title a.app-name,
.topnav-title a.app-name:hover {
  color: #fff;
  text-decoration: none;
}

/* ---------- Preference page rows ---------- */
.pref-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 2px;
  border-bottom: 1px solid var(--line);
}
.pref-row:last-child { border-bottom: none; }
.pref-row .pref-label { flex: 1; font-size: 14px; }
.pref-row .pref-label small { display: block; color: var(--ink-soft); font-size: 12px; }
.pref-row .btn-mini {
  padding: 3px 11px;
  font-size: 13px;
  line-height: 1.3;
}
.theme-opts { display: flex; gap: 18px; margin: 10px 0 4px; flex-wrap: wrap; }
.theme-opts label { display: inline-flex; gap: 6px; align-items: center; font-size: 14px; }

/* ---------- floating Memo drawer (global, every logged-in page) ---------- */
.memo-shade {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 18, .42);
  z-index: 1400;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease;
}
.memo-shade.open { opacity: 1; pointer-events: auto; }

.memo-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 350px;
  max-width: 94vw;
  background: var(--card);
  border-left: 1px solid var(--line);
  box-shadow: -6px 0 26px rgba(0, 0, 0, .22);
  z-index: 1500;
  transform: translateX(105%);
  transition: transform .22s ease;
  display: flex;
  flex-direction: column;
}
.memo-panel.open { transform: none; }
.memo-panel .memo-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.memo-panel .memo-head h3 { margin: 0; font-size: 15px; }
.memo-panel .memo-head .memo-open-count {
  font-size: 11px;
  font-weight: 700;
  background: var(--danger-tint);
  color: var(--danger);
  border-radius: 999px;
  padding: 1px 8px;
}
.memo-panel .memo-close {
  margin-left: auto;
  border: none;
  background: transparent;
  color: var(--ink-soft);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
}
.memo-panel .memo-close:hover { color: var(--danger); }
.memo-add {
  display: flex;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  align-items: center;
  flex-wrap: wrap;
}
.memo-add input[type="text"] { flex: 1 1 160px; min-width: 0; padding: 7px 9px; font-size: 13.5px; }
.memo-add input[type="date"] { width: 138px; padding: 6px 8px; font-size: 13px; }
.memo-add .btn { padding: 8px 14px; font-size: 13.5px; }
.memo-list {
  flex: 1;
  overflow-y: auto;
  padding: 6px 12px;
}
.memo-item {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 9px 2px;
  border-bottom: 1px dashed var(--line);
  font-size: 13.5px;
}
.memo-item .memo-check {
  flex: 0 0 auto;
  margin-top: 1px;
  width: 19px;
  height: 19px;
  border: 1.6px solid var(--ink-soft);
  border-radius: 5px;
  background: transparent;
  cursor: pointer;
  color: transparent;
  font-size: 12px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.memo-item .memo-check:hover { border-color: var(--ok); color: var(--ok); }
.memo-item .memo-body { flex: 1; min-width: 0; }
.memo-item .memo-title { color: var(--ink); word-break: break-word; }
.memo-item .memo-due { display: block; font-size: 11.5px; color: var(--ink-soft); margin-top: 2px; }
.memo-item.overdue .memo-title { color: var(--danger); font-weight: 600; }  /* overdue = red-ish text */
.memo-item.overdue .memo-due { color: var(--danger); }
.memo-empty { color: var(--ink-soft); font-size: 13px; padding: 18px 4px; text-align: center; }
.memo-foot {
  padding: 9px 16px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.memo-tab {
  position: fixed;
  right: 0;
  top: 46%;
  transform: translateY(-50%);
  z-index: 1300;
  writing-mode: vertical-rl;
  background: var(--brand);
  color: #fff;
  border: none;
  border-radius: 10px 0 0 10px;
  padding: 16px 8px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .6px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, .22);
}
.memo-tab:hover { background: var(--brand-dark); }
.memo-tab .memo-tab-n {
  display: inline-block;
  margin-bottom: 6px;
  background: rgba(255, 255, 255, .22);
  border-radius: 999px;
  font-size: 11px;
  padding: 0 6px;
  font-weight: 800;
}

/* =====================================================================
   0.27.0-DocFmt — self-produced document layout alignment (all docs:
   Quotation / PI / Order Confirmation / Invoice / PL / Purchase Order).
   Header field labels & item text are no longer bold; item column headers
   centre with "Item" first; Model/Description left, Qty/Unit/Line centre,
   Unit price/Total right; Page X of Y right-aligned without the " | ".
   ===================================================================== */

/* 1.2 — right-corner / header field labels not bold (section titles like
        BUYER CONTACT keep .doc-info-section bold for grouping). */
.doc-head-label { font-weight: 400; }
.doc-info-label { font-weight: 400; }

/* 1.1 — Attn / Tel / Email all regular weight in the buyer block. */
.doc-attn-strong { font-weight: 400; }

/* 3 — item content never bold (Model rows, product names). */
.pi-model { font-weight: 400; }
.doc-model-line { font-weight: 400; }
.doc-item-name { font-weight: 400; }
.doc-code { font-weight: 400; }

/* 2 + 4 — .doc-items (Quotation / Order family) header centred, content
        aligned: Line centre · Model&Desc left · Qty centre · Unit centre
        · Unit price right · Amount right. */
.doc-items thead th:nth-child(n) { text-align: center !important; }
.doc-item-no { text-align: center; }
.doc-qty { text-align: center; }
.doc-unit { text-align: center; }
.doc-price { text-align: right; }
.doc-amount { text-align: right; }

/* 2 + 4 — .doc-table (PI / PO family, 6 visual columns: Line · Model&Desc
        · Qty · Unit · Unit price · Amount/Total). */
.doc-table thead th:nth-child(n) { text-align: center !important; }
/* 🔴 1.73.0-DocTypeScale（批 3／3）—— **`Model & Description` 表头左对齐**，六张一致。
   1.58.0 时只作用报价单（`.doc-quotation-table thead th:nth-child(2)`），本批提升为通用
   （任务书 §4-2）。依据：数据（第 2 列）本来就是左对齐，错位的是**表头**
   （上面那条把所有表头居中）⇒ 两条**特异性相同**（都是 (0,2,2)）且都 `!important`
   ⇒ **必须紧挨着写在它后面**（同特异时书写顺序后者胜）。
   ⚠️ **改动本行的位置 = 静默失效**：判据里有一条专门钉"它排在 `.doc-table thead th:nth-child(n)`
      之后"（`m143` 与 `guard_docfontscale`），搬走它会让守卫红 —— 这是**故意**的绊线。
   ⚠️ 六张纸的第 2 列都叫 `Model & Description`（`doc_items_table_open()` 与 CI/PL 的表头
      都是「Item + Model & Description + …」）⇒ `nth-child(2)` 在六张上都指同一列。 */
.doc-table thead th:nth-child(2) { text-align: left !important; }
.doc-table thead th { padding: 4px 6px; }
.doc-table tbody td:nth-child(1) { text-align: center; }
.doc-table tbody td:nth-child(2) { text-align: left; }
.doc-table tbody td:nth-child(3) { text-align: center; }
.doc-table tbody td:nth-child(4) { text-align: center; }
.doc-table tbody td:nth-child(5) { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.doc-table tbody td:nth-child(6) { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* 1.3 — Page X of Y right-aligned, no " | " prefix (printed header). */
.kbp-head .doc-head-meta .doc-page-num {
  display: block;
  margin-left: 0;
  text-align: right;
}
.kbp-head .doc-head-meta .doc-page-num::before { content: none; }
.doc-table tfoot td:last-child { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* 0.28.0 (#146) — Memo drawer input: note on its own line, then a row with
   the optional date and the Add button. */
.memo-add { flex-direction: column; align-items: stretch; }
.memo-add input[type="text"] { flex: 1 1 auto; width: 100%; }
.memo-add-row { display: flex; gap: 8px; align-items: center; }
.memo-add-row input[type="date"] { flex: 1 1 auto; }

/* 0.28.0 (#145) — items table column widths tuned for the available 190mm
   print area. Item gets the bare 4 % it needs; Qty / Unit shrink so Model &
   Description (and Item Name on Quotation/Order docs) gains 6 % and stops
   wrapping short descriptions like "Pressure sensor 100 / 0-10 bar". */
.doc-table thead th:nth-child(1),
.doc-items thead th.doc-th-item { width: 4%; }
.doc-items thead th.doc-th-desc { width: 53%; }
.doc-table thead th:nth-child(2) { width: 53%; }
.doc-table thead th:nth-child(3) { width: 7%; }
.doc-table thead th:nth-child(4) { width: 7%; }
.doc-table thead th:nth-child(5) { width: 14%; }
.doc-table thead th:nth-child(6) { width: 15%; }
.doc-items thead th.doc-th-qty { width: 8%; }
.doc-items thead th.doc-th-unit { width: 7%; }
.doc-items thead th.doc-th-price { width: 14%; }
.doc-items thead th.doc-th-amount { width: 15%; }

/* 0.33.0 (#3) — Partner list "Completeness" column colour coding. */
.complete-ok   { color: #1d7a3a; font-weight: 700; }
.complete-warn { color: #b45309; font-weight: 700; }
.complete-bad  { color: #b91c1c; font-weight: 700; }
td[data-col="complete"] { white-space: nowrap; }

/* 0.35.1 — a draft document marks its TITLE line red and appends
   "DRAFT — NOT ISSUED" right after the document name; the header itself
   stays untouched so issuing never shifts the layout. */
.doc-title-draft { color: #b91c1c !important; }
.doc-draft-tag {
  color: #b91c1c;
  font-size: 0.62em;
  font-weight: 800;
  letter-spacing: 3px;
  margin-left: 10px;
  white-space: nowrap;
  vertical-align: 0.35em;
}

/* 0.34.0-182 (final) — header back to the 0.30.0 baseline: logo (5 cm)
   on the left and the QT/Rev/Date/Page meta column on the right, on one
   row, above the divider. The company identity line was removed entirely
   (user decision 2026-09-08 — several layout attempts did not converge). */
.doc-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8mm;
  padding-bottom: 5px;
  border-bottom: 1px solid #111;
  width: 100%;               /* 0.35.2 — keep full .doc width even on the screen fallback */
  box-sizing: border-box;   /* padding/border stay inside so the divider spans full width */
}

/* 0.34.0-179 — keep the contact box pushed to the right edge (kept from
   the previous #177 attempt; layout mode is the 0.30.0 flex row but the
   .doc-top row is still flex space-between + .doc-infobox margin-left:auto
   so BUYER/SELLER CONTACT is on the right side of the A4 page). */
.doc-top,
.doc-topinfo {
  display: flex !important;
  justify-content: space-between !important;
  align-items: flex-start !important;
  gap: 8mm;
}
.doc-top > .doc-customer,
.doc-top > .doc-buyer,
.doc-topinfo > .doc-buyer { flex: 0 1 auto; max-width: 55%; }
.doc-top > .doc-infobox,
.doc-topinfo > .doc-infobox,
.doc-top > .doc-rcol,
.doc-topinfo > .doc-rcol { margin-left: auto; }

/* 0.34.1 — PI/PO item note line (grey, under the description) */
.pi-note { color: #666; font-size: 8pt; margin-top: 1px; }

/* 0.35.2 — Preference arrows live in their own tiny forms (inline). */
.pref-arrows { display: inline-flex; gap: 4px; margin-left: 8px; }
.pref-move { display: inline-flex; margin: 0; padding: 0; }

/* 0.35.3 — Quotation A4 preview whitespace fix (compare with PI/PO which
   rendered fine) — pull the paged page flush to the top of the iframe
   and give the iframe enough vertical room for one full A4 sheet. */
.doc-work-preview iframe { min-height: 1180px; }
body.doc-print-body .paged-host { padding-top: 0 !important; }
.pagedjs_pages { margin-top: 0 !important; padding-top: 0 !important; }
.pagedjs_page { margin-top: 0 !important; }
.pagedjs_page_content { padding-top: 16mm !important; }
.kbp-head { top: 3mm !important; }

/* 0.36 L1-v2 — page content bottom white-space keeps body text off the
   10mm-off footer (.kbp-foot bottom edge at 10mm). */
.pagedjs_page_content { padding-bottom: 24mm !important; }   /* L2: content stops 10mm above the footer divider */


/* 0.36 L1-v3 — footer columns: first line bold (company block emphasis). */
.doc-footer-col::first-line,
.kbp-foot .doc-footer-col::first-line { font-weight: 700; }

/* =====================================================================
 * 0.36.0-DocFrame — KBP 统一文档框架全局规则（KBP_文档生成格式规范 v2.0）
 * LAYER 1 chrome + LAYER 2 content 生产落地；覆盖早期各 doc 自行定义。
 * ===================================================================== */
.doc-head {
  padding-bottom: 5px;
  border-bottom: 1px solid #111;
}
.doc-head-top {
  display: grid;
  grid-template-columns: auto minmax(50mm, 1fr) auto;   /* logo · 公司 3 行(min 50mm) · meta 4 行 */
  column-gap: 8mm;
  align-items: start;
  width: 100%;
}
.doc-head-co { justify-self: start; text-align: left; font-size: 9.5pt; line-height: 1.55; min-width: 50mm; }
.doc-head-co > div { white-space: nowrap; }            /* 公司各行不换词 */
.doc-head-co > div:first-child { font-weight: 700; }   /* 首行加粗 */
.doc-head-meta { font-size: 9.5pt; line-height: 1.55; white-space: nowrap; }
.doc-head-meta .doc-page-num { text-align: right; }    /* Page 第 4 行右对齐，与 No/Rev/Date 同栈 */

.doc-title { padding-top: 10mm; padding-bottom: 5mm; margin: 0; }   /* L2 上止点：页眉线下 10mm（用 padding 避开 Paged.js flow 内 margin collapse）*/

.doc-topinfo {
  display: grid;
  grid-template-columns: 60% 40%;          /* L2 v2.1 (2026-09-09): 60/40 split */
  column-gap: 8mm;
  align-items: start;
}
.doc-topinfo .doc-buyer, .doc-topinfo .doc-infobox, .doc-topinfo .doc-rcol { text-align: left; min-width: 0; }
.doc-topinfo .lay2-reg-co { font-weight: 700; font-size: 10.5pt; line-height: 1.4; }
.doc-topinfo .lay2-vspace { height: 4.5mm; }   /* L2 v2: blank line between address blocks */
/* L2 v2.2 — 左栏项缺失时不渲染（doc_join_blocks 过滤），非空项之间以 .lay2-vspace 分隔；无固定槽占位 */
.doc-topinfo .lay2-sec-label { font-size: 10pt; font-weight: 700; margin-bottom: 0.6mm; }
.doc-topinfo .lay2-addr { font-size: 9.5pt; line-height: 1.4; }
.doc-topinfo .lay2-attn { font-size: 9.5pt; line-height: 1.35; }

/* Item 表格（6 th：Item / Model&Desc(col3) / Qty / Unit / Unit Price / Total） */
/* 🔴 1.09.0-QtyProp —— **这一块才是真正生效的那一代**。`.doc-table` 的列宽在文件里有
   **三代**（≈L2029 / ≈L2453 / **这里**），同特异性 ⇒ **后写赢**。
   改列宽必须改这里；改前面那两代等于没改（本批第一版就改错了，
   是探针量出"Qty 列只从 49.47 涨到 49.92px"才发现的）。

   实测（`_probe_qty.php`：真标记 + 真样式 + 真 A4 容器，2026-10-02）：
     · Qty 列 **49.47px**，左右 padding 各 5px ⇒ **可用内宽只有 37.47px**；
     · `10000` 需要 **33.38px**（只剩 4px 余量）· 六位数 40.05px **当场折行** ——
       这正是 Wolfgang 报的 *"数量为10000时，由于列宽不足会被分为了两行显示"*。
   ⇒ Qty 8% → **10%**，从 **Item 列**借 3%（它只印 1–2 位序号，9% 用不完）
   ⇒ Description 反而**净增 1%**（0.28 那条"别让描述折行"的承诺不受影响）。 */
.doc-table thead th { font-size: 10pt; }             /* L2 v2.4: header same size as body (Wolfgang 2026-09-11) */
.doc-table thead th:nth-child(1) { width: 6%; font-weight: 700; }
.doc-table tbody td:nth-child(1) { font-weight: 700; }    /* L2 v2.3: Item 列加粗 */
.doc-table thead th:nth-child(3) { width: 10%; text-align: center !important; }
/* 数字格**禁止断行**：一个数量被拆成 "100" / "00" 两行是纯错误，不是排版取舍。
   （`.cell-qty` / `.cell-num` 全站**零规则** = 0.89 那个"影子实现"，所以从前没有 nowrap 兜着，
   而 `.doc-table td` 又带 `overflow-wrap: break-word` ⇒ 到宽度边缘就断。） */
.doc-table tbody td.cell-num,
.doc-table tbody td.cell-qty { white-space: nowrap; }
.doc-table thead th:nth-child(4) { width: 7%; text-align: center !important; }
.doc-table thead th:nth-child(5) { width: 16%; text-align: right !important; }
.doc-table thead th:nth-child(6) { width: 17%; text-align: right !important; }
.doc-table td { overflow-wrap: break-word; font-size: 10pt; padding: 3px 5px; }   /* L2 v2.1: item text 10pt */
.doc-table tbody tr { border-top: none; }                       /* 行间无细线 */
.doc-table tfoot tr:first-child td { border-top: 1.5px solid #8a8a8a; }   /* Amount 上方分隔线 */
.doc-table tfoot td { border-top: 0.5px solid #ddd; padding: 3px 5px; }
.doc-table .cell-num { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* 表格下区块
   1.05.0-TermsFrame（Wolfgang 2026-09-29）—— *"整个Terms与上方控件或字符最多保持
   一行间距"*（定案：**全部单据一起改**）。
   实测（`_measure_terms.py`，真渲染 + headless Chrome）：改前 `.doc-term` 的
   `margin-top` = **12mm = 45.35px**，而它上面那行文字（`.doc-tablenote`，9pt /
   line-height 1.4）的行高只有 **16.8px** ⇒ 空了约 **3.3 行**。
   改后 `margin-top` 走 **`--doc-blank-line`（4mm ≈ 15.1px）** —— 与 greeting 下方
   那个"一个空行"是**同一个量**，不新造数字；`padding-top` 归零（2.5mm 当年只是给
   下面那条 `border-top` 让呼吸位，线已经没了）。
   ⚠️ `border-top` 一并从这条规则里拿掉（原来写死 0.8pt #b9b9b9）。0.57 把它挪到首行
   格子上、0.58 又补了一条 `border-top: none !important` 兜底 —— 现在 1.05 连首行
   那条也去掉（"一起去掉"），兜底那条**保留**（见 0.57 / 0.58 两个权威块）。 */
.doc-term { margin-top: var(--doc-blank-line); padding-top: 0; }
.doc-term, .doc-notes { text-align: left; }
/* 🔴 1.112.0-PageFit（Wolfgang 2026-11-02）—— 底部注脚的下边距**不再参与分页高度**。

   现场：QT-26NO01081 Ver.1 只有 6 行明细，第 1 页却在 Terms 之后结束，
   把底部那 4 条 `* …` 注脚整块挤到第 2 页（尾页 90% 空白）。
   量出来的账（确定性探针 `_measure_q127.html`，不引 paged.js）：
     · `.doc-notes` 本体高 **21.2mm**，而它带着 `margin-bottom: 10mm`
       ⇒ paged.js 算的是 **31.2mm** —— 比实际高出整整 10mm；
     · 一页正文只有 226mm（297 − 40 上 − 31 下），第 1 页当时只剩约 9mm
       ⇒ 21.2 已经装不下，再被那 10mm 虚构的高度一顶，**必跳页**。

   这 10mm 原本是"内容到底时别贴着页脚"的呼吸位。但让开页脚这件事
   **`padding-bottom: 31mm` 已经做完了**（内容盒下沿 266mm，页脚顶 268mm），
   这 10mm 是在那之上**再加一层**，属于重复计算 ⇒ 去掉它。

   ⚠️ **代价**：内容刚好填满一页时，最后一块的下沿会到内容盒下沿（距页脚 2mm），
      比从前紧 10mm。绝大多数单据内容不到底，看不出差别。
   ⚠️ `.doc-term:last-of-type` 那 10mm **保留** —— 它服务的是"没有注脚"的单据
      （那时 Terms 就是最后一块，删了会真的贴住页脚）。
   ⚠️ 别再把它加回来：这是**分页承诺**的一部分，`guard_pagefit` 钉着。 */
.doc-term:last-of-type { margin-bottom: 10mm; }    /* L2 下止点（无注脚的单据） */
.doc-notes { margin-bottom: 0; }                  /* 1.112.0-PageFit：让出 10mm 给"能否放下" */
.doc-item-note { font-size: 8.5pt; color: #666; }
.doc-line { display: block; }

/* 0.36.1 — L1/L2 物理间距兜底（Wolfgang 2026-09-09 19:55 反馈：
   logo 上 10mm / 页脚 10mm / 标题距分隔线 10mm 必须落实）。
   覆盖早期 .doc/.doc-title/.kbp-head/.kbp-foot/.pagedjs_page_content 的
   历史定义（print + workbench preview 两条路径同时生效）。 */
.doc { padding: 10mm 10mm 10mm !important; }                                    /* L1: logo 上 10mm（直出路径） */
.kbp-head { top: 10mm !important; }                                              /* L1: print 路径 logo 上 10mm */
.kbp-foot { bottom: 10mm !important; }                                           /* L1: print 路径页脚底 10mm */
.pagedjs_page_content { padding-top: 22mm !important; padding-bottom: 22mm !important; }   /* L2: 内容距页眉/页脚分隔线 10mm */
.doc-title { padding-top: 10mm !important; margin-top: 0 !important; }   /* L2: 标题距分隔线 10mm（用 padding 避开 Paged.js margin collapse）*/

/* 0.36.4 — L1 页脚物理边距加固：Paged.js 0.4.3 在 doc-paged.js 之后会注入默认
   @page margin（覆盖 style.css 中不带 !important 的版本），导致 .kbp-foot
   bottom:10mm 距 A4 物理底 ≈0mm。强制 @page margin 0 !important 锁死 pagebox
   = A4 物理尺寸，footer 10mm 才能真正生效。 */
@page { size: A4; margin: 0 !important; }

/* ============================================================
 * 0.39.0-DocUnify (#8) — date fields carry their ISO week (KW)
 * ============================================================
 * The native <input type="date"> calendar has no week column, so the week is
 * printed NEXT to the field (assets/date-week.js keeps it in sync) and week
 * values such as "KW 20-21" use week_input()'s free text + datalist instead of
 * pretending to be a date.
 */
.date-kw-field { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 0.88.0-PoReceive (Wolfgang 2026-09-27) —— *"日期框下行加注参考周"*。
   默认那一行（上面）把 KW 摆在字段**旁边**；这一条把提示降到**下一行**。
   只在 date_input(..., ['kw_below' => true]) 的地方生效 —— 见 lib/date-input.php。
   `align-items: flex-start` 让提示与字段**左缘对齐**（不跟着字段宽度居中）。

   ⚠️ 这里有两个必须一起成立的细节（本批第一版漏了第一条，量出来才发现）：
   ① **提示必须换行**（`white-space: normal`）。列向 flex 容器的宽度 = **最宽的那个孩子**
      —— `.kw-hint` 默认 `nowrap`，"KW 39/2026 · 21.09.–27.09.2026" 有 200px 宽，
      于是容器被撑到 200px，按钮反而离日期框更远（实测空隙 135px，正是他要消灭的那种）。
   ② 换行需要**一个上限**，就是字段自己的宽度 —— 所以宽度只留**一个**数字
      （`--kw-field-w`），字段与提示都从它取值。 */
.date-kw-field.date-kw-below {
  --kw-field-w: 150px;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.date-kw-field.date-kw-below > input { width: var(--kw-field-w); }
.date-kw-field.date-kw-below > .kw-hint { max-width: var(--kw-field-w); white-space: normal; }
/* --- 0.95.0-KwHint：提示改成**两行**（Wolfgang 2026-09-28）------------------------
   *"把所有涉及有周提示的日期框，下面的提示改为两行，第一行显示形如KW 40/2026，
     第二显示形如28.09-04.10.2026"*
   两行是**结构**（两个块级孩子），不靠 `white-space` 折行 —— 0.88 那版靠折行，
   结果"列向容器的宽度 = 最宽的孩子"把 `Mark received` 顶远（实测 135px 空隙）。
   ⚠️ 子元素各自 `nowrap`：两行都短（~85px），撑不破 `--kw-field-w`(150px)；
      上面那条 below 档把宿主设成 `normal`，不写的话会在 "KW 40/2026" 的空格处折成三行。
   ⚠️ 这两个类名是 **PHP（`kw_hint_inner()`）与 JS（`paint()`）共用的契约**，
      改一处必须改两处（测试与打包守卫钉着这一对）。 */
.kw-hint {
  display: inline-flex;
  flex-direction: column;
  font-size: 11px;
  line-height: 1.25;
  color: #6b7280;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.kw-hint-kw, .kw-hint-range { display: block; white-space: nowrap; }
.kw-hint[hidden] { display: none; }
.week-field { display: inline-flex; align-items: center; gap: 6px; }
.week-field input[list] { min-width: 140px; }
/* the hint must never show up in a printed document */
@media print { .kw-hint { display: none !important; } }

/* ============================================================
 * 0.42.0-InvLayout (#2) — Invoice: bold fixed declaration
 * ============================================================
 * Wolfgang 2026-09-13: "后加固定文本字体加粗" — the tax-exempt declaration sits
 * between the Amount / VAT / Total Sum footer and the Terms block. One <div>
 * per line so a two-language note stacks cleanly on the A4.
 */
.doc-taxnote {
  margin-top: 4mm;
  font-weight: 700;
  font-size: 10pt;
  line-height: 1.45;
  text-align: left;
}

/* ============================================================
 * 0.43.0-UiUnify — shared UI fixes (#5 ghost buttons, #11 btn-primary,
 *   #12 ⓘ panels, #3 page code, #10 item editor cell, #6 panel actions)
 * ============================================================ */

/* #11 — `.btn-primary` was used in ~20 places but NEVER defined, so every
 * "primary" button silently rendered as a plain `.btn`. Defining it keeps the
 * semantic class meaningful (it is the filled brand button). */
.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-dark); }

/* #5 — `.btn-ghost` is white-on-transparent, made for the dark topbar. Inside
 * white cards / tables that is white on white: the History link on the PI list,
 * "Versions", "Timeline", documents' "Open", users' "Edit" … were all invisible.
 * Scoped to light surfaces so the topbar keeps its white ghost buttons. */
.card .btn-ghost,
.table .btn-ghost {
  color: var(--brand);
  border-color: var(--line);
  background: #fff;
}
.card .btn-ghost:hover,
.table .btn-ghost:hover { background: var(--brand-tint); border-color: var(--brand); }

/* 0.49.0 — Wolfgang 2026-09-16: "给所有的 Timeline 按钮着底色".
 * The rule above fixed the ghost buttons that sit in a card / table, but the
 * Timeline button on a DETAIL page lives in `.page-head` — outside both — and
 * was still white on white (the screenshots he reviewed showed it washed out).
 * Timeline buttons are given a tinted fill so they read as buttons on every
 * light surface, and the same treatment is extended to any other ghost button
 * in a page head (identical defect, identical cause). */
a.btn-ghost[href^="timeline.php"] {
  color: var(--brand);
  background: var(--brand-tint);
  border-color: var(--brand);
}
a.btn-ghost[href^="timeline.php"]:hover {
  color: #fff;
  background: var(--brand);
}
.page-head .btn-ghost {
  color: var(--brand);
  background: var(--brand-tint);
  border-color: var(--line);
}
.page-head .btn-ghost:hover { background: var(--brand-tint); border-color: var(--brand); }

/* #12 / #7-#9 — money chips in the list strip + the ⓘ explanation panel.
 * The panel is a native <details> popover: no JavaScript, keyboard reachable. */
.pipeline-metrics {
  display: flex;
  align-items: stretch;
  gap: 8px;
  margin-left: 8px;
  padding-left: 12px;
  border-left: 1px solid var(--line);
}
.pipeline-metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
  justify-content: center;
  padding: 6px 12px;
  border-radius: 8px;
  background: var(--brand-tint);
  min-width: 96px;
}
.pipeline-metric-label { font-size: 11px; font-weight: 600; color: var(--ink-soft); }
.pipeline-metric-value { font-size: 15px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }

.pipeline-info { margin-left: auto; align-self: center; position: relative; }
.pipeline-info > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--brand);
  color: var(--brand);
  font-size: 12px;
  font-weight: 700;
  font-style: italic;
  font-family: Georgia, "Times New Roman", serif;
  background: #fff;
  user-select: none;
}
.pipeline-info > summary::-webkit-details-marker { display: none; }
.pipeline-info[open] > summary { background: var(--brand); color: #fff; }
.pipeline-info-body {
  position: absolute;
  right: 0;
  top: 26px;
  z-index: 20;
  width: 380px;
  max-width: 92vw;
  padding: 12px 14px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(15, 30, 50, .16);
  text-align: left;
}
.pipeline-info-body dl { margin: 0; }
.pipeline-info-body dt {
  font-size: 12px;
  font-weight: 700;
  color: var(--brand);
  margin: 8px 0 2px;
}
.pipeline-info-body dt:first-child { margin-top: 0; }
.pipeline-info-body dd { margin: 0; font-size: 13px; color: var(--ink-soft); line-height: 1.5; }
/* an explanation is screen-only — never let it print */
@media print { .pipeline-info { display: none !important; } }

/* #3 — the stable page code (so a page can be named unambiguously).
   0.45.0-MemoTotal (#2) — it MOVED OUT OF THE TOPBAR into the page footer.
   Reason: its length differs per page ("PI-LIST" vs "DC-TIMELINE"), and sitting
   between the nav and the user menu it pushed that menu sideways on every
   navigation (Wolfgang: "切换页面时会引起标题栏跳动"). In the footer it is
   absolutely positioned and right-aligned, so its length cannot shift anything. */
.footer {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
}
.footer .page-id {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card, #fff);
  color: var(--ink-soft);
  font-size: 11px;
  letter-spacing: .3px;
  white-space: nowrap;
}
.footer .page-id b { font-weight: 700; color: var(--brand); }
.footer .page-id .page-id-name { font-weight: 400; opacity: .8; }
@media print { .footer { display: none !important; } }

/* #10 — the item editor's Model / Description / Note cell */
.item-main { min-width: 260px; }
.item-main input { font-size: 14px; }
/* 🔄 1.77.0-ItemText — Note 与上面那两个 input 同一个字号（同一格里三行要一致）。 */
.item-main .item-note-input { font-size: 14px; }
/* 1.77.0-ItemText — item 编辑器的 Note 控件（由 `doc_item_main_fields()` 产出，
   六个编辑器共用一份）。`rows="1"` ⇒ 默认只占一行，不把卡片撑高；
   `resize: vertical` 打开浏览器**原生**的右下角拖拽柄（他 2026-10-16：
   "右下角有拖动功能，可以扩大来编辑"）；宽度与同格的 Model / Description 对齐。
   ⚠️ 特异性 (0,1,0) 高于通用 textarea 那条 (0,0,1) ⇒ 这几条一定生效。 */
.item-note-input {
  display: block;
  width: 100%;
  margin-top: 4px;
  resize: vertical;
  min-height: 30px;
  line-height: 1.35;
  font-family: inherit;
}
table.item-table td.item-main { vertical-align: top; }

/* #6 — actions at the bottom of a workbench's editing panel */
.doc-work-actions {
  display: flex;
  gap: 10px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

/* ============================================================================
 * 0.44.0-DocFont9 — 文档版式规范（Layer 1 chrome + Layer 2 内容）
 * ============================================================================
 * Wolfgang 2026-09-14 给出的统一要求，**六个文档（Quotation / PI / Order /
 * PO / Invoice / PL）今后全部适用**，总纲：默认字号 9pt，遇到标题才加粗。
 *
 * 本块放在文件**最末尾**，是这套规范唯一的权威定义：前面几十年份的同名字号
 * 规则都会被它覆盖。它们是历史层，不要再去逐条改（改了也白改，见下）。
 *
 * ⚠️ 两条渲染路径都必须满足，写法上有个硬约束：
 *   ① 打印 / PDF 主路径：doc-paged.js 把 `.doc` 的子元素**复制**进 `.doc-flow`，
 *      该元素已经**不在 `.doc` 之下** → 覆盖规则**不能**用 `.doc ` 前缀限定；
 *   ② 无 JS 回退路径：`.doc` 原样在文档流里。
 *   所以下面一律按元素类名直接写；与旧规则冲突处加 `!important`
 *   （旧规则常有更高特异性，例如 `.doc-table tbody td` 会盖掉 `.doc-items-ref td`）。
 *
 * ── Layer 1（页眉 / 页脚 / 页面框）────────────────────────────────────────
 *   1.1 左右边距 15mm，上边距 15mm，下边距 10mm
 *   1.2 去除页码（→ 移到分隔线之下，见 Layer 2 / 1.1）；分隔线贴合地址与 date 行
 *   1.3 页脚三栏**整体居中**（每栏内部仍左对齐）
 *   1.4 字号：公司名 9pt 加粗(1)；地址行 9pt(2)；单号值 9pt(3)；字段名 9pt(4)；页码 9pt(5)
 *   1.5 字号：页脚 6.5pt 保持(31)
 * ── Layer 2（内容）────────────────────────────────────────────────────────
 *   1.1 页码位于页眉分隔线之下，9pt(5)
 *   1.2 公司信息：客户公司名 10pt 加粗(7)、Attn: 标签 10pt 加粗(9)；地址 9pt(8)、
 *       联系人 9pt(10)
 *   1.3 联系人表格：去掉所有内部横线，只保留外边框
 *   1.4 联系人表格：字段名(11)/字段值(12) 均 9pt；分组标题(13) 9pt 大写
 *   1.5 问候语(14) 9pt
 *   1.6 Item 表格：全部 9pt；表头(15/16) 与 Model(18) 加粗；所有单元格**上对齐**
 *   1.7 Ref 行(26/27) 9pt
 *   1.8 表下 Terms(28/29) 9pt，其中字段名(28) 加粗
 *   1.9 蓝色 Notes(30) 9pt
 *   ※ 不在清单上、但按"默认 9pt"一并收敛的：文档标题保持 17pt(6，待确认)、
 *     PL 包装块、Invoice 免税声明(加粗)/合计脚、PI bank、PO 供方规格行。
 * ========================================================================== */

:root {
  --doc-mx: 15mm;   /* 左右页边距 */
  --doc-mt: 15mm;   /* 上页边距（页眉起始位置） */
  --doc-mb: 10mm;   /* 下页边距（页脚结束位置） */
}

/* ---- 1.1 页面框 ---------------------------------------------------------- */
.doc     { padding: var(--doc-mt) var(--doc-mx) var(--doc-mb) !important; }  /* 直出/无 JS 回退路径 */
.kbp-head { top: var(--doc-mt) !important; left: var(--doc-mx) !important; right: var(--doc-mx) !important;
            display: block !important;              /* 页眉内部现在分两行：top 行 + 页码行 */
            padding-bottom: 0 !important; border-bottom: none !important; }
.kbp-foot { bottom: var(--doc-mb) !important; left: var(--doc-mx) !important; right: var(--doc-mx) !important; }
/* 打印主路径：.pagedjs_page_content 的 padding 是唯一的页边距来源（@page margin 被锁 0）。
   实测（2026-09-14）：页眉 top 行 15.02mm + 页码行 5.4mm → 底边 30.02mm，
   所以 padding-top = 30mm 正好落在分隔线处；流内第一个元素是 .doc-title，
   它的 padding-top:10mm 就是原有的 L2 上止点（标题距分隔线 10mm）。
   padding-bottom = **31mm**（0.70.0 批：页脚行距 1 → 1.25，块高 44.02 → 53.72pt；
   页脚底仍钉在 10mm → 盒顶 ≈758.8pt，所以内容框下沿要退到 754.1pt，净空 ≈4.7pt）。
   ⚠️ 这条简写**不是最后生效的那条** —— 0.57 块末尾另有一条 `padding-bottom`，靠书写顺序
      决胜（两道守卫与三个测试问的都是"最后一条"，所以两个数字必须同步改）。 */
.pagedjs_page_content { padding: 30mm var(--doc-mx) 31mm var(--doc-mx) !important; }

/* ---- 1.2 分隔线贴合 / 页码下移 ------------------------------------------ */
.doc-head { display: block !important; width: 100%; padding-bottom: 0 !important;
            border-bottom: none !important; box-sizing: border-box; }
.doc-head-top {                                    /* 原来的 flex 行搬到这一层 */
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 8mm; width: 100%; box-sizing: border-box;
  padding-bottom: 0;                               /* ← “贴着地址与 date 行” */
  border-bottom: 0.5pt solid #111;
}
.doc-head .doc-page-num,
.kbp-head .doc-page-num {                          /* Layer 2 / 1.1：分隔线之下的独立一行 */
  display: block !important;
  text-align: right;
  margin: 0.8mm 0 0 !important;
  padding: 0 !important;
  font-size: 9pt !important;
  font-weight: 400 !important;
  color: #444 !important;
  white-space: nowrap;
}
.doc-page-num::before { content: none !important; }   /* 不再需要 “|” 前缀 */

/* ---- 1.4 页眉字号（1/2/3/4 → 9pt，1 加粗） ------------------------------ */
.doc-head-co, .doc-head-co > div { font-size: 9pt !important; line-height: 1.4; }
.doc-head-co-1 { font-weight: 700 !important; }
.doc-head-meta { font-size: 9pt !important; }
.doc-head-label { font-size: 9pt !important; }     /* 非加粗：清单未要求(4)加粗 */

/* ---- 1.3 / 1.5 页脚：三栏整体居中，每栏左对齐 --------------------------- */
.doc-footer, .kbp-foot { justify-content: center !important; gap: 10mm !important; }
.doc-footer .doc-footer-col,
.kbp-foot  .doc-footer-col { flex: 0 1 auto !important; min-width: 0; text-align: left !important; }
.doc-footer, .kbp-foot { font-size: 6.5pt; }       /* 31：保持 */

/* ---- Layer 2 / 1.2 公司信息（7/9 → 10pt 加粗，8/10 → 9pt） ------------ */
.doc-topinfo .lay2-reg-co       { font-size: 10pt !important; font-weight: 700 !important; line-height: 1.4; }
.doc-topinfo .lay2-sec-label    { font-size: 10pt !important; font-weight: 700 !important; }
.doc-topinfo .lay2-addr         { font-size: 9pt !important; }
.doc-topinfo .lay2-attn         { font-size: 9pt !important; }

/* ---- Layer 2 / 1.3 + 1.4 联系人表格 ------------------------------------ */
.doc-infobox {
  font-size: 9pt !important;
  border: 0.5pt solid #111 !important;   /* 只保留外边框 */
}
.doc-infobox td,
.doc-infobox th {
  border: none !important;               /* 内部横线全部去掉（旧 M4.16 规则给每个 td 加了 border-bottom） */
  font-size: 9pt !important;
  padding: 2px 6px !important;
  background: none;
}
.doc-info-label { font-size: 9pt !important; }
.doc-info-section { font-size: 9pt !important; }   /* 13：9pt 大写（text-transform 已在上文设定） */

/* ---- Layer 2 / 1.5 问候语 ---------------------------------------------- */
.doc-greeting { font-size: 9pt !important; }

/* ---- Layer 2 / 1.6 Item 表格：全部 9pt、表头与 Model 加粗、上对齐 ------ */
.doc-table { font-size: 9pt !important; }
/* 🔴 1.73.0-DocTypeScale（批 3／3，2026-10-15 下午）—— **表头字重 700 → 400**。
   依据：口径从「只有报价单去粗」改成「**六张一致**」（任务书 §4-2「提升为六张通用」＋已定项 4）。
   值**归权威块**（这里就是 `.doc-table thead th` 的权威定义），并**删掉**原来那条只在报价单
   生效的覆盖（`.doc-quotation-table thead th { font-weight: 400 }`）—— 留着它就是
   「两条选择器不同、特异性相同、靠书写顺序决胜」的写法，正是本项目反复付账的形状。
   ⚠️ 连带的**已接受**后果：老快照 HTML 里也有 `.doc-table` ⇒ 已签发单据**重印时表头也不再加粗**
      （已定项 4：接受老单据变新版式、不加开关）。 */
.doc-table thead th { font-size: 9pt !important; font-weight: 400 !important; vertical-align: top !important; }
.doc-table td,
.doc-table tbody td,
.doc-table tfoot td { font-size: 9pt !important; vertical-align: top !important; }
/* 17：序号列 —— 1.73.0 起 **六张一律不加粗**（1.69 时只改报价单，本批收成一处）。
   ⚠️ 只改这里**不够**：下面还有一条按行类排除的规则（特异性 (0,6,3) 高过这里的 (0,2,2)）
   ⇒ 两条**必须一起**改，否则本条的 400 会被静默顶回 700。 */
.doc-table tbody td:nth-child(1) { font-weight: 400 !important; }
.doc-line b, .doc-table b, .doc-model-line b { font-weight: 700 !important; }   /* 18：Model 加粗 */
.doc-item-note, .pi-note, .pi-ref { font-size: 9pt !important; }    /* 20 / PO·PI 的 item note·Ref */

/* ---- Layer 2 / 1.7 Ref 行 --------------------------------------------- */
.doc-items-ref td { font-size: 9pt !important; }

/* ---- Layer 2 / 1.8 表下 Terms ---------------------------------------- */
.doc-term, .doc-term-table { font-size: 9pt !important; }
.doc-term-table td { font-size: 9pt !important; }
.doc-term-label { font-weight: 700 !important; }

/* ---- Layer 2 / 1.9 蓝色 Notes ---------------------------------------- */
.doc-notes, .doc-note { font-size: 9pt !important; }

/* ---- 其余文档（PL / Invoice / PI）按"默认 9pt"一并收敛 ---------------- */
.doc, .pagedjs_page_content .doc-flow { font-size: 9pt !important; }   /* 文档默认字号 = 9pt */
.doc-pack, .doc-pack td { font-size: 9pt !important; }
.doc-taxnote { font-size: 9pt !important; }                            /* Invoice 免税声明：仍加粗 */
.doc-bank, .pi-artno, .doc-meta, .doc-total,
.doc-term-title, .doc-item-ref, .doc-customer, .doc-cust-name { font-size: 9pt !important; }

/* ============================================================
 * 0.45.0-MemoTotal (#1) — the Memo drawer can edit a row in place
 * ============================================================
 * `.memo-item` is a flex row: [✓] [body] [✎]. The pen swaps the body for a
 * one-line text field + a date field; Enter saves, Esc / Cancel restores.
 * The endpoint (todos.php action=todo_edit) already existed — only the UI was
 * missing, so the drawer was add-and-check only.
 */
.memo-item .memo-edit {
  flex: 0 0 auto;
  margin-top: 1px;
  width: 22px;
  height: 22px;
  line-height: 1;
  padding: 0;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink-soft);
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
}
.memo-item .memo-edit:hover { border-color: var(--brand); color: var(--brand); }
.memo-item .memo-edit.active,
.memo-item .memo-edit[disabled] { opacity: .45; cursor: default; }

.memo-edit-form { display: flex; flex-direction: column; gap: 6px; }
.memo-edit-form input[type="text"],
.memo-edit-form input[type="date"] {
  width: 100%;
  padding: 6px 8px;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: 6px;
  box-sizing: border-box;
}
.memo-edit-form input[type="date"] { width: 150px; }
.memo-edit-actions { display: flex; gap: 6px; }
.memo-edit-actions .btn { padding: 4px 10px; font-size: 12.5px; }

/* ============================================================================
 * 0.46.0-DocLine — 文档细节（Wolfgang 2026-09-14，八条，六文档全部生效）
 * ============================================================================
 * 与 0.44.0-DocFont9 同样的规矩：本块放在文件最末尾，是这批改动的唯一权威
 * 定义，前面的历史规则一律不动（多代并存，后写 + 同特异性后者赢）。
 *
 * ⚠️ 两条渲染路径都要满足：打印/PDF 走 doc-paged.js，把 `.doc` 的子元素**复制**
 * 进 `.doc-flow`（已不在 `.doc` 之下）→ 覆盖规则**不能**用 `.doc ` 前缀限定。
 * 所以下面一律按元素类名直接写，与旧规则冲突处加 `!important`。
 *
 *   1) item 表格：每条 item 上方一条实线（表头下边那条除外，它已经是一条线）
 *   2) Note 内容与标识文字：用默认字色（黑），不再是灰
 *   3) 3a 表头：灰底 + **不显示列白线** + 标题行下边黑实线
 *      3b 表格之后：Table Note 项（无内容不渲染，见 doc_table_note()）
 *   4) ~~Show total column 选项在 Save 后要保持~~（Wolfgang 2026-09-14 暂缓，
 *      本地逐页实测五种工作台均未复现，等他验证后再处理）
 *   5) Ref 行：上方加一个空行、去除链接、字色黑
 *   6) 页脚三栏：左右栏分别向两端外移 15mm
 *   7) Validity 行：10pt 加粗
 *   8) 全文默认黑色；例外 = 页脚 75% 灰度、logo 的蓝
 *
 * ── 有意保留的两个例外（不在"默认字色"里，Wolfgang 如要改说一声即可）──────
 *   · 草稿标题的 DRAFT 红（0.35.1 起的状态标记，.doc-title-draft）
 *   · Notes 的蓝（.doc-notes，报价单底部的 "* In case of purchase…" 等）
 * ========================================================================== */

:root {
  --doc-ink:      #000;      /* 8 · 文档正文默认字色 */
  --doc-foot-ink: #404040;   /* 8 · 页脚 75% 灰度（75% 墨量 ≈ #404040） */
  --doc-th-bg:    #e6e6e6;   /* 3a · 表头灰底（沿用原值，只去白缝） */
  --doc-line:     #000;      /* 1 / 3a · 表头下边的黑实线（标题下那条） */
  --doc-row-line: #bdbdbd;   /* item 之间的**行线**（1.74.0 起 `1px dotted`；KBP7-③ 曾是 1px dashed、更早是 0.5pt dotted #000） */
  --doc-foot-out: 8mm;       /* 6 · 页脚左右外扩：想更靠边就调小（0 = 顶住页边距） */
}

/* ---- 8 · 全文默认黑色 ---------------------------------------------------- */
.doc,
.pagedjs_page_content .doc-flow { color: var(--doc-ink) !important; }

/* 历史灰色文字（多代遗留，逐个点掉；子元素自己的 color 永远赢过继承，
   所以必须显式覆盖，不能只靠父级 color） */
.doc-item-note, .pi-note, .pi-ref, .doc-item-ref, .doc-item-no, .doc-ref,
.doc-info-section, .doc-info-value, .doc-empty, .pi-artno,
.doc-line, .doc-item-name, .doc-model-line, .doc-freight-label,
.doc-pack-label, .doc-term-label, .doc-ref-label, .doc-tablenote { color: var(--doc-ink) !important; }

/* 🔵 1.120.0-RefLink（Wolfgang 2026-11-05）—— 纸面 Ref 的**链接要看得出来**。
   现场：全局 `a { color: var(--brand) }` = #1f4e79，跟正文墨色几乎一样、而且没有下划线
   ⇒ 屏幕上看不出、打印出来更看不出（他原话："肉眼无法知道这个 ref 会是一个链接"）。
   链接蓝 + 下划线：黑白打印也能区分。
   `print-color-adjust: exact` 防浏览器打印时把链接强制刷成黑色。 */
/* ⚠️ **不能**写成 `.doc .doc-ref-link`（0,2,0）——
   `doc-paged.js` 会把 `.doc` 的**子元素克隆**进 `.doc-flow`，原 `.doc` 被隐藏
   ⇒ 克隆出来的元素**没有 `.doc` 祖先**，那条选择器在真实 A4 上永远不匹配。
   （2026-11-05 第二版就是这么又失效的；本地手写 `<div class="doc">` 测过是**假绿**。）

   正确做法：锚在「容器类 + a + 类」上 = **0,2,1**，压过下面那条兜底
   `.doc-item-note a { color: inherit !important }`（0,1,1）。
   容器类（`.doc-item-note` / `.doc-items-ref` / `.doc-table` / `.doc-item-ref`）
   全都在克隆出来的子树里，分页前后都成立。 */
.doc-item-note a.doc-ref-link,
.doc-item-note a.doc-ref-link:visited,
.doc-items-ref a.doc-ref-link,
.doc-items-ref a.doc-ref-link:visited,
.doc-table a.doc-ref-link,
.doc-table a.doc-ref-link:visited,
.doc-item-ref a.doc-ref-link,
.doc-item-ref a.doc-ref-link:visited {
  color: #1155cc !important;
  text-decoration: underline !important;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.doc-page-num { color: var(--doc-ink) !important; }

/* 8 · 例外一：页脚 = 75% 灰度（原来 #999 + opacity .6，实测约 25% 灰，太淡） */
.doc-footer, .kbp-foot, .doc-footer .doc-footer-col, .kbp-foot .doc-footer-col {
  color: var(--doc-foot-ink) !important;
  opacity: 1 !important;
}
/* 8 · 例外二：logo 的蓝色来自图片本身，这里不需要做任何事（.doc-logo-img） */

/* ---- 3a · item 表格：灰底表头 + 无列白线 + 黑实线 ----------------------- */
/* `border-collapse` 是这批最关键的一条：`.doc-table` 从来没有设过它，
   浏览器默认 `separate` + `border-spacing: 2px` → 相邻 th 之间 2px 白缝，
   在灰底上就表现为一列列"白线"（Wolfgang 说的"列白线"）。collapse 之后
   单元格共边，白缝消失，行/表头边框也才画得干净。 */
.doc-table {
  border-collapse: collapse !important;
  border-spacing: 0 !important;
}
.doc-table thead th {
  background: var(--doc-th-bg) !important;
  border: 0 !important;                        /* 去掉一切列竖线 */
  border-bottom: 1px solid var(--doc-line) !important;   /* 标题行下边 = 黑实线 */
}

/* ---- 1 · 每条 item 上方一条线（细密**点**线）---------------------------- */
.doc-table tbody > tr > td { border-top: 1px dotted var(--doc-row-line) !important; }
/* 🔴 1.74.0-DocNameRev0（Wolfgang 2026-10-15 20:1x，**带截图**）—— `dashed` → **`dotted`**。
   他：*"报价单 item 表格每个 item 之间的分隔线现在明显是虚线，我要的效果是一种细密的地点细线"*。
   · `dashed` 在 Chromium 里是 **3px 段 / 3px 隙**（节距 6px）⇒ 一眼就是"虚线"；
   · `dotted` 在 Chromium 里是 **1px 点 / 1px 隙**（节距 2px）⇒ 正是他要的"细密点线"。
   · 🔴 **仍必须是 1px 整数像素 + 浅灰**：1.33.0 那次他就要过"更细更密的点线"，
     当时落成 `0.5pt dotted #000`（= 0.667px **亚像素**）⇒ 他自己反馈*"很不平滑"*、
     KBP7-③ 才改成 dashed。**亚像素是那次的病根，不是 dotted 本身** —— 所以本批用
     `1px`（整数）＋ `--doc-row-line`（#bdbdbd，不与表头黑线抢注意力），两边都不再踩。
   ⚠️ **六张纸共用**（他 2026-10-13：*"6 张纸都换"*）—— 同族**两处必须同改**：
      这里（通用行线）与下面 `1a`（lead / Freight 行）。
   ⚠️ 表头下那条黑实线**不动**（`border-bottom: 1px solid var(--doc-line)`）。 */
/* 🔵 1.33.0-WeekStd（Wolfgang 2026-10-08：「关于分隔线的改进要求，将其应用于所有六张纸」）
   —— 上面这条**通用行线**才是其余五张纸的 item 分隔线来源（`doc-item-lead` 只由
   `quotation-document.php` 输出 ⇒ 下面 `1a` 那条在别的纸上只命中 Freight 行）。
   🔴 KBP7-③（Wolfgang 2026-10-13 20:41，**带截图**）：*"Items 表格内的细密点状虚线可能没有
      选对正确的线型，显示出来感觉很不平滑"* —— **他说对了**。旧值 `0.5pt dotted var(--doc-line)`
      而 `--doc-line` 是 **#000** ⇒ 一条 **0.667px 的黑色点线**：亚像素宽度 ＋ 浏览器自身的
      点距节拍 ⇒ 点忽大忽小、忽连忽断（1× 屏与 1.5× 高分屏都能看出来）。
      ⇒ 改成 **`1px dashed` + 专用变量 `--doc-row-line`（#bdbdbd）**：1px 是整数像素、
        dashed 的 3px 段 / 3px 隙在任何 DPR 与打印下都均匀；颜色压到浅灰，
        行线不再与表头黑线抢注意力。
      ⚠️ **六张纸一起换**（他 2026-10-13 明确「6 张纸都换」）—— 同族**两处**必须同改：
         这里（通用行线）与下面 `1a`（lead / Freight 行）。
      ⚠️ 表头下那条黑实线**不动**（`border-bottom: 1px solid var(--doc-line)`，`--doc-line` 仍是 #000）。 */
/* 第一行不加：表头下边已经是一条黑实线（否则两条线叠在一起） */
.doc-table tbody > tr:first-child > td { border-top: none !important; }
/* Ref 行不是货品：它上方是"空行"而不是实线 */
.doc-table tbody > tr.doc-items-gap > td,
.doc-table tbody > tr.doc-items-ref > td { border-top: none !important; }

/* ---- 5 · Ref 行：上方空行 / 无链接 / 黑字 ------------------------------- */
.doc-items-gap > td {
  padding: 0 !important;
  border: 0 !important;
  font-size: 6pt !important;      /* 高度即"一个空行"，不靠 margin 撑 */
  line-height: 1.2 !important;
}
.doc-items-ref td { font-size: 9pt !important; color: var(--doc-ink) !important; }
/* 兜底：万一还有调用方塞进 <a>，也不允许出现链接色/下划线。
   顺带收掉老数据里"按 item 的 Ref: <a>…"（.doc-item-note 里那条），
   否则同一张表里黑字旁边会突然冒出一条蓝链 —— 与 #8"所有文字黑色"一致。
   🔄 1.120.0-RefLink（Wolfgang 2026-11-05）—— **这条现在有个例外**：
     Ref 里的 datasheet 链接他明确要求"蓝色 + 下划线，看得出能点"。
     例外由上面的 `.doc .doc-ref-link`（0,2,0，同样 !important）实现，
     本组选择器（0,1,1）压不住它 —— 这是**故意的**，别把那条例外删掉。 */
.doc-items-ref a,
.doc-items-ref a:visited,
.doc-table a,
.doc-table a:visited,
.doc-item-note a,
.doc-item-note a:visited,
.doc-item-ref a,
.doc-item-ref a:visited { color: inherit !important; text-decoration: none !important; }

/* ---- 3b · Table Note（item 表格之后，无内容不渲染） -------------------- */
/* 0.80.0-PlLayout — 多行时的对齐（Wolfgang 2026-09-25："如果是多行，则从第二行起
   会与 Table note 对齐，这需要修订到内容各行左对齐"）。
   老写法是"行内标签 + 裸文本"：第一行的内容缩在标签之后，而文本的**续行**回到版心最左边
   → 各行左边缘不齐。现在标签与正文是**同一个 flex 行**的两个项：
   正文自己贴住标签右沿，它的每一行（`<br>` 分隔）都落在同一个左边缘上。
   ⚠️ 刻意**不下载体量**（不用 `padding-left` + 负 `text-indent`）：标签宽度随字号/字体变，
   量出来的常数迟早漂；flex 不需要那个数。`min-width: 0` 是长单词/长编号的防溢出的。 */
.doc-tablenote {
  display: flex;
  align-items: flex-start;
  gap: 4px;
  margin-top: 2.5mm;
  font-size: 9pt !important;
  line-height: 1.4;
}
.doc-tablenote-label { font-weight: 700 !important; flex: 0 0 auto; }
.doc-tablenote-text  { flex: 1 1 auto; min-width: 0; }

/* ---- 6 · 页脚三栏：左右栏分别向两端外移 15mm --------------------------- */
/* 实测（2026-09-14）：三栏自然宽 35.9 / 36.3 / 42.1 = 114.3mm，容器 180mm。
   原来 `justify-content: center` + gap 10mm → 左右各留 22.85mm 空白。
   改成 space-between 后左右栏会顶到页边距（各外移 22.85mm，比要求多），
   所以再收 8mm 内边距：22.85 − 8 ≈ **14.9mm ≈ 用户要的 15mm**，且不溢出。 */
.doc-footer, .kbp-foot {
  justify-content: space-between !important;
  gap: 10mm !important;                 /* 保持一个最小栏间距 */
  padding-left: var(--doc-foot-out) !important;
  padding-right: var(--doc-foot-out) !important;
  box-sizing: border-box !important;    /* .kbp-foot 是绝对定位，padding 不能撑出页面 */
}

/* ---- 7 · Validity 行：10pt 加粗 ---------------------------------------- */
/* 特异性必须高于 0.44.0 的 `.doc-infobox td { font-size: 9pt !important }`
   （两条都是 !important，比的是特异性，不是先后） */
.doc-infobox tr.doc-validity td {
  font-size: 10pt !important;
  font-weight: 700 !important;
}

/* ============================================================================
 * 1.31.1-QuoteLine-2 — **Quotation 纸面三处**（Wolfgang 2026-10-07）
 * ----------------------------------------------------------------------------
 * 他的原话：*"1 table note有内容时，标题字样在A4纸面上不要出现；2 Table的最后一行
 * 下面加一条实分隔线，表示表格结束；3 Item之间的横分隔线，全部使用虚线。"*
 * 口径（1.31.x）：*"对 Quotation 做这个需求"* ⇒ 当时**只作用 Quotation**。
 * 🔵 **1.33.0-WeekStd（2026-10-08）他推翻了这条口径**：*"关于分隔线的改进要求，
 *    将其应用于所有六张纸"* ⇒ ② 收尾实线 与 ③ item 点线 **改为全局**
 *    （见下面 `1a` / `1c`；本块③的那三条作用域规则已删除）。① 仍只属于 Quotation。
 *
 * 🔴🔴 **作用域锚点 = 明细表自己身上的 `doc-quotation-table`**
 *   （`<table class="doc-table doc-quotation-table">`）—— **不是** A4 包装 div。
 *   这一条栽过一次，务必记住原因：打印 / 预览走 `assets/doc-paged.js`，它把 **`.doc` 的
 *   children 克隆**进一个新的 `.doc-flow`，然后把**原 `.doc` 隐藏**
 *   （`doc.style.display = 'none'`）⇒ 挂在 `.doc` 上的类**既不被克隆、也不再是祖先**，
 *   所有 `.doc-xxx …` 选择器在**分页后全部失效**。
 *   1.31.0 的第一版正是那么写的，现象极具迷惑性：
 *   **HTML 层改的（①）生效、CSS 层改的（②③）没生效**。
 *   ⇒ 铁律：**A4 纸面样式的作用域锚点必须落在 `.doc` 的子树里**（子节点跟着克隆走）；
 *     也**不能**写成 `.doc ` 前缀限定（同一条原因，0.47 块已记过一次）。
 *
 * 🔴 **位置**：本块**不放在文件最末尾**，而是放在 `0.47.0-DocLine2` 权威块**之前**。
 *    两条硬理由：
 *      ① 打包守卫有一条"**文件必须以规则收尾**"的判据（从 `0.47.0-DocLine2` 处往后找
 *         最后一个 `}`，其后不许再出现任何内容）—— 末尾挂一段裸注释就当场红
 *         （本批第一版正是这么被拦下的）；
 *      ② （1.31.x 时）本块靠**特异性**赢（`.doc-table.doc-quotation-table` 前缀 ⇒ (0,3,3)），
 *         与位置无关；块内**自下而上**排序（三条例外写在虚线规则之后，同特异 ⇒ 后来者胜）。
 *         ⚠️ **1.33.0-WeekStd 起这条不再适用** —— 线型已全局化，作用域规则已删除。
 * ============================================================================ */

/* ---- 2 · 表格收尾 + 3 · Item 之间的横线 —— **已上收为全局（六张纸一致）** --------
 * 1.31.0/1.31.1 当时靠 `doc-quotation-table` 作用域把 ②③ 限定在 Quotation；
 * 1.33.0-WeekStd（Wolfgang 2026-10-08：「将其应用于所有六张纸」）**推翻了那个口径** ⇒
 * 这里的三条作用域规则**整体删除**，改由无作用域的 `1a` / `1c` 承担
 * （`1a` = item 之间的点线，`1c` = 表格收尾实线，两处都带同一套三条例外）。
 * ⚠️ 标记类 `doc-quotation-table` 仍由 `doc_items_table_open()` 第三个参数输出，
 *    是 1.31.1 那次锚点事故留下的**遗存**（当前没有 CSS 依赖它）；留着不动是另一件事。
 * ---------------------------------------------------------------------------- */

/* ---- 1 · Table Note 的标题字样不上纸 ---------------------------------- */
/* 走**标记层**（`doc_table_note($t, false)` 根本不输出那个 span），不做 CSS 隐藏：
   本项目"同一件事只许一处实现"，用 `display:none` 藏就等于留了第二份真相。 */

/* ============================================================================
 * 0.47.0-DocLine2 — 档位表垂直整合 + 分页顶部留白（Wolfgang 2026-09-15）
 * ============================================================================
 * 本块同样放在文件**最末尾**，是这批改动的唯一权威定义，前面的历史规则不动。
 * ⚠️ 打印主路径下内容被复制进 `.doc-flow`（已不在 `.doc` 之下）→ 覆盖规则
 *    **不能**用 `.doc ` 前缀限定，一律按元素类名直接写 + 冲突处 `!important`。
 *
 * ── 1 · Quotation 档位表（方案 A）──────────────────────────────────────────
 * 背景：一个产品有多档价格时，产品名（Model + Description + Note）原来塞在
 * **第一档那一行**里，下面各档的"Model & Description"格是**空的**。产品名有
 * 2–3 行 → 第一档那行被撑高到 2–3 倍，其余档位就顺着往下跌、左边顶着一大片
 * 空洞，价格看起来"被分拆"。
 * 现在：序号格 + 产品格 `rowspan="档数"`、垂直居中 → 各档行**等高**，产品名
 * 读起来属于整组价格。Wolfgang 定：**档位之间不画线**，一个**产品**一条线
 * （档位不是 item）。
 *
 * ── 2 · 对齐必须改成"按类"，不能再靠位置 ──────────────────────────────────
 * ⚠️ 这是本批最容易只改一半的地方：item 表的对齐历来靠
 *   `.doc-table tbody td:nth-child(3..6)`（0.36 块）和
 *   `.doc-table tbody td:nth-child(1) { font-weight:700 !important }`（0.44 块）。
 * 档位行从 6 格变 4 格后：`nth-child(1)` 变成 Qty（会被**加粗**）、
 * `nth-child(3)` 变成 Unit Price（会被**居中**）→ 整块错位。
 * 所以下面把 item 表 body 的对齐/加粗**只认类**，并显式压掉档位行上的位置规则。
 * （其余五个文档没有 rowspan、格数不变，`nth-child` 继续有效，未受影响。）
 * ========================================================================== */

:root {
  /* 0.57.0-A4Split — 4mm → 9mm。
     实测（2026-09-21，PagedConfig.after 里读 getComputedStyle）：
       · `.pagedjs_page_content` 的 padding-top = 30mm，是按"页码行 5.4mm"算的；
       · 但页码行 ink 底实测在 **34.7mm**（86.9..98.3pt）→ 30mm 基线本身短了 **4.7mm**；
       · 用户要的"分隔线下空一行" = 再加一个空行 4mm（= --doc-blank-line 同一个量）。
     → 9mm = 4.7mm（补差） + 4mm（空行）。续页内容顶 = 39mm，第 1 页保持 30mm 不变。 */
  --doc-break-gap: 9mm;
}

/* ---- 1a · 行线：一个产品一条（Freight 也算一个"item"） ------------------ */
.doc-table tbody > tr.doc-item-lead > td,
.doc-table tbody > tr.doc-freight   > td { border-top: 1px dotted var(--doc-row-line) !important; }
/* 🔴 与上面「1 · 通用行线」**同一条线型**（1.74.0 起 `1px dotted --doc-row-line`）——
   两处必须一致，否则"有意料之外的双线/异色线"。他 2026-10-13：「6 张纸都换」。 */
/* 档位之间不画线（rowspan 那一列本来就画不出线，硬留着会断成一截） */
.doc-table tbody > tr.doc-tier > td { border-top: none !important; }
/* 🔴 1.33.0-WeekStd —— 三条例外必须写在 `1a` **之后**（同特异 ⇒ 后来者胜）：
   否则本规则会盖掉前面那两条 `none`，**Quotation 的首行**（它自己就是 `doc-item-lead`）
   会在表头那条黑线下面再画一条点线，**看起来像双线**。 */
.doc-table tbody > tr:first-child > td { border-top: none !important; }
.doc-table tbody > tr.doc-items-gap > td,
.doc-table tbody > tr.doc-items-ref > td { border-top: none !important; }

/* ---- 1c · 表格收尾：最后一行下面一条实线（= 表格结束）—— **六张纸一致** ---- */
/* 1.33.0-WeekStd：原来只有 Quotation 有（`doc-quotation-table` 作用域），现上全部六张纸。
   用 `:last-child` 而**不是**"最后一条 item"：Ref 行按设计本来就在这张表里
   （`doc_items_ref_row()` 的注释写着"与 Table Note 同一作用域"）⇒ 有 Ref 时
   收尾线在它下面，读起来就是"这张表到此为止"。 */
.doc-table tbody > tr:last-child > td {
  border-bottom: 1px solid var(--doc-line) !important;
}

/* ---- 1b · 产品格 / 序号格：跨整组，顶部对齐 --------------------------- */
/* Wolfgang 2026-09-15 复核 5 档样张后改：**序号与 Model 都上对齐**（原来是
   垂直居中）。产品名与第一档价格同起一条基线，读起来更像表格；居中会让
   产品名在整组里"浮"起来，反而不好找起点。
   与 0.44.0 的 `.doc-table tbody td { vertical-align: top !important }` 同向，
   这里显式写出来是为了让 m23 能断言"最后生效的那条"。 */
.doc-table tbody td.cell-model,
.doc-table tbody td.cell-pos { vertical-align: top !important; }

/* ---- 2 · 对齐与加粗只认类 ------------------------------------------------ */
/* 🔴 KBP7-⑮（Wolfgang 2026-10-15 凌晨）—— **报价单的序号不再加粗**
   （他：*"item表格中的序号，去除加粗"*）。
   🔵 `cell-pos` 全仓只由 `quotation-document.php` 输出（唯一一处）⇒ 这一格的加粗
      只影响报价单；其余五张纸的序号格**没有类**，走的是另一条规则（见下面那段
      "序号列加粗只对货品行生效"，本批给它补了 `:not(.cell-pos)`）。
   🔴 两条**必须一起改**：那条按行类排除的规则特异性 (0,5,3)，高过这里的 (0,2,2)
      ⇒ 只把这一条改成 400 是**不够的**（它会静默把序号重新加粗）。
   ⚠️ `text-align: center` 不动 —— 序号列仍然居中，本批只动字重。 */
.doc-table tbody td.cell-pos   { text-align: center !important; font-weight: 400 !important; }
.doc-table tbody td.cell-qty,
.doc-table tbody td.cell-unit  { text-align: center !important; }
.doc-table tbody td.cell-money { text-align: right !important; }
/* 档位行的第一格是 Qty，不是序号 —— 必须把 0.44 / 0.36 的位置规则压掉 */
.doc-table tbody > tr.doc-tier > td:first-child { font-weight: 400 !important; }

/* ---- 1c · 一个产品块不跨页 --------------------------------------------- */
/* rowspan 单元格不能拆开，行级 avoid 足够让整组一起移到下一页
   （老表 .doc-items tr 一直有这条，item 表反而漏了 —— style.css:1239） */
.doc-table tbody tr { break-inside: avoid; page-break-inside: avoid; }

/* ---- 3 · 分页后，第 2 页起先空一行再输出内容 --------------------------- */
/* ⚠️ 判据必须是 **`.pagedjs_page[data-page-number]`**。
   0.57.0-A4Split 实测：真实 DOM 是 `.pagedjs_page > .pagedjs_sheet > .pagedjs_page_content`，
   **每个 `.pagedjs_sheet` 都是父节点的 `:first-child`** —— 原来写成
   `.pagedjs_sheet:not(:first-child)` 时**永远不命中**，这条规则从写下那天起就是死的
   （实测两页 padTop 都是 113.386px = 30mm）。改成 data-page-number 后实测：
   第 2 页 147.402px = 39mm，第 1 页仍是 113.386px = 30mm。
   ⚠️ 这条必须写在 style.css 里（Paged.js 渲染之前就存在），否则页面已按旧高度排好，
   事后改 padding 不会重排。直出/无 JS 路径没有分页概念，不适用。 */
.pagedjs_page:not([data-page-number="1"]) .pagedjs_page_content {
  padding-top: calc(30mm + var(--doc-break-gap)) !important;
}

/* ---- 4 · Ref 行与 Table Note：都是左对齐 ------------------------------- */
/* Wolfgang 2026-09-15：\"Ref和Table note，都保持左对齐\"。
   Ref 本来就有 0.41.0 的 `.doc-items-ref td { text-align: left !important }`，
   这里把它与 Table Note 一起**钉在权威块里**（本块在文件最末尾 = 最后生效）：
   Ref 行唯一的 colspan 格正是\"第一格\"，一旦有人动
   `.doc-table tbody td:nth-child(1)`（它把每个首格居中），这条就是最后的防线。
   注意 Table Note 印在 `</table>` **之后**，是独立 div，必须自己声明。 */
.doc-items-ref td,
.doc-items-ref .doc-ref-label { text-align: left !important; }
.doc-tablenote,
.doc-tablenote-label { text-align: left !important; }

/* ============================================================================
 * 0.48.0-DocLine3 — 文档细节（Wolfgang 2026-09-16，三条）
 * ============================================================================
 * 本块同样在文件**最末尾**，是这批改动的唯一权威定义。
 *
 * ── 1 · Greeting 与 item 表格之间一个空行 ────────────────────────────────
 * `.doc-greeting` 原本只有 `margin-top`，与下面的表格之间是 0。
 * 「一个空行」沿用本文件既有的同一量：`--doc-break-gap: 4mm`（≈ 正文 9pt
 * 一行）—— 不再新造一个数，否则以后两处「空一行」长得不一样。
 * 注意：greeting 目前只有 **Quotation** 会输出（`quotations.doc_greeting`），
 * 所以这条实际上只影响 Quotation，符合 Wolfgang 的答复「只改 Quotation」。
 *
 * ── 2 · item 表行高 +20%（所有行）────────────────────────────────────────
 * **实测基准（2026-09-16，无 JS 直出路径，180mm 宽）**：
 *   表头行 6.850mm（padding 4px）· 普通 item 行 6.321mm（padding 3px）
 *   → 行内容高 17.4px（9pt × 1.45），所以「+20%」≈ 每行 +4.78px = 每侧 +2.4px。
 * ⚠️ **1.73.0-DocTypeScale 改了口径**：**表体**仍按"自己的高度 +20%"（3px → **5.4px**），
 *    但**表头**不再是 6.6px —— 1.66.0 时 Wolfgang 看过成品后要 *"带有灰度的背景，
 *    将其高度减少一些，当前过于高了"*，报价单表头改成了 **3px**；批 3 把这条
 *    **提升为六张通用**（任务书 §4-2）⇒ 表头统一 3px。原来那条只在报价单生效的覆盖
 *    （`.doc-quotation-table thead th { padding … 3px }`）已删除。
 *    ⇒ 结果：**表头 3px / 表体 5.4px**（表头比表体薄，这是用户看过的样子）。
 * ⚠️ `.doc-items-gap`（Ref 行上方那个「空行」）**不加**：它是刻意的高度占位
 *    （3.808mm），涨 20% 会连带把 Wolfgang 明确要过的「一个空行」改掉。
 *
 * ── 3 · 文档尾部两栏 —— **已撤回**（0.49.0）─────────────────────────────
 * 0.48.0 曾把 PI / Invoice 的尾部改成左右两栏（Term 左 / 银行右），并在这里
 * 定义了对应的 flex 容器类。Wolfgang 2026-09-16 复核后："两栏显示 term 和银行
 * 信息的设计不好看，还是保持原样" → 标记与样式**一并删除**，回到整幅堆叠：
 * PI = Term → 银行备注 → 银行块；Invoice = 免税声明 → Terms → 银行块。
 * （此处刻意不写回那个类名/函数名 —— 散文里出现选择器会让"它不许回来"这类
 *   打包守卫误判，m16 上吃过一次亏。）
 * ========================================================================== */

:root {
  --doc-blank-line: 4mm;   /* 1 · 「一个空行」——与 --doc-break-gap 同一个量 */
}

/* ---- 1 · greeting 与表格之间留一个空行 ---------------------------------- */
.doc-greeting { margin-bottom: var(--doc-blank-line) !important; }

/* ---- 2 · item 表：表体行高 +20% ／ 表头固定 3px（1.73.0 起六张一致，见上） ---- */
.doc-table thead th {
  padding-top: 3px !important;
  padding-bottom: 3px !important;
  /* 🔴 1.74.0-DocNameRev0 —— 表头**显式钉行高**：Terms 灰条那一条（见 `.doc-term-table
     tr.doc-term-head td`）按同一个式子钉住（3 + 9pt×1.45 + 3 = 23.4px）⇒ 两根柱子
     一样高。⚠️ 只钉一边 = 没钉（两边各自继承，浏览器一换就先漂一个像素）。 */
  line-height: 1.45 !important;
}
.doc-table tbody td,
.doc-table tfoot td {
  padding-top: 5.4px !important;
  padding-bottom: 5.4px !important;
}
/* 「空行」是刻意的高度占位，不参与 +20%（见本块顶部说明） */
.doc-table tbody > tr.doc-items-gap > td {
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}

/* ==========================================================================
   0.50.0-CRM — customer relationship panel, touchpoints, follow-up dates
   (Wolfgang 2026-09-17)

   Appended last on purpose — the project convention since 0.44.0 is that the
   newest block lives at the end of this file. Everything below is SCREEN UI
   only: no `.doc-*` class is touched, so the A4 documents are unaffected by
   this batch (CRM fields never reach a document — migration 047).
   Colours all come from the theme variables, so the dark theme is covered.
   ========================================================================== */

/* ---- Relationship panel ------------------------------------------------- */
.crm-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px 26px 26px;
  margin-bottom: 8px;
}
.crm-panel .form-section { margin-top: 0; }
.crm-panel .field-hint { font-size: 11px; color: var(--ink-soft); margin: 4px 0 0; }

.crm-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 18px;
}
.crm-stat {
  flex: 1 1 150px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 14px;
}
.crm-stat-label {
  display: block;
  margin-bottom: 2px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .6px;
  color: var(--ink-soft);
}
/* A date that is due (or overdue) is the one thing that must jump out. */
.crm-date { font-variant-numeric: tabular-nums; font-weight: 600; }
.crm-date.crm-due { color: var(--danger); }

/* Grey text — a reminder only: nothing here validates or blocks a save. */
.crm-hint {
  margin: 0 0 16px;
  padding: 8px 12px;
  background: var(--paper);
  border: 1px dashed var(--line);
  border-radius: 8px;
  font-size: 13px;
  color: var(--ink-soft);
}

.crm-sub {
  margin: 0 0 12px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .6px;
  color: var(--ink-soft);
}

/* ---- Touchpoints timeline ---------------------------------------------- */
.crm-tl { display: flex; flex-direction: column; }
.crm-tl-row {
  display: flex;
  gap: 14px;
  padding: 10px 2px;
  border-top: 1px solid var(--line);
}
.crm-tl-row:first-child { border-top: 0; }
.crm-tl-date {
  flex: 0 0 96px;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.crm-tl-date .cell-sub {
  display: block;
  font-size: 11px;
  font-weight: 400;
  color: var(--ink-soft);
}
.crm-tl-body { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.crm-tl-meta { margin-top: 2px; font-size: 12px; color: var(--ink-soft); }
.crm-tl-detail {
  margin-top: 4px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
  font-size: 13px;
  color: var(--ink-soft);
}

/* ---- 0.53.0-CrmQueue (A) — "Customer background" on the project page -----
   Read-only projection of what a HUMAN recorded on the customer BEFORE this
   project existed. The rows reuse .crm-tl / .crm-tl-row, so only the collapsed
   wrapper needs styling. ------------------------------------------------ */
.crm-bg {
  margin: 0 0 14px;
  padding: 12px 14px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.crm-bg > summary { cursor: pointer; font-weight: 600; }
.crm-bg[open] > summary { margin-bottom: 4px; }
.crm-bg-body { margin-top: 6px; }
.crm-bg-body .crm-tl-detail { margin-top: 4px; }

/* ---- 0.53.0-CrmQueue (B) — the CRM follow-up queue page ----------------- */
.crm-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 14px; }
.crm-tl-bg > summary { display: inline-block; cursor: pointer; }
.crm-tl-bg[open] > summary { margin-bottom: 8px; }
.crm-tl-bg-body { min-width: 340px; }
.crm-tl-bg-body .crm-tl-row:first-child { border-top: 0; }

/* Source tags on the merged timeline (base .tl-tag is the light-blue pill). */
.tl-tag-crm       { background: var(--brand-tint); border-color: var(--brand); color: var(--brand); }
.tl-tag-project   { background: var(--paper);      border-color: var(--line);  color: var(--ink-soft); }
.tl-tag-quotation { background: transparent;       border-color: var(--ink-soft); color: var(--ink-soft); }

/* ==========================================================================
   0.51.0-A4Tail — 文档尾部：合计脚不再压页脚、VAT 上下两条线去掉
   (Wolfgang 2026-09-17, items 3 / 4 / 8)

   仍然遵守 0.44.0 起的约定：新块写在文件末尾，历史规则一条不改。
   ========================================================================== */

/* ---- 4 · 合计脚不再是"每页重复的表尾" ----------------------------------
   ⚠️ 这是「内容过长时压到页脚」的真正根因（0.51.0 实测复现）：
   `<tfoot>` 的 display 是 `table-footer-group`，在分页媒体里 **每一页** 都会
   在表格片段的底部重画一次；当片段正好收在内容框下沿时，这一份就被画到了
   内容框 **之外**（A4 842pt − 27mm 下内边距 = 765pt 以下），正好落在页脚带上。
   实测（25 行 item 的 Invoice）：Amount@747 / VAT@768 / Total Sum@790 ——
   后两条在 765pt 之下；而且这三个数字在下一页又重复了一遍。
   改成 `table-row-group`（普通行组）后它只出现一次、紧跟在最后一行 item 之后，
   装不下时整组移到下一页。 */
.doc-table tfoot { display: table-row-group !important; }

/* 整组尽量别拆散（Amount / VAT / Total Sum 三行要么一起在这一页，要么一起去下一页） */
.doc-table tfoot tr { break-inside: avoid; page-break-inside: avoid; }

/* ---- 3 · VAT 上下两条线去掉 -------------------------------------------
   合计脚三行的上边线来自 `.doc-table tfoot td { border-top: .5px }`，
   第一行另有一条更深的线（`tr:first-child` 的 1.5px）—— 那是「Amount 上方」
   那条，**保留**。Wolfgang 要去掉的是 VAT 那一行的上下两条，也就是
   VAT 行自己的上边线 + 紧随其后的 Total Sum 行的上边线。
   按**类**取行、不按位置：没有 VAT 的文档（如 PL）根本不出 VAT 行，
   用 nth-child 会顺手删掉 Amount 下面那条线。 */
.doc-table tfoot tr.doc-sum-vat td,
.doc-table tfoot tr.doc-sum-total td { border-top: 0 !important; }

/* ---- 8 · 标签与值不许被分页切开 ---------------------------------------
   PL 的 `Customer PO:` / Invoice 的 `Terms of ...` 都是
   `.doc-term-table` 的「标签格 + 值格」两格行。Chrome 允许在**行内**分页，
   于是标签留在上一页（灰底那一格）、值被当成"续行的第一格"排到左栏 ——
   正是 Wolfgang 看到的「PO No 跑到左侧标题栏」。行级 avoid 是唯一可靠的挡法
   （外层 `.doc-term` 上已有的 avoid 挡不住表格内部的行）。 */
.doc-term-table tr { break-inside: avoid; page-break-inside: avoid; }

/* ---- 7 · Term 标题行（灰底 + 加粗）-------------------------------------
   Invoice 尾部：「Terms of payment / Terms of delivery」上面加一行灰底的 `Term`
   作为标题（Wolfgang 2026-09-17）。灰底沿用 item 表头的同一个变量，深色主题下
   不会变成一块白。 */
.doc-term-table tr.doc-term-head td {
  background: var(--doc-th-bg) !important;
  /* 🔴 1.73.0-DocTypeScale（批 3／3）—— 700 → **400**：口径由「只报价单去掉灰条加粗」
     改成「**六张一致**」（任务书 §4-2 ＋ 验收 ② "表头三种状态（灰条/字号/字重）六张一致"）。
     原来那条只在报价单生效的覆盖（`.doc-quote-terms .doc-term-table tr.doc-term-head td`）已删除。
     ⚠️ 与它**成对**的是同批提升的 `.doc-term-label`（术语标签单元格，见 4xxx 行那一族）——
        只去灰条、留标签加粗会让**同一张表里**"表头常规 + 标签加粗"，比两端都更难看。 */
  font-weight: 400 !important;
  /* 🔴 同批：灰条高度也归这里（原来住在 `.doc-quote-terms` 那条覆盖里，只作用报价单）。
     依据是 1.66.0 他看过成品后的 *"带有灰度的背景，将其高度减少一些，当前过于高了"*：
     上下 padding 由 **4px → 3px**（与 Items 表头 / 档位行同一个数 ⇒ 全表三处 6.19mm）。
     ⚠️ 基础规则 `.doc-term-table td { padding: 4px 8px }` 没有 `!important`，
        这里带上就能赢（(0,2,2) > (0,1,1)），**不靠书写顺序**。
     🔴 1.74.0-DocNameRev0（Wolfgang 2026-10-15 20:2x，**带截图**）——
        他：*"Terms 的背景阴影高度也缩到和 item 表格标题栏一样高"*（并明确 **不加黑线**）。
        实测结论：h1 与灰条**名义上早就是同一个数**（上下各 3px），但两边的
        **行盒来源不同**（`th` 与 `td` 各自继承），只钉 padding 钉不住 ⇒ 本批把**四项
        垂直几何一次钉死**，让它**结构上不可能**再漂：
          · `padding` 写成 **shorthand**（`3px 8px`）—— 顺带把基础规则的左右 8px 一并定死；
          · `line-height: 1.45`（与文档正文/表头同一个行盒 = 9pt × 1.45 = 17.4px）；
          · 字号由权威块 `.doc-term-table td { font-size: 9pt !important }` 给，与表头同一个数。
        ⇒ 灰条高 = 3 + 17.4 + 3 = **23.4px（6.19mm）**，与 Items 表头**逐字同一个式子**。
        ⚠️ **表头那边也要钉 `line-height`**（见上面 `.doc-table thead th` 那条）——
           只钉一边等于没钉：这就是"两根柱子各自继承"的老形状。 */
  padding: 3px 8px !important;
  line-height: 1.45 !important;
}

/* 尾部的两个自有块也整块走：银行信息/免税声明不再跨页断成两截 */
.doc-taxnote, .doc-bank { break-inside: avoid; page-break-inside: avoid; }

/* ==========================================================================
   0.52.0-DocDate —— 本批权威块（必须留在 style.css 最后，别在它后面加东西）
   --------------------------------------------------------------------------
   #3 · 合计脚：Total Sum 下方一条「与内容等长」的实线
   #2 · 编辑器 item 表末行的 Freight（与 A4 同构）
   ========================================================================== */

/* ---- #3 · Total Sum 下方的实线 ----------------------------------------
   Wolfgang 2026-09-17：「在 Total Sum 下面增加一条实线，长度为与 amount VAT
   total sum 内容等长」。所以 doc_items_foot() 把 Total Sum 那一行做成**一格**
   （colspan = 全宽），下划线画在内层的 inline-block 上 —— 线的长度就是
   「Total Sum   583,10 €」这段内容的宽度，而不是整张表的宽度；右边缘仍与上面
   的金额列对齐（同一个 td padding）。
   ⚠️ VAT 行上下那两条线在 0.51 已经去掉（见上面的 doc-sum-vat / doc-sum-total
   规则），本块不重复声明；这里只**新增** Total Sum 下面这条。 */
.doc-table tfoot tr.doc-sum-total > td.doc-foot-cell { text-align: right; }
.doc-sum-line {
  display: inline-block;
  border-bottom: 1px solid var(--doc-line);
  padding-bottom: 1px;
}
.doc-sum-line .doc-foot-label { padding-right: 10px; }

/* ---- 1.25.0-ScopeFix：Proforma 的「已收预付款」两行 ----------------------
   `Less: advance already received`（普通行，与 VAT 行同构）+ `Amount due`
   （与 Total Sum 同构：那一格带下划线、右对齐）。
   ⚠️ 与 `doc-sum-vat` / `doc-sum-total` 同一条规矩：**上边框一律 0**（0.51 定的），
      否则合计区里会多出两条线。 */
.doc-table tfoot tr.doc-sum-less td,
.doc-table tfoot tr.doc-sum-due td { border-top: 0 !important; }
.doc-table tfoot tr.doc-sum-due > td.doc-foot-cell { text-align: right; }

/* ---- #2 · 编辑器 item 表末行的 Freight ----------------------------------
   编辑器与 A4 同构：Freight 是 item 体的最后一行、占满整行。
   「有金额时不能许隐藏」的**规则**在服务端（doc_freight_settings_from_post()），
   勾选框在金额是数字时被锁（assets/doc-edit.js → kbpFreightSync）；这里只给
   「被锁」一个看得见的提示。 */
.item-freight-cell {
  background: var(--paper);
  border-top: 1px solid var(--line);
}
/* 1.51.0-ItemOrder（Wolfgang 2026-10-12）—— 标签缩成 "Freight"，字重 **700**：
   与明细条目**一样粗**（他的原话 *"字体加粗与其他条目一样"*）。原来是 600。 */
/* 1.54.0-FreightLabel（需求单⑨ Part 2）—— 这一格从"死文本"改成**输入框**
   （Freight 行的标签文字可按单改：`Freight with insurance` …）。
   ⚠️ **必须给宽度上限**：全局那条 `input[type=text] { width:100% }` 会把它拉满整格，
      把金额框挤到下一行（金额框自己也只有 `.item-freight-amount { max-width:170px }`）。
   字重 700 保留 —— 与明细条目**一样粗**（0.27.0 把 item 内容压成 400，这一格是例外）。 */
.item-freight-label { font-weight: 700; margin-right: 10px; max-width: 150px; }
.item-freight-amount { max-width: 170px; }
.item-freight-show {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  font-weight: 400;
  color: var(--ink-soft);
}
.item-freight .field-hint { margin: 6px 0 0; }
.item-freight-forced .item-freight-show { color: var(--ink); }

/* ---- 1.51.0-ItemOrder · 明细行的「上移 / 下移」按钮 ------------------------
   挨着 ✕ 放在同一格（**不加列** —— A4 的表头是六个编辑器共用的，加一列要动两处）。
   刻意**不用 `.btn-del`**：那是红色的"删除"语义，箭头照抄它会被误读成危险操作。
   禁用态（首行的 ▲、末行的 ▼）由 assets/doc-edit.js → kbpSyncItemMove() 统一维护。 */
.kbp-mv {
  padding: 2px 6px;
  margin-left: 2px;
  font-size: 10px;
  line-height: 1.2;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
.kbp-mv:hover:not([disabled]) { color: var(--ink); border-color: var(--ink-soft); }
.kbp-mv[disabled] { opacity: .3; cursor: default; }

/* ============================================================================
   0.55.0-PartnerIA — 列表分页条（唯一实现：lib/ui.php → kbp_pager_html()）

   形状照抄 Dashboard 已有的 .sort-link 表头那套：排序链接在表头、分页条在表尾。
   ⚠️ 只有一页时也要印「N in total」—— 否则用户不知道后面还有没有。
   ============================================================================ */
.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 14px;
  font-size: 13px;
  color: var(--ink-soft);
}
.pager .btn-sm { padding: 4px 12px; }
.pager-off { opacity: .45; pointer-events: none; }
.pager-pos b { color: var(--ink); font-weight: 600; }
.pager-total { margin-left: auto; }

/* ---- 0.55.0-PartnerIA · 客户页的分层视图切换条 ----------------------------
   服务端 query 参数 view=...，**不用 JS**：可深链、可被回归直接断言。
   形状照抄 documents-center.php 的 By Category / Timeline 那套（同样两个 <a>）。 */
.view-tabs {
  display: flex;
  gap: 2px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 4px 0 18px;
}
.view-tab {
  font-size: 13px;
  padding: 8px 12px;
  color: var(--ink-soft);
  border-bottom: 2px solid transparent;
}
.view-tab:hover { color: var(--ink); }
.view-tab-active { color: var(--ink); font-weight: 600; border-bottom-color: var(--ink); }

/* ---- 0.56.0-PartnerTabs · 列表行里的动作图标 + 编号列不换行 ------------------
   Wolfgang 2026-09-21: "找三个好看的小图标来替代以节省空间；列表的编号列调整到
   使编号能不换行完整显示。" 形状见 lib/ui.php → kbp_icon_link()。
   ⚠️ 颜色**一律走变量**：本文件在 `html[data-theme="dark"]`（:1960 起）里重定义了
      --card / --line / --brand / --brand-tint，写死颜色在深色主题下会瞎掉。
   ⚠️ 行内只放图标、不放文字（页头的大按钮仍是文字，那里有横向空间）。 */
.ico-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  color: var(--brand);
  line-height: 0;
  vertical-align: middle;
}
.ico-btn:hover { background: var(--brand-tint); border-color: var(--brand); }
.ico-btn + .ico-btn { margin-left: 4px; }

/* 编号列不换行：单号里的**连字符**（QT-26SE21001 / PA-26AU001）就是换行机会，
   列一被挤窄就从那里折断成两行。nowrap 让这一格的 min-content = 整串，列宽自动够。 */
.cell-no { white-space: nowrap; }

/* ============================================================================
   0.57.0-A4Split — A4 分页三处修正
   全部经**真实打印 PDF + 文本坐标**验证（探针页 = 直接调 pi_render_html + 只加
   一段 <style>，与真实打印页坐标逐条一致；判据见 KBP_改进计划 的 0.57 条目）。

   ① Term 表（Lead time / Delivery term / Payment term）整表不跨页
      Paged.js 的 avoid 只认 **tbody / thead / td**（`tr` 与任意块级都不读 ——
      `avoidBreakInside()` 在这份构建里是死代码）。原来只有 `.doc-term-table tr`
      上有 avoid，所以分页落点会跑进**行的中间**：标签格留在上一页（或整格消失），
      值那一格当"续行第一格"排到**左栏**（Wolfgang 2026-09-21 报的 PI 症状）。
   ② 续页在分隔线下留一行 —— 选择器修正见上文 `--doc-break-gap` 与第 3 节。
   ③ 内容框下沿让开页脚：页脚盒**实测 27.5mm 高、顶边在 735.6pt**，而
      `padding-bottom: 27mm` 把内容框下沿放在 765.3pt → 内容能压进页脚带 **30pt**。
      842 − 735.6 = 106.4pt = 37.5mm，取 38mm（留 0.5mm 余量）。
      ⚠️ 页脚自身之所以是 27.5mm 而不是注释里写的 16mm：它的每行之间多了一个空行
      （`nl2br()` 与 `white-space: pre-line` 重复生效 → 6 行占 11 个行盒）。

   ⚠️ **0.66.0-ListUI 更新了这一条**（把上面那个"将来若修掉"兑现了）：
      空行的根因已在 `.doc-footer-col` 上修掉（移除 `white-space: pre-line`，断行只由
      `nl2br()` 提供）→ 页脚 6 行只占 **6 个行盒**：
        · 页脚内容 71.54pt → **39.02pt**；整块 76.54pt → **44.02pt**（= 16mm 设计意图）
        · 页脚底固定在 `bottom: 10mm`（813.5pt）→ 盒顶 735.6pt → **≈768.5pt**
      于是 `padding-bottom` 由 38mm **回到 27mm**：内容框下沿 765.4pt，净空 ≈3.0pt
      （比原来那 1.4pt 余量还宽松一点），**每页正文多回 11mm**。
      ⚠️ 改这个数字前请重新量（探针页 + `getBoundingClientRect`，或打印 PDF 读坐标），
      别凭感觉调 —— 它同时被 `tests/m31.php` 与 `guard_a4_page_geometry` 钉着。

   ⚠️ **0.70.0 批（DocPolish）又动了一次这一对数字**（Wolfgang：*"页脚公司信息的行间距
      增大一点，太挤了"* → 取 **1.25**）：
        · `.doc-footer-col` 行距 1 → **1.25** → 每行 6.5pt → 8.125pt，6 行：
          内容 39.02pt → **48.73pt**，整块 44.02pt → **53.72pt**（≈19mm）
        · 页脚底仍钉在 `bottom: 10mm` → 盒顶 ≈768.5pt → **≈758.8pt**
      于是 `padding-bottom` 由 27mm → **31mm**：内容框下沿 754.1pt，净空 ≈4.7pt
      （比 0.66 的 ≈3.0pt 还宽一点），代价是**每页正文少 4mm**（0.66 拿回的 11mm 里让回 4mm）。
      实测来源：确定性探针（真实文档标记 + 真实 style.css − Paged.js），三档 A/B 各量一次，
      证据存在 `_shots/footer-0.70.txt` 与 `_shots/q44-*.html`。
   ============================================================================ */

/* ---- ① 整表不跨页 ------------------------------------------------------
   ⚠️ 1.05.0-TermsFrame（2026-09-29）把这批原有的两条**首行上边线**删掉了
   （`tbody tr:first-child > td { border-top: … }`）—— 用户定"灰分隔线也一起去掉"。
   本批"让灰线跟着表走"的意图随之作废（没有线要跟了）；分页承诺由 `tbody` 那条
   `break-inside: avoid` 继续承担，**不许**因为删线把这条一起删。 */
.doc-term-table tbody { break-inside: avoid; page-break-inside: avoid; }
.doc-term { border-top: none !important; }   /* 原 border-top 会孤零零留在上一页 */

/* ---- ③ 底部让开页脚（实测见上：0.70 页脚行距 1.25，27mm → 31mm）---- */
.pagedjs_page_content { padding-bottom: 31mm !important; }

/* ============================================================================
   0.58.0-Terms — Terms 区块在六张单据上统一（Wolfgang 2026-09-21）

   目标形状（Quotation / PI / Order / Invoice / PL / PO 一致）：
     [灰底加粗] Terms                      ← 2.1 标题行
     Delivery Terms: ｜ CPT xxx, Germany   ← 2.3 左标签加粗、右值多行
     Payment Terms:  ｜ …（多行）
     Bank Info.:     ｜ …（多行，**只有 Quotation 与 PI**）

   ① 去网格线（2.2「将表格改为文字描述」）：原来 `.doc-term-table td` 带
      1px 实线（L1136），整块读起来像表格。
      ⚠️ 0.58 当年在这里**重新声明**了 0.57 那条首行上边线（"否则 border:none
      会把 Terms 与 item 表之间的分隔线一起杀掉"）。**1.05.0-TermsFrame
      （Wolfgang 2026-09-29）把那条线也去掉了**（他定"灰分隔线一起去掉"），
      所以这一段现在只剩 `border: none` —— 那两处重声明已删。
   ② 标签格不再灰底（仍 700 加粗）：灰底属于"表格"外观，与 2.2 冲突；整块只留
      标题行一处灰底（2.1 明确要求）。
   ③ Ref 行去粗（0.58 的独立修复）：`.doc-table tbody td:nth-child(1)` 本意是
      **序号列加粗**，而 Ref 行是 `<td colspan="8">` —— 那一格**就是首格**，
      于是整行从 td 继承 700（`Ref:` 那个 span 自带 400 所以幸存 → 症状是
      "值加粗、标签正常"）。⚠️ **不能直接删那条规则**：`cell-pos`（0.44 权威块）
      只在报价单 A4 用，其余五张单据的序号列全靠它 → 按**行类**排除。
      0.70 批（DocPolish） 又给这条排除加了 `:not(.doc-tier)`（档位行的 Qty 被误加粗）。
   ④ 标签与值一律**左上对齐**（0.70 批（DocPolish），Wolfgang 2026-09-23：
      *"Terms的Bank info标题是上下居中，改成左上"*）：
      原来的 `.doc-term-table td`（L1152）只设了 padding/border，**没有** vertical-align
      → UA 样式把 `middle` 传给表格格，单行的粗体标签（`Bank Info.:`）对着 4 行的值
      **垂直居中**，"浮在中间"。旁证：六张单据的 item 表一直被显式钉成
      `vertical-align: top !important`（0.44 块 / 0.47 块），只有 Terms 表漏了。
      水平方向本来就是左的（`.doc-term, .doc-notes { text-align: left }` 继承到格），
      这里**显式写下来** —— 能被断言钉住的东西才不会漂。
      ⚠️ 刻意**并进 ①② 这两条既有的规则**，不新开一个尾块：文档版式规则必须留在
      权威块里（m26 会拦"权威块之后又冒出一个文档选择器"），而且 m32 断言的是
      "**最后一条** `.doc-term-table td` 规则仍然 border:none" —— 另开一条会把靶子撞歪。
   ============================================================================ */

/* ---- ① 去网格线；④ 左上对齐 -------------------------------------------------
   ⚠️ 1.05.0-TermsFrame 删掉了紧随其后的那条 `tbody tr:first-child > td
   { border-top: … }`（灰分隔线，用户定"一起去掉"）。m32 的原靶子是"最后一条
   first-child 规则仍带 border-top"——**那条承诺被用户撤了**，断言跟着改成
   "该选择器在剥注释后的 CSS 里已不存在"。 */
.doc-term-table td { border: none !important; vertical-align: top !important; text-align: left !important; }

/* ---- 1.05.0-TermsFrame ①（Wolfgang 2026-09-29）—— 去掉**表格自己的外框** ----
   *"CI的A4渲染页，Terms窗口去除外框线"*（定案：**全部单据一起改**）。
   .doc-term-table td 的线 0.58 就杀了，可 `.doc-term-table` **自己的**
   `border: 1px solid #111`（本文件最上面那条 M3.3.3 规则）一直留着 ——
   于是整块四周仍是一条 1px 黑框。近景截图（`_shots/ci-terms-close.png`）一眼可见：
   它就是用户说的"外框线"。实测值见 `_shots/`（`_measure_terms.py`）。
   ⚠️ 与 `.doc-term-table td` 不冲突：一个是**表格**的框、一个是**格子**的线，
   两条都 !important 但对象不同，特异性不参与比较（写在这里只为读起来是同一件事）。 */
.doc-term-table { border: none !important; }

/* ---- ② 标签格去灰底（加粗保持不变：L2720 的 .doc-term-label 700）；④ 同样左对齐 ---- */
.doc-term-table .doc-term-label { background: none !important; text-align: left !important; }

/* ---- ③ 序号列**不加粗**（1.73.0 起六张一律；1.69 时只对报价单） ----
   ⚠️ 0.70 批（DocPolish） 加的那个 `:not(.doc-tier)`（Qty 列被莫名加粗的回归）**继续保留**：
   报价单是多档报价，一个产品的**第 2 档起**（`tr.doc-tier`）没有序号与产品格
   （它们在首行 rowspan 掉了）→ 那一行的**第一格就是 Qty**。0.57 的
   `.doc-table tbody > tr.doc-tier > td:first-child { font-weight: 400 !important }`
   特异性 (0,3,3) **低过**这里的 (0,6,3) ⇒ 不排除的话档位行的 Qty 会被重新加粗。
   ⚠️ `:not(.cell-pos)` **保留**（1.69 加的）：它已经不再"必需"（本条自己的值就是 400），
      但它把"报价单那个带类的序号格"明确排除在本条之外 ⇒ 读起来是两条互不重叠的规则，
      而不是靠书写顺序决胜。**它不是冗余装饰，别顺手删**。
   🔴 **1.73.0-DocTypeScale**：`font-weight` 由 **700 → 400** —— 口径从「只有报价单去粗」
      改成「**六张一致**」（任务书 §4-2 已定项 10）。**这是本批的真正动作**：
      其余五张纸的序号格（`doc_item_row()` 输出的裸 `<td>`）全靠这一条，
      改它 = 六张的序号列同时去粗。
   ⚠️ **兄弟规则**：上面权威块的 `.doc-table tbody td:nth-child(1)` 也必须是 400 ——
      两条都 !important 时比的是特异性，(0,6,3) 赢 (0,2,2)，**只改一条等于没改**。 */
.doc-table tbody tr:not(.doc-items-ref):not(.doc-items-gap):not(.doc-tier) > td:nth-child(1):not(.cell-pos) {
  font-weight: 400 !important;
}
.doc-table tbody tr.doc-items-ref > td { font-weight: 400 !important; }
.doc-table tbody tr.doc-items-gap > td { font-weight: 400 !important; }

/* ---- ⑤ Freight 行的**编号格**一起去粗（1.73.0 起六张；1.69 时只作用报价单） ----
   🔴 KBP7-⑮（Wolfgang 2026-10-15 凌晨追加）—— 他先要 *"item表格中的序号，去除加粗"*，
   看过成品后再补一句：*"所有编号都要去除加粗，包括freight的编号"*。
   Freight 那一行的编号是 `<td class="cell-num">`（由共用 helper `doc_freight_row()` 输出），
   它**没有** `.cell-pos` 类 ⇒ 吃的是上面那条按行类排除的规则。
   ⇒ 这一条把类数堆到 **7** 对上面那条 **(0,6,3)** 形成**严格压制**（不靠"谁写在后面"——
      靠顺序赢的写法，将来有人搬动文件就会**静默**失效）。
      `.doc-table`(1) ＋ `tr.doc-freight`(1) ＋ 三段 `:not()`(3)
      ＋ `.cell-num`(1) ＋ `:nth-child(1)`(1) = **7 个类** vs 上面那条的 6 个。
   🔴 **1.73.0-DocTypeScale**：作用域由 `.doc-quotation-table` 改成 **`.doc-table`**（六张一致，
      任务书 §4-2 已定项 10）。**类数仍然是 7** —— 作用域类换的是"哪张纸"，不是"几个类"，
      严格压制的性质**没变**（换成 `.doc-table` 之后它照样比上面那条高一级）。
      此刻它的取值与上面那条**相同**（都是 400）⇒ 属于**保险**：即使将来有人把上面那条
      改回 700，这一行也仍然是 400（反向验证里有一条专测它）。
   🔵 只动**第一格**（编号）——见下面 ⑥ 那条：**标签**也去了粗（六张），
      金额格本来就不粗 ⇒ 最终这一行**三处全不粗**。 */
.doc-table tbody tr.doc-freight:not(.doc-items-ref):not(.doc-items-gap):not(.doc-tier) > td:nth-child(1).cell-num {
  font-weight: 400 !important;
}

/* ---- ⑥ Freight 行的**标签**也去粗（1.73.0 起六张；1.69 时只作用报价单） ----
   🔴 KBP7-⑮（Wolfgang 2026-10-15 凌晨，他看过成品后再追加）—— 他：*"我发现仍有Freight的
   内容仍是加粗的，这个需要再快速修一次"*，并确认 *"是要改纸面显示的freight"*。
   🔴 **1.73.0-DocTypeScale** —— 他 11:5x 拍板（已定项 10）：*"批3里Freight标签去粗，
      作用于所有六张表"* ⇒ 作用域由 `.doc-quotation-table` 改成 **`.doc-table`**。
   🔵 **为什么不去就地改共用那条权威块**（`.doc-freight .doc-freight-label` 的 700）：
      它被**三处判据**钉着（打包守卫的字面串 / `m128` 那条字面串（还钉了旁边 600 那条的先后）/
      `m149` 与守卫的反向）⇒ 就地改要**连改 4 处**并让那些判据失去意义；
      这里换作用域只需**一条规则**，而且**特异性天然赢**：
      `.doc-table`(1) ＋ `.doc-freight`(1) ＋ `.doc-freight-label`(1) = **(0,3,0)**
      压得住共用的 **(0,2,0)** —— 比的是特异性，**不靠书写顺序**。
      ⚠️ 那条共用的 700 **仍然在文件里**（有意保留：它是"与明细条目一样加粗"的旧口径），
         现在被本条的 400 全站覆盖 ⇒ **改它之前先读这一段**。
   🔵 金额格本来就不粗（全站对 `.cell-num` 零 font-weight 规则）⇒ 改完这一行**三处全不粗**。 */
.doc-table .doc-freight .doc-freight-label {
  font-weight: 400 !important;
}

/* ============================================================================
   0.63.0-Void — 作废单据的纸面标记（Wolfgang 2026-09-22）

   已签发的单据不许删除（号段是 MAX(序号)+1，删行会让下一张重号），只能
   **正式作废**。号、版本、token 与冻结的 HTML 一个字节都不动 —— 所以重印出来的
   仍是当初那张纸，但顶上多一个红框 VOID 章。

   ⚠️ 章由 `lib/ui.php` 的 `doc_snap_print_html()` 在**打印/公开查看那一刻**插进
   `<div class="doc">` 里面（`.doc-head` 之前）→ 只出现在第一页。**不写进快照**：
   冻结的 HTML 是不许改的。
   ============================================================================ */

.doc-void-stamp {
  border: 1.2pt solid #b02a37;
  color: #b02a37;
  padding: 3mm 4mm;
  margin: 0 0 6mm;
  text-align: center;
}
.doc-void-stamp .doc-void-mark {
  display: block;
  font-size: 22pt;
  font-weight: 700;
  letter-spacing: 0.4em;
  line-height: 1.1;
}
.doc-void-stamp .doc-void-note {
  display: block;
  font-size: 8pt;
  margin-top: 1.5mm;
}

/* 1.44.0-Backfill（需求单㉛）—— 纸面上的「号已订正」提示。
   改号后**冻结件一个字不动**，只在打印那一刻插这一行（与上面的 VOID 章同一手法、
   同一个插入点 ⇒ 只出现在第一页）。
   ⚠️ 放在这里不是随手：`tests/m26.php` 钉着"列表 UI 权威块之后不许出现 `.doc-*` 选择器"
      —— 挂在文件末尾会让 **0.96.0-ColSep** 那一块被算成"夹带了文档选择器"（实测当场红）。
      它属于**文档**块（与 VOID 章同族），所以跟着 VOID 章住。
   ⚠️ 也**别复用屏幕上的 `.presence-*` 那一族** —— 两者读者不同（一个印在纸上）。 */
.doc-numfix {
  margin: 0 0 6mm;
  padding: 2mm 3mm;
  border: 1.2pt solid var(--warn);
  color: var(--warn);
  font-size: 8pt;
}
.doc-numfix b { font-weight: 700; }

/* ============================================================
   首页版式：KBP7-② / KBP7-③（报价单）→ **1.70.0-DocHeadV2 起六张纸共用**

   作用域 = `doc-head-quote`（**旧名**）／ `doc-head-v2`（**1.70.0 起的新名**）·
            `doc-quote-title`（旧名）／ `doc-title-v2`（新名）· `doc-draft-mark` ·
            以及 `doc-quote-top`（**批 2 才改名**，本批一字未动）。
   🔴🔴 **双选择器别名（新旧名并列）不是冗余 —— 删不得**：
      `doc_snapshots.html` 存的是**冻死的一份完整 HTML**；1.58.0–1.69 签发的**报价单**快照里
      元素带的是**旧名**，而这一代新变体页眉**没有 `doc-head-label`** ⇒ `doc_snap_fix_chrome()`
      第一行就 `return` ⇒ **自愈链救不了**。⇒ 旧名一旦从 CSS 里删掉，那些老快照立刻**掉样式**
      （页眉结构散开、logo 不再贴右、地址与页码不再同行）。**旧名永久保留。**
   🔴🔴 **KBP7-⑤ 修正（Wolfgang 2026-10-13 深夜，带截图）**：右栏那 7 条规则**原来写的是
      `.doc-head-quote ~ .doc-topinfo …`**（兄弟选择器）—— **它在真实分页下全是死的**。
      实测依据（`doc-paged.js` 源码 + 分页后 DOM）：
        · `doc-paged.js` 建 `.doc-flow` 时**把 `.doc-head` 排除在外**（`if (child === headTpl) return`），
          分页后把页眉克隆**追加到 `pagedjs_page`** 上、**并且摘掉 `doc-head` 类**；
        · 真实嵌套：`pagedjs_page > pagedjs_sheet > pagedjs_pagebox > pagedjs_area > pagedjs_page_content`
          （`.doc-flow` 被 paged.js **摊平**，它的孩子直接成为 `page_content` 的孩子）。
      ⇒ 页眉与 `.doc-topinfo` **分居两棵子树，永远不是兄弟** ⇒ 去掉外框 / 表格定宽 / 左列定宽
        / 首行 padding 全部**没生效**（探针实测：`border-width=1px`、表宽 82.5mm ≠ 58mm、
        标签列 34.4mm ≠ 20mm、首行 padding-top=2px）。
      ⇒ 正解：**作用域只许挂在"会被克隆进 `.doc-flow` 的元素自己身上"** —— 这里就是 `.doc-topinfo`
        （`doc_top_grid()` 新增的 `$extraClass` 参数把它变成 `.doc-topinfo.doc-quote-top`）。
   ⚠️ 页眉那一族类**仍要用在页眉自己内部**（品牌块 / 地址行 / `.doc-page-num` / `.doc-draft-mark`）
      —— 那些元素就在页眉子树里，没问题。
   ⚠️ **故意不带 `.doc ` 前缀** —— `doc-paged.js` 把 `.doc` 的 children 克隆进
      `.doc-flow`，挂在 `.doc` 上的类分页后会失效（1.31.1-QuoteLine-2 踩过这个形状）。
   🔴 **要压住别的规则的那几条必须带 `!important`**：本文件后面还有一代"权威块"
      （`.doc-infobox { border: 0.5pt solid #111 !important }`、`td { padding: 2px 6px !important }`）
      ⇒ 不带 `!important` 的规则**即使特异性更高也压不住**（第一版真栽过：探针当场读出
      `border-width = 1px ✘ 还在`）。
   ============================================================ */

/* ---------- ① 右栏信息表：去掉外框（**1.71.0-ContactPanel 起六张纸共用**） ---------- */
.doc-quote-top .doc-infobox, .doc-top-v2 .doc-infobox { border: 0 !important; }

/* ---------- ② logo 锁合：公司名紧贴 logo（**肉眼 3mm**），当作 logo 的一部分 ----------
   他："减少 logo 与公司名称的间距，公司名称上移，两者间隔 3mm。" 原来那 4.4mm 由三项叠成：
     ① `.doc-logo-link` 是**行内元素** ⇒ `<img>` 落在文字基线上，下方留出 ~1.33mm 降部空间；
     ② `.doc-head-q-name { margin-top: 1mm }`；
     ③ logo PNG 自带的 **0.90mm 透明底边**（300×67 的原图，底部 5px 透明）。
   改法：链接与图片都 `display:block`（掐掉行内基线余量）、行高收到 1.2、`margin-top: 1.6mm`
   ⇒ 1.6 + 字面余量 0.72 + 透明底边 0.90 = **3.0mm**（逐像素实测 3.00mm）。
   `position: relative` 是给 ④ 的 DRAFT 浮标做定位参照系的。 */
.doc-head.doc-head-quote, .doc-head.doc-head-v2 { display: block; padding-bottom: 2mm; position: relative; }
.doc-head-quote .doc-head-q-brand, .doc-head-v2 .doc-head-v2-brand { text-align: right; white-space: nowrap; }
.doc-head-quote .doc-head-q-brand .doc-logo-link, .doc-head-v2 .doc-head-v2-brand .doc-logo-link { display: block; line-height: 0; }
.doc-head-quote .doc-head-q-brand .doc-logo-img, .doc-head-v2 .doc-head-v2-brand .doc-logo-img { display: block; margin-left: auto; height: 12mm; }
.doc-head-quote .doc-head-q-name, .doc-head-v2 .doc-head-v2-name { font-size: 9.5pt; font-weight: 700; line-height: 1.2; margin-top: 1.6mm; }

/* ---------- ③ 地址（含名称）与右侧 Page 1 of 1 **同一行**（按基线对齐） ---------- */
.doc-head-quote .doc-head-q-row, .doc-head-v2 .doc-head-v2-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8mm;
  margin-top: 2.5mm;
}
/* KBP7-⑦（Wolfgang 2026-10-14）—— 左上角公司地址 **9pt → 7pt**（他：*"字体调整为7pt"*）。
   `.doc-head-q-co` 只被页眉的 **quotation 分支**输出 ⇒ 改这一条不碰其余五张纸。 */
.doc-head-quote .doc-head-q-co, .doc-head-v2 .doc-head-v2-co { font-size: 7pt; line-height: 1.4; color: var(--doc-ink); }
/* ⚠️ 权威块（`.doc-head .doc-page-num`）写的是 `margin: 0.8mm 0 0 !important`（它是"分隔线之下的
   独立一行"那代版式的残留）⇒ 这里想让它**贴回同一行**必须带 `!important` 才压得住。 */
.doc-head-quote .doc-page-num, .doc-head-v2 .doc-page-num { font-size: 9pt; line-height: 1.4; text-align: right; margin: 0 !important; }

/* ---------- ④ 单据标题：移出右栏 ⇒ 独占一行居中、**不变色** ----------
   他（2026-10-13）："将 Quotation 移出表格，在地址下独占一行且居中，这样无论有没有
   draft-not issued 提示，都不挤占页面其他元素的排版" + "且不变色"。
   1.70.0-DocHeadV2 —— 六张纸共用这一条 ⇒ 类名 `doc-quote-title` **改名**为 `doc-title-v2`，
   旧名留在选择器里当**别名**（1.58.0–1.69 签发的报价单冻结快照里存的是旧名）。
   ⚠️ 标题**不再吃**「草稿标题变红」那条规则 —— 红色只留给 DRAFT 浮标（六张统一）。
      `doc-title-draft` / `doc-draft-tag` 两条老规则**保留**（m22 的颜色白名单钉着它们），
      但 1.70.0 起**没有任何出口再输出它们**（休眠）。
   🔴 **注释里不许写带点的选择器**（尤其在本块这种"紧跟着一条带 color 的规则"的位置）：
      本项目有判据在 CSS 原文上跑 `\.doc-xxx[^{]*\{[^}]*color` 这类正则（m22 那条
      「这个块有没有覆盖它」），注释里写一个带点的类名会被它当成"又冒出一条规则" ⇒ 当场假红。
      （本文件 2611 行那条老规则才是它的唯一实现。） */
.doc-quote-title, .doc-title-v2 {
  font-size: 17pt;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  text-align: center;
  margin: 4mm 0 5mm;
  padding: 0;
  color: var(--doc-ink);
}

/* ---------- ④附 DRAFT 标记：**绝对定位** ⇒ 存在与否都不动排版 ----------
   他："Draft-Not Issued 作为水印漂浮在纸面上或放在页眉。目的是无论它存在还是不存在，
   都不会影响页面排版变动" ⇒ 他 20:12 拍板**按变体 A**（页眉左侧空白处的淡红浮标）。
   🔴 它住在 `.doc-head` 里 ⇒ `doc-paged.js` **每页克隆** ⇒ 第 2 页起也有；
      又因为绝对定位 ⇒ 对页眉高度**零影响**（实测「有→无」时标题/两栏/正文位移全 0.00mm、
      页面总高 297.00 → 297.00mm）。 */
.doc-draft-mark {
  position: absolute;
  left: 0;
  top: 0;
  width: 110mm;
  font-size: 15pt;
  font-weight: 700;
  letter-spacing: .22em;
  color: #b91c1c;
  opacity: .34;
  transform: rotate(-8deg);
  transform-origin: left top;
  text-align: center;
  pointer-events: none;
}

/* ---------- ⑤ 左栏首行（客户公司名）与右栏 `Number:` 起始于同一条水平线 ----------
   权威块给 `.doc-infobox td` 的是 `padding: 2px 6px !important` ⇒ 右栏首行比左栏低 ≈0.8mm。
   只给**第一行**去掉上内边距，行间节奏一字不动（实测墨迹顶差 +0.00mm）。 */
.doc-quote-top, .doc-top-v2 { align-items: start; }
.doc-quote-top .doc-infobox tr:first-child td, .doc-top-v2 .doc-infobox tr:first-child td { padding-top: 0 !important; }

/* ---------- ⑥ 右栏信息表：左列固定 · 表格定宽贴右 · **长内容自动换行** ----------
   他 20:26 + 20:41 两轮：
     · 20:26：*"表格保持左列为固定宽度，右栏为活动宽度…然后整个表格贴右边"*
     · 20:41：*"右栏的宽度**不是**由 sophia@kuemago.com 决定的 —— 客户邮箱常常很长，
              遇到这种情况**自动换行**"*
   🔴 20:26 那版用"表宽 = 最长内容"（自动宽表格）**是错的**：一封长邮件会把整表撑开、
      还把表格往左推。正解 = **表格定宽** + 右列吃掉剩余宽度 + **允许任意处断行**：
     · 表格 **66mm**（= 左列 20mm + 右列 46mm）· 自动外边距靠右 ⇒ 整表贴齐版心右边；
     · 右列宽度**与内容无关**（实测：塞进 65 字邮箱 ⇒ 表宽**不变**、
       行高 5.66 → 19.47mm = **自动换行**、横向溢出 0）；
     · 🔴 `overflow-wrap: anywhere` **必须有** —— 邮箱没有空格，不给它浏览器不会断行，
       会**溢出表格**（这是最容易漏的一条）；
     · `text-align: left` = 右列左对齐（**不是**右对齐，他明确要求）；
     · `border-top/bottom: 0 !important` = **显式**无横线（权威块本来就 `border:none !important`，
       写死是防回潮）。⚠️ 他 20:41 澄清"灰线"指的就是**这张表** ⇒ 实测**一条都没有**
       （用能看见浅灰的阈值 `190 < 均值 < 254` 全页复扫：改前 0 条、改后 0 条）。

   🔴 KBP7-⑥（Wolfgang 2026-10-14 早，带截图）：**58mm 太窄** —— A4 preview 里 Seller 邮箱
      `sophia@kuemago.com` 断成 `…co` + `m`。
      算式（Arial 度量 + CSS 盒模型，不是目测）：每格左右内边距合计 **3.175mm**（权威块给的是
      `2px 6px !important`）⇒ 值列可写宽 = 58 − 20.00（左列）− 3.175 = **34.83mm**，
      而该邮箱 9pt 需 **33.34mm** ⇒ **只剩 1.49mm 余量** ⇒ 浏览器字距一取整就翻面。
      ⚠️ 逐像素反推时值列看起来像 **10pt**（若真是 10pt ⇒ 需 37.04mm、**差 2.2mm 必断**），
         但截图的缩放/抗锯齿噪声大于 9pt 与 10pt 之差 ⇒ 定不下来 —— 所以按"两种假设都要够"选：
         **66mm ⇒ 值列可写 42.8mm**（对 33.3mm 余量 +9.5mm；对 37.0mm 余量 +4.1mm）。
      ⚠️ **62mm 在 10pt 假设下只剩 +0.1mm（等于没有）** ⇒ 没选它；70mm 更宽但右块占版心过大。
      🔵 值列**仍然与内容无关**（定宽 66mm），客户超长邮箱照旧在列内换行 ⇒ 表宽恒定、始终贴右。 */
/* 🔴 KBP7-⑦ —— 信息表改**三列**后重定宽度（他：*"表格总体设计为固定宽度和位置，
   给右列留足够填写空间"*）。66mm → **80mm**：标签列变成"按内容"（最长是 `Quotation No.`），
   还要多出中间那个 `:` 列 ⇒ 值列必须重新算够不够。
   算式（以 10pt 的**保守**假设、权威块 `padding: 2px 6px !important` = 每格左右 3.175mm）：
     标签列 = `Quotation No.` 22.06 + 3.175 + 4(右内边距) = 29.2mm
     `:` 列 = 0.26 + 5(右内边距) = 5.3mm
     ⇒ 值列 = 80 − 29.2 − 5.3 = 45.5mm，可写 **42.3mm**
     ⇒ 对 `sophia@kuemago.com`（10pt 需 37.04mm / 9pt 需 33.34mm）余 **+5.2 / +8.9mm** ✔ */
.doc-quote-top .doc-infobox, .doc-top-v2 .doc-infobox { width: 76mm; margin-left: auto; }
.doc-quote-top .doc-infobox table, .doc-top-v2 .doc-infobox table { width: 100%; }
/* 🔴 KBP7-⑦ —— 三列的**第一列**：按内容收窄 + **左对齐** + **不加粗**。
   ⚠️ 三条都跟下面那条 `.doc-quote-top .doc-infobox td { … text-align: left }` 抢：
      它的特异性是 (0,2,1)，**高于** 这里的 `.doc-quote-top .doc-info-label` (0,2,0)
      ⇒ `text-align` 与 `font-weight` **必须带 `!important`** 才压得住（KBP7-③ 踩过同一个坑）。
   ⚠️ `width: 1px` 是经典的"按内容收窄"（标签本来就 `white-space: nowrap`）；
      不写它的话会吃基础规则里的 `width: 42%`（= 80mm 的 42% = 33.6mm，白占一整块）。
   🔴 KBP7-⑫ —— `padding-right` 从 4mm 改成 **0**：左右两栏的间隙现在**只由中间那一列**承担
      （他：*"中间列宽即是需要的宽度，按4mm即可"*）⇒ 标签这边再留 4mm 就变成 8mm 了。 */
.doc-quote-top .doc-infobox .doc-info-label, .doc-top-v2 .doc-infobox .doc-info-label {
  width: 1px;
  /* 🔴 KBP7-⑮（Wolfgang 2026-10-15 凌晨）—— **右对齐 → 左对齐**
     （他：*"联系人表格的左列改为左对齐"*）。
     ⚠️ 这是**把他⑫ 那批取消掉的那一半要回来**：⑫ 他的原话是 *"左列自己左对齐，
        右列自己左对齐"*，随后一句 *"第8条要求取消"* 把"左对齐"这半撤掉了
        （保留的是：去掉冒号 + 中间留一个空列），标签因此一直右对齐。现在他要回来。
     🔵 `width: 1px`（按内容收窄）与 `padding-right: 0` **都不动** —— 标签列仍然只占
        最长标签的宽度 ⇒ **值列的起点一个像素都不变**，变的只是标签文字贴哪一边
        （标签从表格左缘起排，表格本身仍 `width: 76mm` + `margin-left: auto` 贴右）。
     ⚠️ 必须带 `!important`：下面 `.doc-quote-top .doc-infobox td` 那条 (0,2,1)
        比这里 (0,2,0) 高，不带就压不住（⑦ 那批踩过同一个坑）。 */
  text-align: left !important;
  font-weight: 400 !important;
  padding-right: 0 !important;
}
.doc-quote-top .doc-infobox td, .doc-top-v2 .doc-infobox td,
.doc-quote-top .doc-infobox th, .doc-top-v2 .doc-infobox th {
  border-top: 0 !important;
  border-bottom: 0 !important;
  text-align: left;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* ============================================================================
   KBP7-⑤（Wolfgang 2026-10-13 深夜，带两张截图）—— 报价单的**分页几何**两条修正
   ============================================================================ */

/* ---------- A · 内容区上偏移：新变体页眉（1.70.0 起六张纸共用）比 legacy 高，正文必须整体下移 ----------
   他：*"2 标题侵占到了 page 行"* + *"4 分页后，下一页的顶部需要与 Page 行空一行的距离"*。
   实测（分页后 headless Chrome 取 `getBoundingClientRect()`，单位 mm）：
     · 新变体页眉 `top = --doc-mt (15mm)`，**高 24.6mm ⇒ 底边 39.6mm**（legacy 页眉只有 ~15mm，
       所以权威块给内容区的 `padding-top: 30mm` 对 legacy 够用、对新变体**差了 9.6mm**）；
     · 第 1 页标题实测 `34.0 → 41.7mm` ⇒ **与页眉重叠 5.6mm**（他看到的"QUOTATION 压在地址上"）；
     · 第 2 页起沿用 `30mm + --doc-break-gap(9mm) = 39mm` ⇒ 正文顶到 Page 行，**没有空行**。
   ⇒ 六张纸：第 1 页 40mm（= 页眉底 39.6mm，标题自己的 `margin-top:4mm` 提供那一行空行）、
      第 2 页起 = 40mm + `--doc-blank-line`(4mm) = 44mm（他明确要"空一行"）。
   ⚠️ 选择器靠 `doc-paged.js` 复制到**页面元素**上的 `.kbp-page-v2`（页眉与正文分居两棵子树，
      兄弟选择器认不出"这一页是不是新变体"）。
      1.70.0 起它叫 `kbp-page-v2`（运行时加的类、不进冻结 HTML ⇒ 改名零风险）；
      `doc-paged.js` 那边**同时认旧页眉类名**，所以 1.58.0–1.69 的老快照照样拿到这一条。
   ⚠️ `:not([data-page-number="1"])` 那条旧规则（`30mm + --doc-break-gap`）特异性与下面这条相等
      ⇒ **必须写在它后面**（本块在 3485 行之后）才能赢。 */
.pagedjs_page.kbp-page-v2 .pagedjs_page_content {
  padding-top: 40mm !important;
  /* ⚠️ 元素高必须**跟着各自的 padding 一起算**：漏了这一条，元素高会是
     `297 − 30 − 31 + (40 − 30) = 307mm` ⇒ paged.js 又高估 10mm（本批真踩）。 */
  height: calc(100% - 40mm - 31mm) !important;
}
.pagedjs_page.kbp-page-v2:not([data-page-number="1"]) .pagedjs_page_content {
  padding-top: calc(40mm + var(--doc-blank-line)) !important;
  height: calc(100% - (40mm + var(--doc-blank-line)) - 31mm) !important;
}

/* ---------- B · 🔴 页数少算一页 ⇒ 尾部内容被裁掉（Note 消失的真根因） ----------
   他：*"3 当 A4 文档接近底部时，Note 信息未能被显示出来。尝试增加多个 item 后出现第二页时，
          Note 信息才能再次被显示到第二页"*。
   复现（真实分页路径，逐条量）：12 items → 内容到 259.8mm，页内、正常；
     14 items → 内容到 ~275mm（超出可用底 266mm 约 9mm）⇒ `.doc-notes` 被排到 **x=1737px**
     的**第二个分栏**里，而 `--pagedjs-page-count` 仍是 **1** ⇒ 那一栏没有页面盒 ⇒
     被 `.pagedjs_page { overflow: hidden }` **整块裁掉**（页面上什么都没有）。
   根因：`.pagedjs_page_content` 是 `box-sizing: border-box` ⇒ 元素**高 297mm**，
     而真实可用高只有 `297 − 30 − 31 = 236mm`。**paged.js 按 297mm 算页数**（= `ceil(内容高/297)`）,
     浏览器却按 236mm 分栏 ⇒ 内容落在 (236mm, 297mm] 这一段时，paged.js 少算一页 ⇒ 裁掉。
   修法：把元素高**显式设成真实可用高**（`content-box` + `calc(100% − 30mm − 31mm)`），
     总高度仍是 297mm（布局一字不变），只是 paged.js 看到的数字对了。
   实测 A/B（同一次 headless 渲染、同虚拟时间预算）：
     · 12 items：改前 1 页 → 改后 **1 页**（本就正确，无回归）
     · 18 items：改前 **1 页（Note 被裁）** → 改后 **2 页** ✔
     · 21 items：改前 **1 页（Note 被裁）** → 改后 **2 页** ✔ */
.pagedjs_page_content {
  box-sizing: content-box !important;
  /* ⚠️ `content-box` 会让 paged.js 的 `width:100%` **再加上左右 padding** ⇒ 宽 907px > 页宽 794px
     （第一版真踩：右栏信息表直接捅出纸面）。⇒ 宽度必须自己减掉左右页边距。 */
  width: calc(100% - 2 * var(--doc-mx)) !important;
  height: calc(100% - 30mm - 31mm) !important;
}

/* ---------- B′ · 让 A 块那 10mm 在**分页之前**就生效（1.76.1-PageBoxFix，2026-10-16） ----------
   他报障：*"报价无法顺利显示第二页"* —— 截图里纸面 Table Note 之后空一大段、**Terms / Notes
   整块不见**，页码仍印 `Page 1 of 1`（单据 = 线上报价单 id=113，15 行 / 3 组）。

   根因（`_probe_geom.html` 确定性几何探针实测 —— 手工摆 paged.js 真实嵌套、只引真实
   style.css、**不引 paged.js ⇒ 零竞态**）：
     · **分页时**（页面元素上还没有 `kbp-page-v2`）：`padding-top: 30mm` / 内容盒 **236mm**
     · **显示时**（after() 贴类之后）：`padding-top: 40mm` / 内容盒 **226mm**（第 2 页起 222mm）
   ⇒ paged.js 按 236mm 判页数、按 226mm 显示 ⇒ **(226mm, 236mm] 这 10mm 的死区**：
     正文流落在里面的单据被判"刚好一页"，尾部被 `.pagedjs_page { overflow: hidden }` 裁掉，
     **且不会补第 2 页**（第 2 页起差 14mm）。
   ⇒ 他的单子正文流实测 **225.7mm**（页 1 可用 226mm，净富余只 **0.3mm**）—— 一越界就丢尾。

   修法：`doc-paged.js` 在建 flow **之前**就往 `<body>` 上贴 `kbp-v2-head`（它分页前就存在）
   ⇒ 下面两条在**分页那一刻**就命中，与显示几何逐字一致。
   🔴 **上面 A 块那两条 `.pagedjs_page.kbp-page-v2 …` 永久保留**（1.70.0–1.76.0 已签发快照
      与"类已贴上"那一半的别名；删了会掉偏移）。
   ⚠️ 两条给的值与 A 块**逐字相同**（40mm / `calc(100% - 40mm - 31mm)`；第 2 页起
      `40mm + var(--doc-blank-line)`）⇒ 无论哪条赢，值都一样，不会出现两套几何。 */
body.kbp-v2-head .pagedjs_page_content {
  padding-top: 40mm !important;
  height: calc(100% - 40mm - 31mm) !important;
}
body.kbp-v2-head .pagedjs_page:not([data-page-number="1"]) .pagedjs_page_content {
  padding-top: calc(40mm + var(--doc-blank-line)) !important;
  height: calc(100% - (40mm + var(--doc-blank-line)) - 31mm) !important;
}

/* ---------- C · 🔴🔴 paged.js 的"溢出栏"判定必须与**内容区右边界**对齐（1.77.3-TailClip）
   Wolfgang 2026-10-16 第三次报障（1.77.3-TailClip 部署之后）：
     *"已经部署，但仍然分页有问题…我尝试了给 table 里增加一些行，可以促使分页，
       这样 note 中的文字就能顺利显示在第 2 页"*。

   真事故（**线上真实单据** quotations#110 的打印页 HTML 取回本地、CDP 走真打印管线量出来的，
   不是推理）：
     · 取回线上 HTML 本地复现 ⇒ **1 页、页码 `Page 1 of 1`、`.doc-notes` 整块落在
       x=**1737px**（纸面宽 794px）= **纸张外面**，被 `.pagedjs_page { overflow: hidden }`
       裁掉 ⇒ 他看到的"Note 不见了、也没有第 2 页"。
     · paged.js 0.4.3 的断点判定（`findOverflow`）：候选断点 = **第一个
       `left ≥ bounds.right + gap`（或 `top ≥ bounds.bottom`）的子元素**，
       而 `bounds = element.getBoundingClientRect()` = 页内容元素的**边框盒**。
       我们的 `.pagedjs_page_content` 是 `content-box` + **左和右**各一个 `--doc-mx(15mm)`
       的 padding ⇒ 元素**边框盒右边界 = 794px（整张纸宽）**，
       而正文的**第一个溢出栏**从 `57 + 680 = 737px` 起 ⇒ **永远 < bounds.right**
       ⇒ paged.js 认不出"这里溢出了" ⇒ **不补第 2 页** ⇒ 那一栏被裁掉（内容静默消失）。
     · 修法：**把多余的右 padding 归零** —— 左右页边距本来就已经由
       `width: calc(100% - 2 * var(--doc-mx))` 预留，右 padding 是**重复占位**、
       布局上什么也没管；去掉之后元素右边界回到 **737px** = 正文右边界 ⇒
       第一个溢出栏与它对齐 ⇒ paged.js 认出溢出、正常补第 2 页。
   📐 实测（**同一份线上单据 HTML**，只改这一条；CDP 真打印管线）：
       改前：1 页 · 页码 `Page 1 of 1` · `.doc-notes` 在 **x=1737**（纸外）
       改后：**2 页** · 页码 `Page 1 of 2` / `Page 2 of 2` · `.doc-notes` 在 **x=57**
             （第 2 页上，正常显示）· 第 1 页 101 行**一行未变**、页脚两页都在
   📐 回归矩阵（同一次渲染、4 个长度变体）：**无 notes / 少 3 行 / 少 5 行 ⇒ 仍是 1 页**
       （不会凭空多出空白页）；只有"装不下"的那一份变成 2 页。
   🔴 只改**几何判定**、不动任何可见排版：内容盒的位置与宽度一字不变（57 → 737），
      变的只有元素自己的边框盒右边界（794 → 737）—— 那 57px 本来就是右页边距，
      没有任何东西依赖它（无背景/无边框）。
   🔵 选择器是**通用的**（不带 `kbp-page-v2`）⇒ 六张纸 + legacy 页眉的单据一律受益。 */
.pagedjs_page .pagedjs_page_content { padding-right: 0 !important; }

/* ============================================================================
   KBP7-⑦（Wolfgang 2026-10-14，目标设计图 + 11:36「**仅动 Quotation**」）
   → **1.71.0-ContactPanel（2026-10-15）起这一族是六张纸共用**
   ============================================================================
   🔴 **1.71.0 的名字迁移**：作用域锚点 `.doc-quote-top` → **`.doc-top-v2`**，
      **旧名永久保留、每个选择器都写双名别名** —— 1.58.0–1.69 签发的**报价单冻结快照**
      （`doc_snapshots.html`，冻死的一份完整 HTML）里存的还是旧名，删了旧名那些纸就**掉样式**。
   🔴 **承诺变了**（他 2026-10-15 拍板"六张一起改"）：本族规则现在**六张纸都吃到**
      ⇒ 五张纸的右表从"有外框 / 66mm / 标签右对齐"变成"**无外框 / 76mm 贴右 / 标签左对齐**"。
      ⚠️ 连带效果（本来属批 3 的字号收敛，提前生效）：左栏 `Attn:` 那类小标签
      从 `10pt/700` 变成 **`9pt/400`**（与报价单一致）。属**有意为之**，不是手滑。
   🔴 文中的 `!important` 不是装饰：权威块（`.doc-infobox td` 的 padding、`.doc-info-label`
      的 700、`.doc-term-label` 的 700、`.doc-table thead th` 的 700）**全都是 `!important`**
      ⇒ 增量不带 `!important` 的话**特异性再高也不生效**（KBP7-③ 真踩过）。
   ============================================================================ */

/* ---------- ① `Attn:` 去粗 ＋ 字号收到正文（9pt） ----------
   权威块给 `.doc-topinfo .lay2-sec-label` 的是 **10pt ＋ 700，两条都带 `!important`**
   （**六张纸共用**，`tests/m20.php` 钉着那一条）⇒ 这里只加作用域 + 带 `!important`，**不动那条**。
   · KBP7-⑦：`font-weight: 400`（他：*"Attn:去除文字加粗"*）；
   · KBP7-⑨（他 2026-10-14 14:5x：*"Attn:字样，缩小字号与正文相同"*）⇒ 字号 10pt → **9pt**，
     与同一栏里的收件人行（`.lay2-attn`）及文档默认字号同值。
   ⚠️ 作用域 `.doc-quote-top` 下只有这一个 `lay2-sec-label`（`Attn:`）⇒ 只影响报价单。 */
.doc-quote-top .lay2-sec-label, .doc-top-v2 .lay2-sec-label { font-weight: 400 !important; font-size: 9pt !important; }

/* ---------- ② 三列的**中间列**：从 `:` 改成**纯间隙**（KBP7-⑫） ----------
   他：*“去掉冒号，但保留中列”* ＋ *“中间列宽即是需要的宽度，按4mm即可”*。
   ⇒ 这一格**什么都不印**，它的**宽度就是左右两栏的距离** = **4mm**。
   同批配套的两条（都在本块里）：
     · 标签格 `padding-right: 4mm → 0`（间隙只能由这一列承担）
     · 值格 `padding-left: 0`（权威块给每个格 `padding: 2px 6px`，那 6px 会把
       可见距离变成 4mm + 1.59mm ⇒ 与“4mm”这个数字对不上）
   ⚠️ 仍然带 `!important`：权威块 `.doc-infobox td { padding: 2px 6px !important }`
      是 `!important` 的，不带就压不住（KBP7-③ 踩过同一个坑）。 */
.doc-quote-top .doc-infobox .doc-info-gap, .doc-top-v2 .doc-infobox .doc-info-gap {
  /* 🔴 KBP7-⑭（他 2026-10-14 深夜）：*"将中列宽度再增加1倍"* ⇒ 4mm -> **8mm**。
     他同时要 *"将右列宽度减少8mm"* —— 表宽 80 -> 76mm（−4mm）、中列 +4mm
     ⇒ 值列自动 58.4 -> 50.4mm，正好 −8mm。两个数是一组的，别只改一个。 */
  width: 8mm;
  padding: 0 !important;
  font-weight: 400 !important;
}
/* 值格（第 3 列）左内边距归零 —— 见上。
   ⚠️ `:nth-child(3)` 不会误伤 `doc-info-blank` / `doc-info-section` 那两种
      整行格（它们是 `colspan` 的第 1 格）。 */
.doc-quote-top .doc-infobox td:nth-child(3), .doc-top-v2 .doc-infobox td:nth-child(3) {
  padding-left: 0 !important;
}

/* ---------- ③ 「空行」——单据信息与联系人之间那一行 ----------
   🔴 表格里 `margin` **不生效** ⇒ 撑高只能靠行本身；`padding: 0 !important` 也是必须的
      （权威块给 `.doc-infobox td` 的 `padding: 2px 6px !important` 会把这一行撑高一倍）。
   `--doc-blank-line: 4mm` 是全站"一个空行"的同一个量。 */
.doc-quote-top .doc-infobox tr.doc-info-blank td, .doc-top-v2 .doc-infobox tr.doc-info-blank td {
  height: var(--doc-blank-line);
  line-height: 0;
  padding: 0 !important;
  border: 0 !important;
}

/* ---------- ④ 信息表**全不加粗**（他：*"去除表格中所有字体加粗"*） ---------- */
.doc-quote-top .doc-infobox td, .doc-top-v2 .doc-infobox td,
.doc-quote-top .doc-infobox th, .doc-top-v2 .doc-infobox th { font-weight: 400 !important; }

/* ---------- ⑤ 表下区块的术语标签去粗（他：*"LT, Terms, delivery terms, payment terms,
                 expiry date，都去除标题字体加粗"*） ----------
   🔴 这一块住在 Items 表**之外**（`.doc-quotation-table` 够不着它）⇒ 靠 `doc_kv_table()`
      第 4 个实参传进来的 `.doc-quote-terms`（见 `quotation-document.php` 那一处）。
   `tr.doc-term-head td` 是灰底那行 `Terms`：**灰底保留，只去粗**。
   ⚠️ 散文里刻意不写这两个选择器之外的东西 —— 同类规则散在六张纸上，作用域必须写全。
   🔴 **1.73.0-DocTypeScale（批 3／3）**：由「只有报价单」提升为**六张通用**（任务书 §4-2 ＋ 验收 ②）：
        · 灰底那一行的字重 —— 值**归权威块**（`.doc-term-table tr.doc-term-head td`，见 3761 行），
          原来那条只在报价单生效的覆盖**已删除**；
        · **术语标签格**（`.doc-term-label`）—— 共用权威块的那条 700 被 `m32` / `guard_termframe`
          等判据钉着，**不就地改**（任务书 §5-3「共用权威块不许就地改 —— 只能加作用域」）
          ⇒ 在这里**去掉纸作用域**（`.doc-quote-terms ` 前缀）让它覆盖六张。
          特异性 **(0,2,0)** ＋ `!important` 压得住权威块的 (0,1,0)。
          ⚠️ **银行信息那一行是例外（保持加粗）** —— 见本条正下方那条，别合并。
      ⚠️ 两条**必须成对**：只去灰条、留标签加粗 ⇒ 同一张表里"表头常规 + 标签加粗"，两侧都难看。 */
.doc-term-label { font-weight: 400 !important; }
/* 🔴 **例外（有意为之，别合并进上一条）**：**银行信息那一行**（`Bank Info.:`）**保持加粗**。
   理由两条：
     · 它**不是术语行** —— 他 ⑦ 点名的五个标题是 `LT` / `Terms` / `delivery terms` /
       `payment terms` / `expiry date`，`Bank Info.` 不在其中；
     · 它是"标签 + 多行长值"的**数据块**（`doc_bank_block_html()` 包在 `.doc-bank-block` 里），
       OC / PF 才有（Quotation 2026-10-07 起已移除、Order/CI/PL/PO 一直没有）
       ⇒ 本批没有"参照报价单"的依据，**不动它就是不动**。
   ⚠️ 特异性 **(0,2,0)** 压得住上一条的 (0,1,0) 与权威块 3151 行的 (0,1,0)，**不靠书写顺序**。
   ⚠️ **只认 `.doc-bank-block` 这个包装类**：`doc_kv_table()` 自己会给块加 `.doc-term`，
      而银行块的 `<div class="doc-term">` 就**住在** `.doc-bank-block` 里面 ⇒
      用 `.doc-term .doc-term-label` 当作用域会把银行行也一起命中（**假的"精准"**）。 */
.doc-bank-block .doc-term-label { font-weight: 700 !important; }

/* ---------- ⑥ Items **表头行**去粗（他：*"Items表格修改，标题行去除字体加粗"*） ----------
   ⚠️ **只动表头**：序号列（`tbody td:nth-child(1)` 的 700）与 Model 的 `<b>` **都不动** —— 他
   🔴 **1.73.0-DocTypeScale（批 3／3）**：这条原来写的是 `.doc-quotation-table thead th`（只作用报价单）—— 现在
      **值归权威块**（`.doc-table thead th`，见 3137 行附近）⇒ **这条覆盖已删除**；口径：六张一致（任务书 §4-2）。
/* ============================================================================
   KBP7-⑧（Wolfgang 2026-10-14 下午，**部署后实物截图**）—— 报价单 Items 表 + 开场白 5 条
   ============================================================================
   🔴 **全部只作用报价单**（他 12:52：*"仅做 quotation 页面"*）：
      锚点是 `.doc-quotation-table`（明细表自己身上）、`.doc-items-end`（本批新加的收尾行）
      与 `doc-greeting`（全仓只有报价单输出它）。
      共用的表格权威块（表头字号 / 逐列对齐 / "最后一行"的收尾线 / 型号加粗）
      **一个字都没动** ⇒ 其余五张纸逐字不变（本批的反向判据）。
   ============================================================================ */

/* ---------- ② `Model & Description` **表头左对齐** ----------
   他：*"Model & Description标题也调为左对齐"*。数据本来就是左对齐，错位的是表头。
   🔴 **1.73.0-DocTypeScale（批 3／3）**：由 `.doc-quotation-table …` 提升为**六张通用**，实现搬到了
      ⬇ 第 2563 行那一条的**正下方**（`.doc-table thead th:nth-child(2) { text-align: left !important; }`）—— **必须紧挨着**那条"所有表头居中"

/* ---------- ③ Items **表头字号 ＝ 表格正文** ----------
   他 2026-10-14 14:5x：*"item表格的标题栏，将字号缩小到与表格正文相同"*。
   🔵 做法是**撤掉** KBP7-⑧ 加的那条 10pt 覆盖（**不是**再写一条 9pt）：撤掉之后表头
      只能由权威块 `.doc-table thead th { font-size: 9pt }` 决定，与正文
      `.doc-table td { … 9pt … }` 同源同值 ⇒ **结构上不可能再漂**。
   ⚠️ 这里刻意不写任何 `font-size` 覆盖 —— 写了就等于又开一个可漂的点。
      `m20` / `m143` 的 10pt 白名单随之回到 **3**。 */

/* ---------- ④ **表格收尾线**：补一条"只用于收尾的行"（由 `quotation-document.php` 追加） ----------
   他：*"没有表格附属 ref 时，items 表格的下分隔线左侧半条消失"* —— 描述完全准确。
   根因：收尾线由 3450 行那条"表格最后一行"的 `border-bottom` 规则画，而报价单的**分档行**
   （`.doc-tier`）**只输出后 4 格**（Qty / Unit / Unit Price / Total），前两格靠**首行的
   `rowspan`** 继承 ⇒ 最后一个 item 有**多档**时"最后一行"是那条分档行、它的格子从**第 5 列**
   才开始 ⇒ **线只从 Qty 列起、左半条没有**；有 Ref 行时它是最后一行且 `colspan` 铺满 ⇒
   所以**只在没有 ref 时暴露**。
   ⇒ 追加一条 `.doc-items-end` 空行来画线（`$itemNo > 0` 才加 ⇒ 空表不会凭空多一条线）：
     · `border-top` = 收尾线（全站同一条 `--doc-line` 黑实线，与 3450 行那条等价）；
     · `border-bottom: 0` **必须显式写** —— 它同时是"最后一行"，否则 3450 行那条规则
       会在它下面**再画一条**，两条线隔着 6px 行高并排，一眼就看出来；
     · `padding: 0` + `line-height: 0` ⇒ 行高≈0，线的位置与改动前**逐像素相同**。 */
.doc-quotation-table tbody > tr.doc-items-end > td {
  border-top: 1px solid var(--doc-line) !important;
  border-bottom: 0 !important;
  padding: 0 !important;
  line-height: 0;
}

/* ============================================================================
   KBP7-⑨（Wolfgang 2026-10-14 14:5x，**三处微调**）→ KBP7-⑩（同日 15:3x，**再改三处**）
   ============================================================================
   ⑨ 他：*"1 Attn:字样，缩小字号与正文相同；2 item表格的标题栏，将字号缩小到与表格正文相同；
        3 Expiry Date重新调整到Estamited LT 右边，两者排为一行"*
      ⇒ ① 见上面 KBP7-⑦ 段那条（加了 `font-size: 9pt`）；② 见上面 KBP7-⑧ 段（**撤掉** 10pt 覆盖）。
   ⑩ 他：*"1 Estimated LT 和Expiry Date布置在同一行，后者长度固定，占据整行的右边 1/3。
        要求两项标题和内容只占一行，而不能换行；2 …无论是否填写内容，标题占位都必须存在；
        3 Delivery Terms和Payment Terms，无论有内容，标题占位也必须存在"*
      ⇒ ① 见下面这一段（**四列定宽** ＋ `nowrap`）；
        ②③ 见 `quotation-document.php` —— `doc_kv_table()` 第 2 实参 `false` → **`true`**（`$showEmpty`）。
   作用域仍是报价单专属的 `.doc-quote-terms` ＋ 行钩子 `.doc-lt-pair` ⇒ 其余五张纸一格不动。
   ============================================================================ */

/* ---------- ⑩-1 四列定宽：并排那一行，右边 1/3 固定给 `Expiry Date` ----------
   **版心 = 210mm − 2×15mm(`--doc-mx`) = 180mm** ⇒「整行右边 1/3」= **60mm**，左 2/3 = **120mm**。
   Arial 9pt 实测（内边距 8px+8px = 4.23mm 已计入）：
     `Payment Terms:` **27.52mm**（最长）· `Delivery Terms:` 26.45 · `Estimated LT:` 23.82 · `Expiry Date:` 21.52
   ⇒ 标签列取 **30mm**（= 180 的 16.667%，对最长标签留 2.5mm 余量），
     两个值格分别 **90mm**(50%) 与 **30mm**(16.667%) ⇒ 96 + 90 + 30 = 180mm，算式闭合。
   🔴 **右边 1/3 的算式**：`Expiry Date:` 标签格 30mm ＋ 值格 30mm = **60mm = 180mm 的 33.33%** ✓
   🔴 标签列宽**必须**用 `:first-child`（**不许**再写一条"术语表 ＋ 标签格"那种两段式选择器）：
      `guard_terms_cell_alignment` 取"**最后一条**命中该形状的规则"来判左对齐，
      再写一条同形状的规则会把它顶掉 ⇒ BUILD FAILED（本批当场撞过一次）。
   🔴 值格的宽度**只挂 `.doc-lt-pair`**：挂成通用 `td:nth-child(2)` 会顺带改到
      Delivery / Payment 那两行的 `colspan=3` 值格。 */
.doc-quote-terms .doc-term-table tr > td.doc-term-label:first-child { width: 16.667%; }
.doc-quote-terms .doc-term-table tr.doc-lt-pair > td:nth-child(2) { width: 50%; }
.doc-quote-terms .doc-term-table tr.doc-lt-pair > td:nth-child(3) { width: 16.667%; }
.doc-quote-terms .doc-term-table tr.doc-lt-pair > td:nth-child(4) { width: 16.667%; }

/* ---------- ⑩-2 并排那一行：**四个格一律不换行** ----------
   他：*"要求两项标题和内容只占一行，而不能换行"*。
   标签格本来就有 `white-space: nowrap`（`.doc-term-label`），这里把**两个值格**也钉住；
   宽度已按 9pt 实测给足（LT 值 90mm、日期值 30mm）⇒ 正常内容绝不会挤到换行。
   ⚠️ `nowrap` 的代价是"内容超长就横向溢出"而不是折行 —— 这是他要的（宁可溢出也不许换行）。 */
.doc-quote-terms .doc-term-table tr.doc-lt-pair > td { white-space: nowrap; }


/* ============================================================================
   KBP7-⑫（Wolfgang 2026-10-14 晨 21:5x，**报价单六条微调**）
   ============================================================================
   他：*“5 Items表格，右侧的tier之间，缩小行间距到约当前间距的60%，取个方便的整数值；
        6 Items表格附属的Ref行，排版向上提一行；
        7 Expiry Date: 14.11.2026 变为右对齐，使2026到达最右边”*
   数据侧四条在 `quotation.php` / `quotations.php` / `quotation-document.php` /
   `lib/ui.php`；本块只有 CSS 四条。
   🔴 全部落在**报价单专属锚点**上：`.doc-quotation-table`（明细表自己身上）·
      `.doc-tier` / `.doc-item-lead`（**只由** `quotation-document.php` 输出）·
      `.doc-quote-terms`＋行钩子 `.doc-lt-pair`（KBP7-⑩ 已有）
      ⇒ **其余五张纸一格不动**（本批的反向判据）。
   ============================================================================ */

/* ---------- ⑤ tier 行距 ×60%（他取“方便的整数值”） ----------
   🔵 改前的实测基准（都是本文件里现成的数）：
     · `.doc-table tbody td` 上下 padding 各 **5.4px**（0.48.0 那一段的 DocLine3 权威块，带 `!important`）
       ⚠️ 这里**刻意不写「版本号-名字」那种字面量**（带连字符的块名）：`m26` 从某个早期文档块的
          banner 起扫块名白名单 ⇒ **注释里写一次就把它的锚点/白名单顶坏**（本批真踩了两次）。
          要指某个老块，就写成「0.48.0 那一段 DocLine3」。
     · 正文行盒 = 9pt × `line-height: 1.45` = **17.4px**
     ⇒ 档位**行高 28.2px（7.46mm）**；两行文字之间的**白 = 5.4 + 5.4 = 10.8px（2.86mm）**。
   他选了口径 A：把“白”压到 **6px**（= 55.6%）⇒ 各侧 **3px**，行高 → 23.4px = 6.19mm。
   （口径 B“行高×60%”要求 padding 归 0 ⇒ 行高 17.4px = 4.60mm = 62%。）
   ⚠️ `.doc-item-lead` **一起改**：它是同一组产品的**第一档**行，只改 `.doc-tier`
      会让“首档→第2档”的白（5.4+3）比“第2档→第3档”（3+3）宽，肉眼可见地不齐。
   ⚠️ 带 `!important` 且特异性 (0,2,3) **高于** `.doc-table tbody td` (0,1,2) 才压得住那条 5.4px。 */
.doc-quotation-table tbody > tr.doc-item-lead > td,
.doc-quotation-table tbody > tr.doc-tier > td {
  padding-top: 3px !important;
  padding-bottom: 3px !important;
}

/* ---------- ⑥ Ref 行上提一行（他：*“排版向上提一行”*） ----------
   Ref 行上方那条 `.doc-items-gap` 就是被上提掉的那“一行”（空行，实测 ≈2.54mm）。
   ⚠️ 只在**报价单**里收掉：那行是共用 helper `doc_items_ref_row()` 产出的，
      另外四张纸（OC / PO / CI / PL）的空行**必须保留**（`m22.php` 钉着那个 helper 的输出）
      ⇒ 这里只做**作用域内的显示覆盖**，helper 一个字没动。
   ⚠️ 用 `display: none` 而不是“高度归零”：归零仍会给行盒留下 `line-height` 的残高，
      而且 `&nbsp;`（6pt 字号）在部分字体下不为 0 ⇒ 直接不渲染这一行最干净。 */
.doc-quotation-table tbody > tr.doc-items-gap > td {
  display: none !important;
}

/* ---------- ⑦ `Expiry Date:` 的值**右对齐到纸边** ----------
   他：*“Expiry Date: 14.11.2026 变为右对齐，使2026到达最右边”*。
   · `text-align: right !important` —— 权威块 `.doc-term-table td { text-align: left !important }`
     是 `!important` 的，不带就压不住；
   · `padding-right: 0 !important` —— 基础规则 `.doc-term-table td { padding: 4px 8px }` 的
     右内边距 8px（2.12mm）会把文字从纸边推开 ⇒ 不清掉就到不了“最右边”。
   ⚠️ 只挂第 4 格（`Expiry Date` 的值），且**只挂并排那一行**（`.doc-lt-pair`）——
      写成通用的 `td:last-child` 会顺带改到 Delivery / Payment 那两行的跳列值格。 */
.doc-quote-terms .doc-term-table tr.doc-lt-pair > td:nth-child(4) {
  text-align: right !important;
  padding-right: 0 !important;
}

/* ============================================================================
   KBP7-⑭（Wolfgang 2026-10-14 深夜 · 他**测试后**报的三处）
   ============================================================================
   他：*"1 Items表格中，decription或者note会明显与右边Tier发生显示错行，这不符合人眼习惯；
        2 Items表格，以及Terms表头，带有灰度的背景，将其高度减少一些，当前过于高了；
        3 我尝试使用了一个很长的邮箱来验证联系人表格的右边在哪里。看起来右边已经是到了页面
          最右…但是如果没有这个很长的邮箱，看起来整体表格就过于偏左。所以将右列宽度减少8mm,
          如果遇到邮箱超长则换行；同时将中列宽度再增加1倍"*
   他随后把第 1 条的目标说成了一句话（**本批的验收口径**）：
   *"我希望产品描述能与价格第2档位在同一行，note与价格第3档位在同一行"*
   ⇒ **Model ↔ 第1档 · Description ↔ 第2档 · Note ↔ 第3档 逐行严格对齐**。

   ---------- ① 描述块与档位**逐行对齐**（本批的主修） ----------
   🔴 根因**不是**结构问题，是**两套行高**：
      · 档位行（`.doc-tier` / `.doc-item-lead`）高 **23.4px** =
        上下 padding 各 3px ＋ 行盒 17.4px（9pt × `line-height: 1.45`）。每档一个独立 `<tr>`。
      · 产品描述块住在**首档行的一个 `rowspan=N` 格里**，里面是 N 个
        `<span class="doc-line">`（`display: block`）⇒ 每个 span 只有**行盒 17.4px**，
        **没有**那上下各 3px 的 padding（padding 挂在 td 上、整格只给一次）。
        ⇒ **每多一行就累积 −6px 偏移**：第 1 行 0、第 2 行 −6px、第 3 行 −12px。
      · 雪上加霜：`.doc-item-note` 那条老规则把 `line-height` 钉成 **1.25**，
        而 Model / Description（纯 `.doc-line`）继承 `.doc` 的 **1.45**
        ⇒ **Note / 产品级 Ref 那一行比档位行又矮 2.4px**。
      ⇒ 正解 = 让描述块的**每一行**都占"**一个档位行**"的高度：
        ① td 的上下 padding 归 0（改由每个 span 自己带 3px）；② Note/Ref 的
        `line-height` 一并拉回 1.45。
      ⇒ 于是「N 行描述」与「N 档价格」的总高都是 `N × 23.4px` ⇒ **逐行对齐**。
   ⚠️ 只挂**报价单的明细表**（`.doc-quotation-table`）—— 其余五张纸的明细**没有档位**，
      产品格走 `doc_item_row()` 的 `<b>` ＋ `<br>`，一个字都不动。
   ⚠️ 特异性要够：`.doc-item-note` 的 1.25 是 (0,1,0)；1.66.0 那条档位 padding 是 (0,2,3)
      ⇒ 这里用 (0,2,4) 且带 `!important`。 */
.doc-quotation-table tbody > tr.doc-item-lead > td.cell-model {
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}
.doc-quotation-table tbody > tr.doc-item-lead > td.cell-model > .doc-line {
  padding-top: 3px !important;
  padding-bottom: 3px !important;
  line-height: 1.45 !important;
}

/* ---------- ② 表头 / Terms 灰条减高 ----------
   他：*"带有灰度的背景，将其高度减少一些，当前过于高了"*。
   🔵 改前的实测基准（都在本文件里现成）：
     · **Items 表头** = `.doc-table thead th`，上下 padding 各 **6.6px**
       （早先那次"+20%"留下的）⇒ 行高 17.4 ＋ 13.2 = **30.6px（8.09mm）**。
     · **Terms 灰条** = `.doc-term-table tr.doc-term-head td`，上下 padding 各 **4px**
       ⇒ 行高 17.4 ＋ 8 = **25.4px（6.72mm）**。
   他 1.66.0 那次的口径是*"取个方便的整数值"*，本批沿用 **3px** ——
   正好与档位行（`.doc-tier`）同一个数 ⇒ 全表三处（表头 / 档位行 / 灰条）都是 **6.19mm**。
   ⚠️ **1.73.0-DocTypeScale 改了口径**：本条原来只给**报价单**加作用域
      （`.doc-quotation-table` / `.doc-quote-terms`），批 3 把它**提升为六张通用**
      （任务书 §4-2）⇒ 两条 `padding` 覆盖**已删除**，值搬进了权威块：
        · Items 表头 → `.doc-table thead th`（0.48 那批「DocLine3」的块，紧跟"表体 +20%"之后）；
        · Terms 灰条 → `.doc-term-table tr.doc-term-head td` 基础规则（`.doc-term-table td`
          的 `padding: 4px 8px`）之上，由 3xxx 行那条 `doc-term-head` 规则给出 3px。
      ⇒ 依据仍是 1.66.0 的口径（*"取个方便的整数值"*）＝ **3px**，与档位行同一个数
        ⇒ 全表三处（表头 / 档位行 / 灰条）都是 **6.19mm**。 */



/* ============================================================================
   KBP7-⑬（Wolfgang 2026-10-14 深夜）—— 文档字体**自托管**（Liberation Sans）

   他：*"Quotation 的 A4 现在靠操作系统字体 ⇒ 不同电脑渲染出的 PDF 存在差异。
   目标：① 查清用到的全部字体；② 全部换成 Liberation Sans；③ 为 KBP 平台安装
   自托管字体（@font-face），使字体不依赖用户电脑。"*

   🔵 **为什么是 Liberation Sans**：它是**按 Arial 的 advance width 做的度量兼容替身**
      —— 本项目所有版式宽度都是按 Arial 量出来的（`tests/m141.php` 钉着 9pt 的毫米数）
      ⇒ 换上去**版式数字一个都不用重算**（他明确要求"只换字体的来源，不动任何尺寸"）。
      本批实测：Regular / Bold 与**对应的** Arial 字型 **逐字 0.0000pt 偏差**
      （95 个 ASCII 字符 + 真实单据串；同级比较 Regular↔Regular / Bold↔Bold）。
   🔵 **为什么可以自托管**：Liberation 2.0 起是 **SIL OFL**，实测 `OS/2.fsType = 0x0000`
      （installable）⇒ 可嵌入、可随包分发。Arial 是 Monotype 授权、**不可再分发**
      —— 所以 `assets/fonts/` 里**只有** Liberation。

   ⚠️ **只声明 Regular 与 Bold 两个字型**（他选的口径：斜体不嵌）：
      · `font-weight: 600`（文档里三处）会落到 Bold，与现状 Arial 的映射一致；
      · **斜体**（`.doc-item-note` / `.doc-ref` / `.doc-empty`，三处都是条件出现）
        **不带字形文件** ⇒ 浏览器合成倾斜。本批实测本地快照 **4/4 零命中**。
        要真斜体：把 `-italic` / `-bold-italic` 两个 woff2 放进 `assets/fonts/`，
        在这里各加一段（`font-style: italic`）即可。

   🔴 **`font-display: block`，不是 `swap`**：`swap` 会先拿回落字体画一帧，而 PDF
      渲染（无头 Chrome 打印）正好可能抓那一帧 ⇒ 屏幕上是对的、PDF 里是旧字体。
      `block` 只是多等几十毫秒。
   🔴 **`?v=` 版本串**：`style.css` 是**静态文件**，拼不进 PHP 的 `KBP_VERSION`
      ⇒ 这里的两处版本串由**打包守卫 `guard_docfonts` 钉住**（断言 == `EXPECTED_VERSION`）：
      升版漏改当场 BUILD FAILED，不会出现"部分机器继续吃旧字体缓存"。 */
@font-face {
  font-family: "Liberation Sans";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("fonts/liberation-sans-regular.woff2?v=1.120.2-RefBlue2") format("woff2");
}
@font-face {
  font-family: "Liberation Sans";
  font-style: normal;
  font-weight: 700;
  font-display: block;
  src: url("fonts/liberation-sans-bold.woff2?v=1.120.2-RefBlue2") format("woff2");
}

/* ---------- 家族的作用域：一次钉死"两棵子树" ----------
   🔴 **为什么不能只改 `.doc`**（本批最关键的一处，也是"看着改了、其实没生效"的老坑）：
      `doc-paged.js:56` 把 `.doc` 的 **children 克隆**进一个新建的 `.doc-flow`，
      而 `.doc-flow` 插在 `.doc` 的**旁边**、原 `.doc` 随即被 `display:none`
      ⇒ 分页后**正文已不在 `.doc` 之下**；页眉/页脚同样被克隆进 `.pagedjs_page`
      （类名换成 `.kbp-head` / `.kbp-foot`）⇒ 也不在 `.doc` 之下。
      ⇒ 家族必须写在**两棵子树都继承得到**的地方 = 打印页的 **`body`**。
      🔵 本批实测的现状：`body`（第 32 行）是 `system-ui,"Segoe UI",Arial`
      ⇒ **Windows 上纸面正文其实是 Segoe UI**，只有右栏信息表（`.doc-info-*`
      自带声明）是 Arial —— 一张纸混用两种字体，也正是"不同电脑渲染有差异"的根因。
   🔵 `.doc` / `.doc-page` / `.doc-info-*` 三处**就地改**（不新增覆盖规则），
      好让 `Helvetica` / `Helvetica Neue` 这些"每台机器解析不同"的名字整体退场。
   🔵 **Arial 留在回退链里是故意的**：它是本项目全部版式数字的度量基准，
      万一字体文件没随包上传，观感不会塌到某个不确定的默认字体上；
      而"文件没进包"这件事由打包守卫拦（不靠纸面肉眼看）。
   ⚠️ 这条必须待在 `.tl-subrows` **之前**（文档块内）—— `guard_terms_cell_alignment`
      拦"界面块之后又出现文档选择器"。 */
body.doc-body,
body.doc-print-body {
  font-family: "Liberation Sans", Arial, sans-serif;
}



/* ============================================================================
   0.66.0-ListUI — Timeline 的 Purchase PO 节点：供应商侧文件按 PI / PL / Invoice 分行
   （Wolfgang 2026-09-22）。

   以前 PL 与 Invoice 只是挤在 PO 行末尾的两个灰标签、PI 根本没有位置；现在每份文件
   一行，各挂自己的上传。这三行的视觉语言与 `.tl-tag`（同一节点里的其它小标）刻意不同：
   它们是**同一张 PO 下的并列文件**，不是状态标签 —— 所以用缩进 + 淡分隔线，
   而不是 chip。
   ============================================================================ */


.tl-subrows {
  margin: 3px 0 0 10px;              /* 缩进：表示"挂在上面那张 PO 之下" */
  padding-left: 8px;
  border-left: 2px solid #e2e6ea;    /* 竖线 = 归属关系，不是状态 */
}
.tl-subrow {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 12px;
  line-height: 1.7;
}
.tl-subrow + .tl-subrow {
  border-top: 1px solid #f0f2f5;     /* 极淡的分隔线：三行之间"分开但不喧哗" */
}
.tl-subrow-label {
  flex: 0 0 auto;
  min-width: 48px;
  color: #6b7280;
}

/* ============================================================================
   0.71.0 批（ColWidths）— 列表列宽的抓柄 + 面板上的 Reset 按钮

   Wolfgang 2026-09-23：*"所有表格列允许自由调节宽度，而且能被不同用户记忆。
   因为我们常使用 2K 外接显示器，所以有足够的宽度可以使用。"*

   ⚠️ 本块**只碰列表 UI**（`th.col-resizable` / `.col-grip` / `.cols-panel`）：
      没有一条 `.doc-*` 选择器 —— m26 会检查"某块之后不许再出现文档选择器"，
      文档版式必须留在自己的权威块里。
   ⚠️ 抓柄是**绝对定位**的窄条（7px），不吃表头那行的布局；表头的排序链接照旧可点。
      拖动时给 `body` 加 `.col-dragging`，把光标锁成 col-resize 并屏蔽选中。
   ========================================================================== */

th.col-resizable { position: relative; }
.col-grip {
  position: absolute;
  top: 0;
  right: 0;                    /* 0.96.0-ColSep — 从 `-3px` 收进表内（原来最后一列会伸出表外 3px） */
  width: 7px;
  height: 100%;
  z-index: 3;
  cursor: col-resize;
  /* 0.96.0-ColSep — 触摸拖拽的**前提**：没有它，手指在抓柄上滑动会被浏览器
     当成"滚动页面"，`pointermove` 根本不会连续过来（列宽拖不动）。 */
  touch-action: none;
}
/* ---- 0.96.0-ColSep（Wolfgang 2026-09-28）—— 常驻一条 1px 竖线 ----------------
   *"给各模块的list表头，增加一个可视的列分隔线，我发现在平版上使用时，无法定位到
     分隔线而无法操作拖拽。"*

   **看见的那条线就是能拖的地方** —— 线与热区是同一个元素（`.col-grip` 的伪元素），
   不新加任何 DOM。⚠️ 从前它平时全透明、只有 `:hover` 才显形，而**平板没有 hover**
   ⇒ 永远看不见（实测 `background: rgba(0,0,0,0)`、`opacity: 1`）。
   ⚠️ 只画在**有抓柄的表头**上 —— `assets/cols.js` 首行 `if (!panel) return;` 决定了
      抓柄只存在于带 `#cols-panel` 的 6 个列表页（Proj / QT / OC / Order / PO / Partners），
      其余页面**一条线都不会多出来**（"有线却不能拖"的误导不存在）。 */
.col-grip::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 6px;                   /* 7px 宽的**最右** → 线正好压在列边界上 */
  width: 1px;
  /* ⚠️ **不能用 `--line`**（0.96 第一版就是它，探针量出来才现形）：深色主题下
     `--line` = `#2b3342`，而表头底色 `--brand-tint` 在那个主题下也是深色
     （实测 `rgb(33, 51, 73)`）→ 线等于没画；浅色主题下 `#e3e6ea` 压在 `#eef3f8` 上
     也只有 ~4% 的亮度差。**跟随表头文字色**（`currentColor`，两种主题下都与底色成对比）
     + 半透明（有存在感但不抢戏）。 */
  background: currentColor;
  opacity: .45;
}
.col-grip:hover,
.col-grip.dragging { background: var(--brand); opacity: .3; }
/* 指到 / 正在拖 → 线变实（`opacity` 是主题无关的"变亮"信号；**不靠改颜色**，
   因为 `--brand` 在深色主题下压在深色表头上也不显）。 */
.col-grip:hover::before,
.col-grip.dragging::before { opacity: 1; }
/* ---- 触摸设备专属（平板）：热区 40px ≈ 指尖 34–42px；**桌面那 7px 一个字不动** ----
   热区以列边界为**中心**向两侧各扩 20px（`right: -20px`），线仍在边界上（`left: 19px`）。
   表头文字左对齐、占不到右侧那 20px，所以排序链接照旧可点（手指按列左侧 = 排序，
   按右侧 = 拖列）—— 这是"加宽热区"与"表头可排序"之间唯一需要守住的边界。 */
@media (pointer: coarse) {
  .col-grip { right: -20px; width: 40px; }
  .col-grip::before { left: 19px; width: 2px; }
}
body.col-dragging,
body.col-dragging * { cursor: col-resize !important; user-select: none !important; }

.cols-panel .cols-sep { border: 0; border-top: 1px solid var(--line); margin: 8px 0 6px; }
.cols-panel .cols-reset {
  display: block;
  width: 100%;
  padding: 5px 8px;
  font-size: 12px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink-soft);
  cursor: pointer;
}
.cols-panel .cols-reset:hover { border-color: var(--brand); color: var(--brand); }

/* ============================================================
 * 1.17.0-DocRef — 产品选型 Ref 的二级选择器（品牌 → 文件 + 搜索）
 * 唯一版式：Quotation 条目/表尾、OC 条目/表尾、PO 的 Ref 与 Spec、
 * CI/PL/PF 表尾，全部用同一个 `.ref-picker` 结构（`doc_ref_picker()`）。
 * 结构 = 品牌下拉 + 搜索框 + 文件下拉；前两个**不带 name**（只是筛选视图）。
 * ============================================================ */
.ref-picker { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.ref-picker .ref-brand  { min-width: 104px; max-width: 168px; }
.ref-picker .ref-search { flex: 1 1 140px; min-width: 120px; }
.ref-picker .ref-file   { flex: 1 1 220px; min-width: 160px; }
/* 1.95.4-RefFold（Wolfgang 2026-10-19）—— 明细行里的那一个（`doc_item_ref_field()`）：
   **默认折叠**，摘要那一行直接显示当前引用名；展开时把三个控件压回单元格宽度。
   🔴 没有这套收窄，三个控件的最小宽度（104+120+160 ≈ 384px）会把只有 150–180px 的
      `.item-main` 撑开 —— 他报的"item 编辑位置很乱"就是这个（Unit price 被切在框外）。 */
.ref-inline { margin-top: 4px; }
.ref-inline > summary {
  cursor: pointer;
  font-size: 11px;
  line-height: 1.7;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink);            /* 收起时也要看得清（不是灰字） */
}
.ref-inline > summary .ref-cur { font-weight: 600; }
.ref-inline > .ref-picker {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  margin-top: 4px;
}
.ref-inline > .ref-picker .ref-brand,
.ref-inline > .ref-picker .ref-search,
.ref-inline > .ref-picker .ref-file { width: 100%; min-width: 0; max-width: none; }

/* ============================================================
 * 1.95.4-RefFold（Wolfgang 2026-10-19）—— 「Check the PDF」这一页
 * ------------------------------------------------------------
 * 他：*"在pdf的check界面，下面check部分太大，占用了太多屏幕空间"* ＋ *"your call也收成一行"*。
 * 三块一起收：清单 → 一条可折叠的摘要；PDF → 高度跟视口；决断 → 底部一条贴边行动条
 * （Reject 的理由框藏进一个下拉，默认不占高度）。
 * ⚠️ 清单内容**一条都不许删**（`doc_pdf_checklist()` 是唯一真源），只改可见性。
 * ============================================================ */
.pdf-check-list {
  margin-top: 12px;
  padding: 8px 12px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.pdf-check-list > summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 700;
}
.pdf-check-list > ul {
  margin: 8px 0 2px 18px;
  font-size: 13px;
  line-height: 1.65;
}
.pdf-check-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 12px;
  padding: 10px 12px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 -2px 12px rgba(16, 24, 40, .06);
}
/* Reject 的下拉：默认只占一个按钮的位置，展开时在**上方**浮出（不把行动条撑高）。 */
.pdf-check-reject { position: relative; }
.pdf-check-reject > summary { list-style: none; }
.pdf-check-reject > summary::-webkit-details-marker { display: none; }
.pdf-check-reject > form {
  position: absolute;
  left: 0;
  bottom: calc(100% + 8px);
  z-index: 20;
  width: 320px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(16, 24, 40, .16);
}

/* A4：条目级 Ref（`pi_render_html()` / `po_render_html()` / Order / Proforma 的明细行）。
   🔵 1.95.4-RefFold —— **不再是灰字**（他：*"ref:不使用灰色"*）：颜色跟正文一致，
      与 `.pi-note` 那种"次要信息"刻意分开。 */
.pi-ref { margin-top: 2px; }


/* 1.43.0-Presence —— 「有人在动同一份草稿」的横幅（需求单㉙ Part 2）。
   与 .msg 同一档尺寸：两者都出现在页面最上方，视觉上应当像同一族。
   琥珀 = 有人在**编辑**（要你留意一格），蓝 = 只是**在看**（知道就行）。 */
.presence-banner {
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 20px;
  font-size: 14px;
}
.presence-banner.is-edit { background: #fff4e0; color: var(--warn); border: 1px solid #efd7ad; }
.presence-banner.is-view { background: #eaf3fb; color: #1f6fb2; border: 1px solid #c5dcef; }
.presence-title { font-weight: 600; }
.presence-line { margin-top: 4px; }
.presence-line b { font-weight: 600; }
.presence-note { margin-top: 6px; font-size: 13px; opacity: .85; }

/* 1.79.0-SoftwareAdmin —— Software 列表的软件名：悬停看完整说明（虚线示意"可悬停"）。 */
.soft-name { cursor: help; border-bottom: 1px dotted var(--line); }

/* ===========================================================================
 * 1.80.0-QuotUI —— 报价单详情页：按键统一压扁
 * Wolfgang 2026-10-18：*"在 Quotation 详情页面，将所有显示的按键都统一为更扁的形态
 * （按键高度降低）"*
 * 🔵 作用域只到 `.detail-view`（七张详情页的整页锚点）⇒ **不动全局 `.btn`**，
 *    别的页面（列表 / 工作台 / 单据页）的按钮高度是既定观感，不能跟着变。
 * 🔄 1.82.0-P2 —— 作用域从 `.q-view` 扩到 `.detail-view`：他要求"详情按键样式
 *    推广到其它页面"（`.q-view` 只罩着报价单）。**同一个类管字号与按键**，不再分两套。
 * ⚠️ 选择器特异性 0,2,0 > `.btn` 的 0,1,0 ⇒ 不需要 `!important`。
 * ========================================================================= */
.detail-view .btn {
  padding: 6px 14px;
  font-size: 13.5px;
  line-height: 1.3;
}
/* 页内本来就更小的那档（如 Columns / 行内操作）跟着再收一档，保持两级差 */
.detail-view .btn.btn-sm {
  padding: 4px 10px;
  font-size: 12.5px;
}
/* 禁用态：签发后的 Edit、发布后的 Delete。
   刻意用 `<span>` 而不是 `<a disabled>` —— `<a>` 没有 disabled 语义，
   灰掉的 `<span>` 根本不可点，且 title 气泡照样能看（这就是灰它的理由）。 */
.detail-view .btn.btn-disabled {
  opacity: .45;
  cursor: not-allowed;
}

/* ===========================================================================
 * 1.81.0-DocType —— 详情页统一字号阶梯（七张：QT / PF / OC / PO / PL / CI / Order）
 *
 * Wolfgang 2026-10-18：*"除页面标题，按键和卡片标题以外，将其他字体和字号进行统一，
 * 右侧 Line Items 表格使用更小一号字号"*；后续定案 **正文 14 / 注解 12 / Total 13**。
 *
 * 🔴 每条都带 `.detail-view` 前缀 —— `.kv` / `.table` / `label` / `input` 是**全局类**，
 *    不带前缀就把 partner / project / 列表页一起改了（那是"顺手改别人的页面"）。
 * 🔵 锚点由 `render_page(['root_class' => 'detail-view'])` 注入，唯一实现在 lib/layout.php。
 * ⚠️ 特异性：`.detail-view .kv`(0,2,0) > `.kv`(0,1,0) ⇒ 不需要 `!important`。
 * ========================================================================= */
.detail-view { --dv-body: 14px; --dv-note: 12px; --dv-item: 13px; }

/* ---- 正文 14px ---- */
.detail-view .kv,
.detail-view .card-note,
.detail-view .tl-content,
.detail-view .empty,
.detail-view .page-head .subtitle { font-size: var(--dv-body); }
.detail-view label { font-size: var(--dv-body); }
.detail-view input[type="text"],
.detail-view input[type="password"],
.detail-view input[type="email"],
.detail-view input[type="number"],
.detail-view input[type="date"],
.detail-view select,
.detail-view textarea { font-size: var(--dv-body); }

/* ---- 单据表体 13px；表头 11px；表内副值仍归注解 12px ---- */
.detail-view .table { font-size: var(--dv-item); }
.detail-view .table thead th { font-size: 11px; }
.detail-view .table .cell-sub { font-size: var(--dv-note); }
.detail-view .items-total-value { font-size: var(--dv-item); }

/* ---- Add Entry：Type 下拉收小（他要的是"矮一点"，不追求与日期严格等高） ----
   只作用于那**一行**（`note-add-row`），不碰 Change Status 那个 `.status-form`。 */
.detail-view .note-add-row select { padding: 4px 10px; font-size: 13px; }

/* ===========================================================================
 * 1.88.0-PlPolish —— 右栏：联系表格**归位** + 包装块对齐（Wolfgang 2026-10-18）
 *
 * 他的原话：*"联系人表格右列宽度增加到 35 个字节"* ＋ *"包装和物流信息，与联系人表格的
 * 左列的文字对齐，增加行间距"*。
 *
 * 🔴 **一处根因同时解释了两条**（headless Chrome 实测，真实 PL #322）：
 *    `.doc-infobox` 在 `.doc-rcol` 里被推右了 **24.5px**（`margin-left: 24.5px`）——
 *    于是它既比下面的包装块右移（`.doc-pack` 从 rcol 左边缘起，它自己从 +24.5 起，
 *    标签再叠 6px 的 td padding ⇒ 文字左边缘差 **22.5px**），又白白让值列少了 24.5px。
 *
 *    归零之后（实测数字）：
 *      · 联系表格左边缘 449.8 → **425.3**（= 包装块左边缘）
 *      · 宽度           287.2 → **311.7**
 *      · 值列宽度       172.8 → **194.3px** ≈ **39 个字符宽**（他要 35 ⇒ 达标）
 *      · 两块标签的文字左边缘 **都落在 431.3** —— 逐像素对齐
 *
 * ⚠️ 六张纸共用 `.doc-rcol > .doc-infobox`（他选"六张一起"）。
 * ⚠️ `!important`：要压的是一个来源不明的 `margin-left`（`.doc-infobox` 自己在
 *    2215 行还有一条 `width: 48%`），实测只有带 `!important` 才稳。
 * ========================================================================= */
.doc-rcol > .doc-infobox { margin-left: 0 !important; width: 100% !important; }

/* ===========================================================================
 * 1.90.0-PlBand —— PL 的动态信息带 + 客户 PO 二选一 + 两个红字字段
 *                  （Wolfgang 2026-10-18）
 *
 * 三块互不相干，但都只服务这一批，所以放同一段：
 *
 * ① `.doc-plband` —— PL 纸面上**顶部固定区之下**那条两列带：
 *      左列 PO No.（N 行）/ Invoice No. / 物流 / 备注；右列 Package 那五项。
 *    🔴 用 `1fr 1fr`（不是 `50% 50%`）：两列各半**再扣掉 column-gap**，
 *       写成百分数会溢出 100%（8mm 间隙没有地方出）。
 *    🔴 `align-items: start` 是他那句"PO和package起始于同一行"的实现：
 *       两列各自从带子顶端起，第一行自然齐平。
 *    ⚠️ 两块内部仍复用 `.doc-pack`（`max-content 1fr`）—— 1.88 定下的
 *       标签/值对齐与 6px 内边距因此一字不变。
 *    ⚠️ 只**新增**本类，`.doc-pack` 一个字符没动：老快照里包装块还在 `.doc-rcol`
 *       里，它们照旧按老结构渲染。
 *
 * ② `.po-src` —— **1.108.0-PoOnly 起退役**：客户 PO 不再二选一（他试用后判定 E-mail
 *    那一档没有存在必要），控件回到单框，`assets/po-src.js` 与那段 CSS 一起删。
 *
 * ③ `.field-red` —— PL 编辑栏最后那两个可编辑的纸面值（Invoice No. / PL No.）。
 *    🔴 红字加粗是**他点名的**（*"用红字加粗来显示"*），用品牌外的醒目红
 *       `#c0392b`（与 `.btn-del:hover` 同一个红），不新造颜色。
 * ========================================================================= */
/* 1.91.0-PlShip（Wolfgang 2026-10-19）—— *"在PO No那一行之上，增加一个标题，
   命名为Shipment，字体增粗，左对齐独占一行"*。

   🔴 它是**带子的兄弟**、不是 grid 的一格：进 grid 就会把左列撑高，
      `PO No.` 与 `Package` 立刻不在同一行（1.90 的承诺会破）。
   🔴 `padding-left: 0`（1.95.4-RefFold，Wolfgang 2026-10-19）—— 他：*"PL中关于shipment块，
      与上面公司名称相比，仍有缩进未能实现左对齐到最左侧"*。缩进来自 `.doc-pack` 的
      `padding-left: 6px`（1.88 为"与联系人表格左列文字对齐"加的），标题跟着用了同一个数。
      现在把 **标题与两列的文字一起贴到最左** ⇒ 整条带子的左边缘与上方公司名齐平；
      `column-gap` 与列宽算法一个字没动（两列之间的相对关系不变）。 */
.doc-plband-title {
  padding-left: 0;
  /* 上面 3mm：让标题与"顶部固定区"（地址 / 联系人框）分开一口气；
     下面 2mm：标题属于带子的一部分，不拉开。 */
  margin: 3mm 0 2mm;
  font-size: 10pt;
  font-weight: 700;
  text-align: left;
}

.doc-plband {
  display: grid;
  /* 1.91.0-PlShip —— 从 `1fr 1fr` 改成「左列按内容收、右列吃掉剩下」。
   他：*"栏宽适当。右栏内容左对齐，但尽可能向左栏靠近"*。
   `1fr 1fr` 把右栏硬钉在 50%（≈97mm）处，左列明明只有 46mm 宽，中间白空一大片。

   🔴 用 **`fit-content(58mm)`**，不要用 `minmax(max-content, 58mm)` —— 实测：
      · `max-content 1fr`      ⇒ 短内容 175.7px（贴内容），但长串会顶到 545px，右列被挤成 105px；
      · `minmax(max-content,58mm)` ⇒ **永远等于 58mm**（短内容也 219.2px，"靠近"就没了），
        而且长串时 min > max ⇒ min 胜出 ⇒ 左列 545px（同上）；
      · `fit-content(58mm)`    ⇒ **短内容 175.7px、长内容封顶 219.2px**，两行都正确。
      `fit-content(<length>)` 的语义恰好就是 `min(上限, max-content)` 且下界不小于
      min-content —— 正是"按内容收、但有上界"。
   🔴 上界 58mm 是**量过的**：左列最长的一行是 `Tracking No.:` + 一条运单备注；
      58mm 装得下正常的 PO 号 / 发票号，又不至于把右列压窄。
   ⚠️ 别写成 `auto 1fr`：`auto` 的下界是 min-content，`Tracking No.:` 会被折成两行。 */
  grid-template-columns: fit-content(58mm) 1fr;
  column-gap: 8mm;
  align-items: start;
  margin: 0 0 4mm;
}
/* 带子里的两块自己不要再带下边距（外层已经给了 4mm）；左内边距同标题一起归零
   （1.95.4-RefFold：整条带子左对齐到上方公司名，见 `.doc-plband-title` 那段注释）。
   ⚠️ 只作用于 `.doc-plband` 的**直接子** `.doc-pack` —— CI / PF / 老快照里其它包装块
      仍保留 6px（它们要跟联系人表格的文字对齐）。 */
.doc-plband > .doc-pack { margin: 0; padding-left: 0; }

/* 1.91.0-PlShip —— 左列的上界是 **58mm**，于是值格必须有"长串自己断"的能力：
   他的运单号那一格会写备注（*"编号位置我会写备注"*），发票号也可能是连写的长串。
   🔴 `overflow-wrap: anywhere` 是**必须显式写**的 —— 没有空格的串浏览器不会自己断行，
      结果就是压到右栏、被 `overflow:hidden` 裁掉（1.77 那条丝印丢内容的教训）。
   ⚠️ 只作用于 `.doc-plband` 之内：老快照的包装块还在右栏（`.doc-rcol`），
      它们的分行行为一个字都不动。 */
.doc-plband .doc-pack-value { overflow-wrap: anywhere; }

/* 🔄 1.108.0-PoOnly（Wolfgang 2026-10-27）—— 客户 PO 的"二选一"整套撤掉了：
   `.po-src` / `.po-src-pick` / `.po-src.is-off`（含 1.95.2 那个"radio 被 pointer-events
   一起封死"的修法）。他试用后判定 E-mail 那一档没有存在必要 —— 邮件下单直接写进
   `PO No.` 即可 ⇒ 控件回到单框，这一整块 CSS 与 `assets/po-src.js` 一起退役。
   ⚠️ 别把它们当"没人用的影子规则"再加回来：判据函数 `doc_po_src()` 仍然在，
      但它只为**已签发快照**里的历史 `po_email` 服务，不再有任何界面控件。 */

.field-red label { color: #c0392b; font-weight: 700; }
.field-red input  { color: #c0392b; font-weight: 700; }

/* ===========================================================================
 * 1.89.0-MailSig —— 邮件签名：管理端生成器 + 偏好页预览窗（Wolfgang 2026-10-18）
 *
 * 签名本身是**给邮件客户端的 HTML**（内联样式，由 `lib/mail-signature.php` 渲染）。
 * 这里只管"把它摆在页面上"的容器：白底预览框、代码区、复制按钮那一行。
 *
 * 🔴 `.sig-preview` 的 `background:#fff` 是**硬写的**，不跟主题变量走：
 *    邮件到了客户那里是白底黑字，暗色主题下把预览也弄成深色就等于预览了一个假东西。
 *    （与 A4 预览"永远是白纸"同一条规矩。）
 * ⚠️ 但**代码框**跟着主题走 —— 那里是源码、不是邮件成品，深色底下用深色底更舒服。
 * ⚠️ 本块**不许出现 `.doc-` / `.pagedjs_` 选择器**（m26 的列表 UI 段一直管到文件末尾）。
 * ========================================================================= */
.sig-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 18px;
}
@media (max-width: 560px) { .sig-fields { grid-template-columns: 1fr; } }

.sig-live-note {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0 0 10px;
}

.sig-preview {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 16px;
  overflow-x: auto;
}

.sig-code-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 6px;
}
.sig-code-title {
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .8px;
  color: var(--brand);
}
.sig-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 12px;
  line-height: 1.5;
  min-height: 190px;
  resize: vertical;
  white-space: pre;
  overflow: auto;
}

/* 标签页（User edit：User / Mail signature）—— 两个面板在**同一个 <form>** 里，
   只切显示、不切 DOM，所以藏在 `hidden` 里的输入框照样跟着提交。 */
.tabbar {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--line);
  margin: 0 0 22px;
}
.tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 10px 14px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
}
.tab:hover { color: var(--ink); }
.tab.is-on { color: var(--brand); border-bottom-color: var(--brand); }
/* `[hidden]` 默认是 `display:none`，但任何 `display:` 声明都会压过它 ⇒ 显式钉住。 */
.tabpane[hidden] { display: none !important; }
