/* ============ 包装订单管理系统 ============ */
:root {
  --bg: #f3f5f8;
  --card: #ffffff;
  --line: #e3e7ee;
  --text: #232a35;
  --muted: #6b7280;
  --brand: #1f6feb;
  --brand-dk: #1756c0;
  --green: #2f9e44;
  --red: #e03131;
  --orange: #e8590c;
  --amber: #f08c00;
  --radius: 10px;
  --shadow: 0 1px 3px rgba(20, 30, 60, .08);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/* 关键：横向滑/划到页面边缘时，不让浏览器把"返回/前进"手势接走，
   否则在触屏/触摸板上划一下就会把当前页面（如新建订单）带走 */
html, body { overscroll-behavior-x: none; }
body {
  font-family: "Segoe UI", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", sans-serif;
  background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.5;
}
.hidden { display: none !important; }
a { color: var(--brand); text-decoration: none; }

/* ---------- 登录 ---------- */
#login-view {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  /* ⚠ 方向必须是 column。默认的 row 下，下面那个 .icp-foot 会变成**第二个 flex 项、
     被横排到登录卡片的右边**（症状是「备案号跑到登录框旁边了」）。
     column + center 让它自然落在卡片正下方、水平居中，**一个定位属性都不用加**。
     ⚠ 别为了挪位置给它加 position:fixed/absolute：卡片实测高 ≈369px，矮屏
     （横屏手机 ≈375px，或窗口拖得很矮）时卡片下沿正好落在那条上 ⇒ 备案号横压在
     卡片下沿（2026-09-23 两个方案都实测过，都会压字）；column 排布则让容器跟着
     内容长高、永远不会压。
     ⚠ 别给这里补 `justify-content: safe center`（2026-09-23 试过，是个**恒等变换**）：
     容器是 `height:auto + min-height:100vh`，内容比屏幕高时它会**跟着长到内容那么高**，
     于是容器内**没有富余空间**，`justify-content` 无物可分配 ⇒ center 与 safe center
     必然同解；内容比屏幕矮时两者也都是居中。矮屏上要靠滚动才看得到页脚是正常现象，
     **不是**「居中把顶部裁掉」那一类 bug（那种 bug 需要容器高度被钉死才会出现）。 */
  gap: 14px;
  background: linear-gradient(135deg, #eaf2ff 0%, #f6f8fc 60%);
}
/* 登录页底部那一块：版权行 + ICP 备案号（工信部要求首页挂号并链到 beian.miit.gov.cn）。
   两行之间用 <br> 分行、同吃这一条规则，所以这里没有「行间距」要管。
   位置完全靠上面 `#login-view` 的 flex 排布，**本规则只负责字号和颜色** —— 没有
   left/right/bottom，也没有 position，正因为如此它在任何屏幕高度下都不会压到卡片。
   ⚠ 别把它挪出 `#login-view`：它是靠父级被 .hidden 才跟着消失的（登录后不出现）。 */
.icp-foot {
  font-size: 12px; color: var(--muted); text-align: center;
}
.icp-foot a { color: inherit; }
.icp-foot a:hover { text-decoration: underline; }
.login-card {
  width: 360px; max-width: 92vw; background: var(--card); border-radius: 14px;
  padding: 34px 30px 26px; box-shadow: 0 10px 30px rgba(20, 40, 90, .12);
}
.login-brand { font-size: 20px; font-weight: 700; text-align: center; }
/* 自定义站标（2026-10-16 增量七十）。
   ⚠ `display:block` 是**承重的**：`<img>` 默认是行内元素，会跟右边那个品牌名挤在
     同一行上（症状是「logo 和字并排、还很别扭地对齐在基线上」）。改成块级 + 居中，
     它自然占满一行落在品牌名上方，一个定位属性都不用加。
   ⚠ `max-height` 必须给：用户传的图长宽比完全不受控，不封顶的话一张 200×800 的图
     会把整张登录卡撑出屏幕（`.login-card` 没有 overflow 限制）。
   ⚠ 隐藏仍然靠 `.hidden`（`display:none !important`），这里不写 display 的开关 ——
     两处都管 display 的那种写法，谁的优先级赢取决于选择器权重，很难看出来。 */
.login-logo {
  display: block; margin: 0 auto 10px;
  max-width: 200px; max-height: 80px; object-fit: contain;
}
/* 登陆页设置页里那两块图片预览（管理员页，不在登录页上）。
   高度写死并 `overflow:hidden`：预览区高度随图变化的话，传完图整页会跳一下，
   而"传完了"和"页面抖了一下"同时发生，很容易被当成没生效。 */
.lp-prev-wrap {
  height: 110px; border: 1px dashed var(--line); border-radius: 8px;
  background: #fafbfd; display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.lp-prev { max-width: 100%; max-height: 106px; object-fit: contain; }
.lp-prev-none { color: var(--muted); font-size: 12px; text-align: center; padding: 0 12px; }
.login-sub { color: var(--muted); text-align: center; font-size: 12px; margin: 6px 0 22px; }
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 5px; }
.field input, .field select, .field textarea {
  width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  font-size: 14px; background: #fff; color: var(--text);
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(31,111,235,.12);
}
/* 「登记入库」弹窗里，数量框右边那个「是否完结」勾选框（.qtyslot 是它俩的那一行）。
   ⚠ 上面 `.field input` 是 `width:100%`（表格里的输入框就该那样），勾选框直接塞进去
   会被撑满整行、把数量框挤没 —— 所以这里成对地改：数量框退回弹性宽度、勾选框退回自然宽度。
   选择器都带 `.qtyslot` / `.finish-ck`，权重压过 `.field input` / `.field label`。 */
.qtyslot { display: flex; align-items: center; gap: 10px; }
.qtyslot input[data-k="qty"] { flex: 1 1 auto; min-width: 0; }
.field label.finish-ck {
  display: flex; align-items: center; gap: 5px; flex: 0 0 auto;
  margin: 0; font-size: 13px; color: var(--text); white-space: nowrap; cursor: pointer;
}
.field label.finish-ck input { width: auto; margin: 0; }

/* 新建订单「订单类型」单选胶囊（成品单 / 半成品单 / 定制单） */
.ot-radios { display: flex; flex-wrap: wrap; gap: 8px; }
.ot-radios .ot-r {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px;
  border: 1px solid var(--line); border-radius: 999px; background: #fff;
  font-size: 13px; color: var(--text); cursor: pointer; user-select: none;
}
.ot-radios .ot-r:hover { border-color: var(--brand); }
.ot-radios .ot-r input { width: auto; height: auto; padding: 0; margin: 0; border: none; box-shadow: none; accent-color: var(--brand); cursor: pointer; }
.ot-radios .ot-r input:focus { box-shadow: none; }
.ot-radios .ot-r:has(input:checked) {
  border-color: var(--brand); background: #eef5ff; color: var(--brand-dk); font-weight: 600;
}

/* ---------- 商品设置 · 字段编辑器 ---------- */
.ff-grid { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.ff-grid > .ff-card { flex: 1 1 300px; min-width: 0; }
/* 商品设置 →「商品字段」和「定制产品字段」两张卡：比同行的「材质/规格选项」宽，且两张严格同宽。
   宽度 = basis + (grow/Σgrow)·剩余空间。两张宽卡 basis 相同、grow 相同（1.5），
   所以无论容器多宽，这两张算出来的宽度永远相等 —— 要「一样宽」就得让 basis 和 grow 都一致，
   只改其中一个都会让两张随容器宽度错开。
   basis 330 而非 540：两张宽卡 + 一张窄卡若按 540 起算，1400px 容器里 540×2+300+32 = 1412 放不下，
   「材质/规格选项」会被挤到第二行。330 起算合计 992px 仍在一行，窄屏才照旧换行落整宽。
   1400px 容器下实测 ≈ 483 / 483 / 402。 */
.ff-grid > .ff-card-wide { flex: 1.5 1 330px; }

/* ---------- 系统设置 · 数据维护卡里的并排行 ----------
   四段说明两两并排（②③ 一行、④⑤ 一行），不再一条压一条。
   两栏等宽：basis 相同 + grow 相同 ⇒ 任何容器宽度下两栏严格相等（与上面两张宽卡同一套算法）。
   basis 320：两个 320 + gap 16 = 656px 起才并排，比这窄（手机/窄窗）自动落回上下堆叠。 */
.maint-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.maint-row > * { flex: 1 1 320px; min-width: 0; }
.ff-row {
  display: flex; align-items: flex-start; gap: 10px; padding: 6px 2px;
  border-bottom: 1px solid var(--line);
}
.ff-row:last-child { border-bottom: none; }
.ff-main { flex: 1; min-width: 0; }
.ff-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ff-line input[data-key="label"] {
  flex: 1; min-width: 140px; padding: 7px 10px; border: 1px solid var(--line);
  border-radius: 8px; font-size: 13px; background: #fff; color: var(--text);
}
.ff-line select { padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px; font-size: 13px; background: #fff; }
.ff-line input:focus, .ff-line select:focus { outline: none; border-color: var(--brand); }
.ff-badge {
  font-size: 11px; padding: 2px 7px; border-radius: 10px; white-space: nowrap;
}
.ff-b-builtin { background: #eef1f6; color: #5b6472; }
.ff-b-custom { background: #e6f2ff; color: var(--brand-dk); }
.ff-static { font-size: 12px; color: var(--muted); }
.ff-toggles { margin-top: 6px; display: flex; align-items: center; gap: 12px; font-size: 12px; color: var(--muted); }
.ff-toggles label.ff-chk { display: flex; align-items: center; gap: 4px; cursor: pointer; }
.ff-toggles label.ff-chk input { accent-color: var(--brand); }
.ff-field { margin-top: 6px; max-width: 300px; }
.ff-field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.ff-field input { width: 100%; padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; font-size: 13px; }
.ff-actions { display: flex; align-items: center; gap: 5px; flex: none; }
.ff-actions .btn-sm { padding: 4px 8px; }
.ff-max { font-size: 11px; color: var(--muted); }
.craft-row .ff-line input {
  flex: 1; min-width: 120px; padding: 7px 10px; border: 1px solid var(--line);
  border-radius: 8px; font-size: 13px; background: #fff; color: var(--text);
}
.craft-row .ff-line input:focus { outline: none; border-color: var(--brand); }

/* ---------- 一组胶囊式勾选框 ----------
   用到两处：订单工艺类型（新建订单 / 订单信息编辑），以及商品资料里「复选框」类型的
   自定义字段（见 app.js productFormFields）。类名带 craft 是历史原因，做法是通用的。 */

.craft-checks { display: flex; flex-wrap: wrap; gap: 8px; }
.craft-checks .craft-ck {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px;
  border: 1px solid var(--line); border-radius: 999px; background: #fff;
  font-size: 13px; color: var(--text); cursor: pointer; user-select: none;
}
.craft-checks .craft-ck:hover { border-color: var(--brand); }
.craft-checks .craft-ck input { accent-color: var(--brand); margin: 0; cursor: pointer; }
.craft-checks .craft-ck:has(input:checked) {
  border-color: var(--brand); background: #eef5ff; color: var(--brand-dk); font-weight: 600;
}
/* 订单行里的自定义字段单元格（新建订单页按「新建订单」开关出列；见 app.js cfCellHTML）。
   在窄格子里把控件收紧：输入/下拉限宽别撑破表，胶囊勾选框缩小一号、间距收窄。 */
.cf-cell { min-width: 110px; }
.cf-cell input, .cf-cell select { max-width: 100%; }
.cf-cell .craft-checks { gap: 4px; }
.cf-cell .craft-ck { padding: 3px 8px; font-size: 12px; }

/* 产品明细的「批量填」那一行（新建订单/生产单；它挂在 <thead> 里、紧贴表头，见 app.js batchRowHTML）。
   这一行**必须一眼看出不是数据行**：它跟明细行同构、又紧挨表头，不加底色的话用户会把
   第一行的三个空框看成「这一单的三行」，以为已经填好了。取品牌蓝最淡的一档 ——
   与表头（灰字）分得开，又不至于跟真数据行抢注意力。 */
#no-lines-table tr.batch-row td {
  background: #f2f7ff; border-bottom: 1px solid var(--line); padding: 6px 8px;
  color: var(--brand-dk); font-size: 12px;
}
#no-lines-table tr.batch-row input, #no-lines-table tr.batch-row select { font-size: 12px; }

/* 产品明细里每行的「工艺」列（仅当订单信息勾了工艺才显示，列名/格子联动隐藏） */
#no-lines-table.no-crafts .craft-col,
#no-lines-table.no-crafts .craft-cell { display: none; }
.craft-cell { min-width: 150px; }
.craft-cell .cc {
  display: inline-flex; align-items: center; gap: 3px; margin: 2px 8px 2px 0;
  font-size: 12px; color: var(--text); cursor: pointer; user-select: none; white-space: nowrap;
}
.craft-cell .cc input { accent-color: var(--brand); margin: 0; cursor: pointer; }
.craft-cell .cc:has(input:checked) { color: var(--brand-dk); font-weight: 600; }

/* 「新增/编辑客户」弹窗里那张意向表的工艺格：勾选框**排成一排不折行**。
   `data-intents="modal"` 是意向表容器上的标记（页面详情那份是 "page"），所以这条只作用于
   弹窗 —— 订单行那格（`#no-lines-table`，还是 150px 的窄格）照样折行，那儿折行是对的。
   min-width 只是**下限**：工艺是「商品设置 · 产品工艺」里可配的，条数多了表格会自己变宽
   （`.modal-body` 是 overflow:auto，撑不下就横向滚，不会把勾选框挤没）。 */
[data-intents="modal"] .craft-cell { min-width: 340px; }
[data-intents="modal"] .craft-checks { flex-wrap: nowrap; }
.login-err { color: var(--red); font-size: 13px; min-height: 18px; margin-bottom: 4px; }

/* ---------- 订单列表 · 行内展开（产品明细与进度） ---------- */
.order-expand-row td { padding: 4px 14px 14px; background: #fafbfc; }
.inline-expand { font-size: 13px; }
.ie-head { display: flex; align-items: center; gap: 14px; margin: 6px 0 10px; }
.ie-title { font-weight: 600; font-size: 14px; }
.ie-link { color: var(--brand); font-size: 12px; cursor: pointer; text-decoration: none; }
.ie-link:hover { text-decoration: underline; }
.tbl-compact { font-size: 12px; }
.tbl-compact th, .tbl-compact td { padding: 5px 8px; }
.login-tip { margin-top: 16px; font-size: 12px; color: #a0a7b4; text-align: center; }
.btn-block { width: 100%; }

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  border: 1px solid transparent; border-radius: 8px; padding: 7px 14px;
  font-size: 13px; cursor: pointer; background: #eef1f6; color: var(--text);
  transition: filter .12s, background .12s; white-space: nowrap; user-select: none;
}
.btn:hover { filter: brightness(.97); }
.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-dk); }
.btn-green { background: var(--green); color: #fff; }
.btn-red { background: #fff; color: var(--red); border-color: #f3c1c1; }
.btn-red:hover { background: #fdecec; }
.btn-ghost { background: transparent; color: #d7deea; border-color: rgba(255,255,255,.22); }
.btn-sm { padding: 3px 9px; font-size: 12px; border-radius: 7px; }
.btn-xs { padding: 1px 8px; font-size: 12px; border-radius: 6px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* ---------- 整体布局（左侧竖菜单 + 右侧页签区） ---------- */
.layout { display: flex; height: 100vh; overflow: hidden; }
.sidebar {
  flex: none; width: 108px; background: #f7f8fa; border-right: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0;
}
.sidenav { flex: 1; overflow-y: auto; padding: 8px 6px; }
/* 版本号（2026-10-27 增量九十一）：左边栏最底下两行 —— 名称 + 版本号。
   ⚠ 两行都**单行省略**。名称是管理员能改的品牌名，一个长名字不加省略会把整个侧栏
     往下撑，菜单被挤到看不见 —— 而那种坏法看起来只是「今天菜单变短了」。
   ⚠ 位置靠 `.sidebar` 这个 flex 列的最后一项，**不要**给它加 position/left/bottom：
     窄屏（≤900px）侧栏变成横向条，靠定位写死的会跑到屏幕中间去。 */
.app-ver {
  flex: none; padding: 8px 8px 10px; border-top: 1px solid var(--line);
  font-size: 10px; line-height: 1.5; color: #98a2b3; text-align: center;
}
.app-ver div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sn-item {
  display: flex; align-items: center; gap: 7px; padding: 7px 8px; margin-bottom: 2px;
  border-radius: 6px; cursor: pointer; color: #3a4352; font-size: 13px; user-select: none; white-space: nowrap;
}
.sn-item .ic { flex: none; width: 15px; text-align: center; }
.sn-item:hover { background: #eef1f6; color: var(--text); }
.sn-item.active { background: var(--brand); color: #fff; font-weight: 600; }
/* 右侧飞散子菜单：悬停/点击一级项，在它右边弹出一个浮层框 */
.sn-hint { flex: none; font-size: 10px; color: var(--muted); margin-left: auto; }
.sn-flyout {
  position: fixed; display: none; min-width: 150px; z-index: 70;
  background: #fff; border: 1px solid var(--line); border-radius: 9px; padding: 5px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); white-space: nowrap;
}
.sn-flyout.open { display: block; }
.sn-flyout-item {
  display: block; padding: 7px 10px; border-radius: 6px; cursor: pointer;
  font-size: 12px; color: #3a4352; user-select: none;
}
.sn-flyout-item:hover { background: #eef1f6; color: var(--text); }
.sn-flyout-item.active { background: #e3ecfb; color: var(--brand); font-weight: 600; }
/* 右上角：用户下拉菜单（与页签同行） */
.tabbar-user { flex: none; position: relative; margin-left: auto; padding: 0 8px 0 12px; border-left: 1px solid var(--line); display: flex; align-items: center; }
/* 当前公司那颗胶囊。**默认隐藏**，由 coChipRender() 打开 —— 单人单公司时它整块不出现，
   顶栏跟改造前一模一样。 */
.co-chip-wrap { position: relative; display: none; }
.co-chip-wrap.on { display: block; }
.co-chip {
  display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 10px;
  border: 1px solid var(--line); background: #f7f9fc; border-radius: 15px;
  font: inherit; font-size: 12px; color: var(--text); cursor: default; max-width: 200px;
}
.co-chip.can-switch { cursor: pointer; }
.co-chip.can-switch:hover { background: #eef1f6; }
.co-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: #37b26c; }
#co-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.co-caret { flex: none; font-size: 9px; color: var(--muted); }
/* 只有点得开的时候才画那个小三角 —— 不能切的人看到一个箭头会去点它 */
.co-chip:not(.can-switch) .co-caret { display: none; }
.co-menu-head { padding: 6px 10px 7px; font-size: 12px; color: var(--muted); }
.co-item.cur { color: var(--brand); font-weight: 600; }
.co-item .co-tick { float: right; color: var(--brand); }
.co-sep { height: 1px; background: var(--line); margin: 4px 0; }
.tu-trigger {
  display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px;
  border: none; background: transparent; border-radius: 8px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text); white-space: nowrap;
}
.tu-trigger:hover { background: #eef1f6; }
.tu-avatar {
  flex: none; width: 22px; height: 22px; border-radius: 50%; background: var(--brand); color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600;
}
.tu-name { font-size: 12px; color: var(--text); max-width: 120px; overflow: hidden; text-overflow: ellipsis; }
.tu-caret { flex: none; font-size: 10px; color: var(--muted); }
.dropmenu {
  position: absolute; right: 4px; top: calc(100% + 4px); min-width: 158px; z-index: 60;
  background: #fff; border: 1px solid var(--line); border-radius: 9px; padding: 5px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12); display: none;
}
.dropmenu.open { display: block; }
.dm-head {
  padding: 7px 10px 8px; border-bottom: 1px solid var(--line); margin-bottom: 4px;
  font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dm-item {
  display: block; width: 100%; text-align: left; padding: 8px 10px;
  border: none; background: transparent; border-radius: 6px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text);
}
.dm-item:hover { background: #eef1f6; }
/* 总看板的「合计」行：钉在表尾、跟上面几家的行明显分开。
   只加粗+底色，不换颜色 —— 这一行是同样口径的加法结果，不是「异常」。 */
.tbl tbody tr.row-total td { border-top: 2px solid var(--line); background: #f7f9fc; font-weight: 600; }

.main-col {
  flex: 1; min-width: 0; height: 100vh; display: flex; flex-direction: column;
  background: var(--bg);
}

/* ---------- 顶部页签条 ---------- */
.tabbar {
  flex: none; height: 40px; background: #fff; border-bottom: 1px solid var(--line);
  display: flex; align-items: stretch; padding: 0 8px; z-index: 30;
}
.tablist {
  flex: 1; min-width: 0; display: flex; align-items: stretch; overflow-x: auto;
  overscroll-behavior-x: contain; scrollbar-width: none;
}
.tablist::-webkit-scrollbar { display: none; }
.tab {
  display: inline-flex; align-items: center; gap: 5px; padding: 0 10px 0 14px;
  white-space: nowrap; font-size: 13px; color: var(--muted); cursor: pointer;
  border-bottom: 2px solid transparent; border-right: 1px solid #eef1f4;
  user-select: none; flex: none;
}
.tab:hover { background: #f7f9fc; color: var(--text); }
.tab.active { color: var(--brand); font-weight: 600; border-bottom-color: var(--brand); background: #f2f7ff; }
.tab .pin { font-size: 11px; }
.tab-x {
  border: none; background: none; cursor: pointer; color: #aab2bf; font-size: 14px;
  line-height: 1; padding: 0 0 0 4px; margin-left: 2px; border-radius: 4px;
}
.tab-x:hover { color: var(--red); }

/* ---------- 页签容器 ---------- */
#pane-root { flex: 1; min-height: 0; overflow: auto; background: var(--bg); }
.tabpane { display: none; padding: 14px 18px 60px; }
.tabpane.active { display: block; }
/* 模块页内顶部工具行 */
.page-title { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 14px; }
.page-title h2 { margin: 0; font-size: 18px; }
.page-title .sub { color: var(--muted); font-size: 13px; }
.page-title .pt-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* ---------- 列表页：左筛选 + 右内容 ---------- */
.list-page { display: flex; align-items: flex-start; gap: 14px; }
.filters {
  /* 224 → 268（2026-10-22 用户：「左边的筛选框再宽一点」）。原来那 224px 里，
     财务报表的「起止月份 / 起至年份」两个原生日期框 + 一个「至」根本排不下，
     **右边那个框被挤到看不见**（栏里有 overflow:auto，可它是被挤窄、不是被推出去，
     所以连滚动条都不出现）。 */
  flex: none; width: 268px; background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px;
  position: sticky; top: 6px; max-height: calc(100vh - 120px); overflow: auto;
}
/* 原生日期/月份框在这条栏里**没有任何宽度规则**：它按内容撑开，两个并排就超宽。
   和上面 select 同一条口径（flex:1 + min-width:0 才允许被压窄）。 */
.filters .fi-row > input[type="month"],
.filters .fi-row > input[type="date"] { flex: 1 1 0; width: auto; min-width: 0; }
.fi-title { font-size: 12px; color: var(--muted); margin: 14px 0 8px; font-weight: 600; }
.fi-title:first-child { margin-top: 2px; }
.fi-row { display: flex; gap: 6px; margin-bottom: 8px; }
.fi-row .search { flex: 1; width: auto; }
/* 左筛选里的下拉（入库记录页的 入库人 / 产品编码）。裸 select 在这条筛选栏里既没宽度
   规则也没内边距：`width` 取内容宽，长编码会把 224px 的栏顶破。跟 .fi-row .search 同宽口径。 */
.fi-row select {
  flex: 1; width: auto; min-width: 0; border: 1px solid var(--line); border-radius: 8px;
  padding: 5px 8px; font-size: 13px; background: #fff; color: #3a4352;
}
.fi-radio {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; text-align: left; border: none; background: none; cursor: pointer;
  padding: 6px 10px; border-radius: 7px; font-size: 13px; color: #3a4352; margin-bottom: 2px;
}
.fi-radio:hover { background: #f2f5fa; }
.fi-radio.on { background: #e7f0ff; color: var(--brand-dk); font-weight: 600; }
.fi-radio .cnt { font-size: 12px; opacity: .6; font-variant-numeric: tabular-nums; }
.content { flex: 1; min-width: 0; }

/* ---------- 数据看板：顶部 KPI 卡排 + 订单页式左筛选 + 表内滚动 ---------- */
.filters .fi-row > select {
  flex: 1; min-width: 0; width: 100%;
  border: 1px solid var(--line); border-radius: 20px; padding: 5px 10px;
  font-size: 13px; background: #fff; color: var(--text);
}
.filters .fi-row > select:focus { outline: none; border-color: var(--brand); }
/* 销售报表左栏的「起止日期」（增量八十）：224px 的栏里并排两个日期框。
   不给 min-width:0，浏览器给日期控件的固有宽度会把这一栏顶破。 */
.sr-range input[type="date"] {
  flex: 1; min-width: 0; width: auto;
  border: 1px solid var(--line); border-radius: 8px; padding: 4px 6px;
  font-size: 12px; background: #fff; color: var(--text);
}
.sr-range input[type="date"]:focus { outline: none; border-color: var(--brand); }
#bd-kpis { display: flex; gap: 10px; flex-wrap: wrap; padding: 0 0 14px; flex: none; }
#bd-kpis .kpi {
  flex: 1 1 0; min-width: 118px; max-width: 200px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 9px 12px 8px;
}
#bd-kpis .kpi .k { font-size: 20px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--text); }
#bd-kpis .kpi .t { color: var(--muted); font-size: 12px; margin-top: 3px; white-space: nowrap; }
#bd-kpis .kpi.ud .k { color: var(--amber); }
#bd-kpis .kpi.od .k { color: var(--brand); }
#bd-kpis .kpi.dl .k { color: var(--green); }
#bd-tblwrap { min-height: 140px; }
#bd-tblwrap .bd-empty { padding: 44px 16px; text-align: center; color: var(--muted); }

/* 看板关键字框下面的快捷词按钮（词表来自 系统设置 → 看板快捷关键词）。
   走 flex-wrap：词多/词长时自动换行，不撑破左筛选栏。 */
.filters .fi-qks { flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.filters .fi-qk {
  border: 1px solid var(--line); background: #fff; color: #3a4352;
  border-radius: 999px; padding: 3px 10px; font-size: 12px; cursor: pointer;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filters .fi-qk:hover { background: #f2f5fa; }
.filters .fi-qk.on { background: #e7f0ff; border-color: var(--brand); color: var(--brand-dk); font-weight: 600; }

/* ---------- 页签右键菜单 ---------- */
.ctxmenu {
  position: fixed; z-index: 150; min-width: 150px; background: #fff;
  border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 30px rgba(20, 30, 60, .16); padding: 5px;
}
.ctxmenu .cm-item {
  display: block; width: 100%; text-align: left; border: none; background: none;
  padding: 7px 12px; border-radius: 7px; font-size: 13px; cursor: pointer; color: var(--text);
}
.ctxmenu .cm-item:hover { background: #f0f4fa; }
.ctxmenu .cm-sep { height: 1px; background: var(--line); margin: 4px 6px; }
.ctxmenu .cm-item.danger { color: var(--red); }
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.spacer { flex: 1; }

/* ---------- 卡片 ---------- */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 16px; margin-bottom: 16px;
}
.card h3 { margin: 0 0 12px; font-size: 15px; display: flex; align-items: center; gap: 8px; }
.card h3 .sub { font-weight: normal; color: var(--muted); font-size: 12px; }
/* 窄卡：整页三分之一宽。min-width 兜住窄屏，别把里面的按钮组挤扁 */
.card.card-third { width: 33.333%; min-width: 280px; }
/* 半宽卡：放 .ff-grid 里两张并排。basis 扣掉 gap，min-width 让窄屏自动落回整宽。
   保留 .card 自带的 margin-bottom —— flex 容器的行高把它算进去，正好和下一张卡留出间距 */
.card.card-half { flex: 1 1 calc(50% - 8px); min-width: 280px; }

/* ---------- 统计方块 ---------- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; margin-bottom: 16px; }
.tile {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 14px 16px; cursor: pointer;
}
.tile:hover { border-color: var(--brand); }
.tile .n { font-size: 28px; font-weight: 700; line-height: 1.1; }
.tile .t { color: var(--muted); font-size: 13px; margin-top: 2px; }
.tile.red .n { color: var(--red); }
.tile.amber .n { color: var(--amber); }
.tile.green .n { color: var(--green); }
.tile.blue .n { color: var(--brand); }

/* ---------- 表格 ---------- */
table.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tbl-grid { table-layout: fixed; }   /* 产品明细表：按 colgroup 固定列宽，表头/数据逐列对齐 */
table.tbl th {
  text-align: left; color: var(--muted); font-weight: 600; font-size: 12px;
  padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
table.tbl td { padding: 8px; border-bottom: 1px solid #eef1f6; vertical-align: top; }
table.tbl tbody tr.clickable { cursor: pointer; }
table.tbl tbody tr.clickable:hover { background: #f6f9ff; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
/* 数字列的表头也要靠右 —— 光有 `.num` 不够：`table.tbl th`（0,1,2）比 `.num`（0,1,0）
   更特异，一路把表头摁在左边，于是「金额」两个字在左、下面的数字在右，列看起来是歪的。
   这条（0,2,2）才压得住它。 */
table.tbl th.num { text-align: right; }
/* 订单列表这类整表左对齐的表：表头和数据都得回到左边，所以 th/td 分别写死。
   （只写 `.num` 的话特异性（0,2,1）低于上面那条表头规则，表头会自己跑回右边。） */
table.tbl-left th.num, table.tbl-left td.num { text-align: left; }
tr.row-red td { background: #fff4f4; }
tr.row-mute td { color: #9aa2af; }
tr.row-over td { background: #fdeaea; }
.ov-badge, .chip {
  display: inline-block; padding: 1px 8px; border-radius: 20px; font-size: 12px; line-height: 1.6;
}
.ov-badge { background: var(--red); color: #fff; font-weight: 600; }
.chip-blue { background: #e7f0ff; color: var(--brand-dk); }
.chip-green { background: #e4f6e7; color: #1b7f30; }
.chip-red { background: #fdeaea; color: var(--red); }
.chip-gray { background: #eef1f6; color: var(--muted); }
.chip-amber { background: #fff3e0; color: var(--amber); }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.mono { font-variant-numeric: tabular-nums; font-family: Consolas, monospace; }
/* 单号前的订单类型标记：成=成品  定=定制  半=半成品生产单 */
.ortag { display: inline-block; min-width: 18px; padding: 0 3px; margin-right: 6px; border-radius: 4px;
  text-align: center; color: #fff; font-size: 11px; line-height: 18px; font-weight: 600; vertical-align: 1px; }
.ortag.ot-f { background: var(--brand); }   /* 成 普通订单 */
.ortag.ot-c { background: #9b59b6; }        /* 定 定制订单 */
.ortag.ot-h { background: var(--orange); }  /* 半 半成品生产单 */

/* 阶段徽标 */
.st { display: inline-block; padding: 2px 9px; border-radius: 6px; font-size: 12px; white-space: nowrap; }
.st-pending { background: #eef1f6; color: #5b6472; }
.st-semi { background: #e7f0ff; color: var(--brand-dk); }
.st-finished { background: #e4f6e7; color: #1b7f30; }

/* ---------- 面板分区 ---------- */
.panel-title { font-weight: 600; margin: 4px 0 10px; display: flex; align-items: center; gap: 8px; }
.panel-title .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot-red { background: var(--red); }
.dot-green { background: var(--green); }
.dot-blue { background: var(--brand); }
.dot-amber { background: var(--amber); }
.emptynote { color: var(--muted); text-align: center; padding: 26px 0; }

/* ---------- 动态 feed ---------- */
.feed-item {
  display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px dashed #eef1f6; align-items: baseline;
}
.feed-item .at { color: #a0a7b4; font-size: 12px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.feed-item .who { color: var(--muted); font-size: 12px; white-space: nowrap; }
.feed-item .what { flex: 1; }
.feed-item a { color: var(--text); }
.feed-item:hover a { color: var(--brand); }

/* ---------- 筛选条 ---------- */
.filter-bar { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; align-items: center; }
.fbtn { border: 1px solid var(--line); background: #fff; padding: 4px 12px; border-radius: 20px; cursor: pointer; font-size: 13px; color: var(--text); }
.fbtn.active { background: var(--brand); border-color: var(--brand); color: #fff; }
.fbtn .cnt { opacity: .7; font-size: 12px; margin-left: 2px; }
.search {
  border: 1px solid var(--line); border-radius: 20px; padding: 5px 12px; width: 200px; font-size: 13px; background: #fff;
}

/* ---------- 订单详情 ---------- */
.oid-title { font-size: 16px; font-weight: 700; }
.oid-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px 22px; margin-top: 12px; }
.oid-meta .mi label { display: block; font-size: 12px; color: var(--muted); }
.oid-meta .mi .v { font-size: 15px; }
.line-note { color: var(--muted); font-size: 12px; }
/* 「行备注」那一格（订单详情页 / 订单列表行内展开）：整格可点，点开小窗改。
   min-height 不能省：没备注的行只有一行「—」，省了它这一格会塌成一条线，看着像列没渲染出来。
   hover 底色只是告诉用户「这儿能点」—— 那一列里还有 muted 的「—」，不给点就没法区分。 */
.note-cell { min-height: 1.4em; padding: 1px 4px; margin: -1px -4px; border-radius: 4px; cursor: pointer; }
.note-cell:hover { background: #eef3fc; }
/* ══ 行备注长到了一格装不下时：**就地换行**，不许它横着铺出去 ══
   ★ 2026-09-30 用户报「行备注太长会挡住其它字段的位置」。根因不是列宽被撑开 ——
     这三张表都是 `table-layout: fixed`（`table.tbl-grid`），列宽由 colgroup 写死，
     单元格**撑不宽列**；超长内容只会**溢出到格子外面、盖住右边那一列**。
     所以修法是让这一格自己断行，跟列宽一点关系都没有（别去动 DETAIL_COLS 那一串百分比）。
   · `white-space: normal` 必须显式写：同族那几格（表头、按钮）是 `nowrap`，
     不明写容易被后加的通用规则一带就跑。
   · `overflow-wrap: anywhere` 管的是**一连串没有空格的字符**（长商品编码 / 网址 /
     连着敲的一长串数字）—— 只写 normal，中文能断、这些照样横着铺出去，就是用户看到的那种「挡住」。
   · 看板那张表（`data-board-exp`）的备注格没有 `.note-cell` 那层 div（它不可点），
     所以直接给那个 `<td>` 挂 `.note-td`，两条选择器共用一个规则。 */
.note-cell, .note-td {
  white-space: normal; overflow-wrap: anywhere; word-break: break-word; line-height: 1.35;
}
.del-chip { display: inline-flex; align-items: center; gap: 5px; background: #f3f6fb; border-radius: 8px; padding: 2px 7px; margin: 2px 6px 2px 0; font-size: 12px; }
.actioncell { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
/* 订单详情相关表：操作列按钮与表头「操作」一起左对齐（列表主表 / 行内展开 / 单笔详情明细） */
table[data-tblk="orders"] .actioncell,
table[data-tblk="orders-exp"] .actioncell,
table[data-tblk="order-detail"] .actioncell { justify-content: flex-start; }

/* ---------- 弹窗 ---------- */
#modal-root { position: fixed; inset: 0; z-index: 100; display: none; }
#modal-root.on { display: flex; align-items: center; justify-content: center; background: rgba(15,23,42,.45); }
.modal {
  background: #fff; border-radius: 14px; width: 560px; max-width: 94vw; max-height: 88vh;
  display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,.25);
}
/* 宽版弹窗（目前只有「新增/编辑客户」）——那张意向产品表列多，560px 里会挤成一团；
   工艺那格还要把勾选框排成一排（那两条作用域规则在 `.craft-cell` 那一段下面）。
   max-width: 94vw 那条继续兜窄屏，所以手机上不会被撑出界。 */
.modal.modal-wide { width: 1240px; }
.modal-head { padding: 14px 18px; font-weight: 700; font-size: 15px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
.modal-body { padding: 16px 18px; overflow: auto; }
.modal-foot { padding: 12px 18px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.fs { font-size: 12px; color: var(--muted); }
/* 「新增/编辑客户」里的 省 / 市 / 区县 三个下拉排成一行。
   ⚠ 三个都要 `min-width:0`：`<select>` 的默认最小宽度是「最长那个选项的宽度」，
   而「区 / 县」那一栏里最长的区名很长，不给 0 的话第三格会把整行顶破、
   把右边那个「全地址」框挤出去（grid 的 1fr 拦不住内容的最小宽度）。 */
.area-row { display: flex; gap: 6px; }
.area-row select { flex: 1 1 0; min-width: 0; }
.area-row select:disabled { background: var(--bg); color: var(--muted); }
.modal .field { margin-bottom: 10px; }



/* ---------- toast ---------- */
#toast {
  position: fixed; left: 50%; bottom: 30px; transform: translateX(-50%);
  background: #1d2533; color: #fff; padding: 9px 18px; border-radius: 30px;
  font-size: 14px; z-index: 200; box-shadow: 0 6px 20px rgba(0,0,0,.25);
}
#toast.err { background: var(--red); }

/* ---------- misc ---------- */
.grid-cust { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.cust-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 13px 15px; }
.cust-card .nm { font-weight: 600; font-size: 15px; }
.cust-card .meta { color: var(--muted); font-size: 12px; margin-top: 3px; }
.cust-actions { margin-top: 9px; display: flex; gap: 6px; flex-wrap: wrap; }
.kbd-hint { font-size: 12px; color: #a0a7b4; margin: 6px 0; }
.lead-form .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.lead-form .row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
.lead-form .row4 { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px; }

/* 明细行内输入控件 */
table.tbl input, table.tbl select {
  padding: 5px 7px; border: 1px solid var(--line); border-radius: 7px;
  font-size: 13px; background: #fff; color: var(--text);
}
table.tbl input:focus { outline: none; border-color: var(--brand); }
/* 新建订单「产品明细」的商品编码：只显示、打不了字。去掉边框与底色，读起来就是「直接显示」，
   但仍是真正的 <input>（三处接线靠 .value 取值/赋值，换成 span 会静默失效）。
   选择器钉住 [readonly]：哪天有人把 readonly 摘了、编码又能手打了，这一格会立刻变回输入框的样子。
   只动边框和底色，**别动 padding**：全局 `* { box-sizing: border-box }` 下 width:90px 是含边框的，
   透明边框仍占着那 1px，所以保留基础 padding 才能让编码文字和旁边「材质/规格」「数量」的文字
   精确对齐在同一个 x 上；写 padding-left:0 会让它整格往左飘 7px。 */
table.tbl input[data-lk="product_code"][readonly] {
  border-color: transparent; background: transparent;
  cursor: default; color: var(--muted);
}
table.tbl input[data-lk="product_code"][readonly]:focus { border-color: transparent; }
.w70 { width: 70px; } .w90 { width: 90px; } .w120 { width: 120px; } .w140 { width: 140px; }
.num-i { text-align: right; }
.btn-lg { padding: 10px 24px; font-size: 15px; }
.modal .x { border: none; background: none; font-size: 15px; color: #98a1b0; cursor: pointer; padding: 2px 6px; }
.modal .x:hover { color: var(--red); }
.del-chip .btn { padding: 0 5px; font-size: 11px; border: none; color: var(--red); background: none; }

/* ---------- 商品资料 ---------- */
/* 行内产品名 + “选”按钮 */
.pkcell, .pkline { display: flex; align-items: center; gap: 6px; }
.pkcell .w120 { flex: 1; width: auto; min-width: 90px; }
.pkline input[data-k="product"] { flex: 1; }
.btn-pick {
  flex: none; padding: 4px 8px; font-size: 12px; border-radius: 6px; cursor: pointer;
  background: #e7f0ff; color: var(--brand-dk); border: 1px solid #cfe0fa; white-space: nowrap;
}
.btn-pick:hover { background: #dbe9ff; }

/* 商品选择弹窗列表 */
#pp-list { max-height: 320px; overflow: auto; border: 1px solid var(--line); border-radius: 10px; }
.pp-item {
  display: flex; align-items: baseline; gap: 10px; padding: 8px 12px; cursor: pointer;
  border-bottom: 1px solid #f0f3f8;
}
.pp-item:last-child { border-bottom: none; }
.pp-item:hover { background: #f0f6ff; }
.pp-code { flex: none; min-width: 80px; }
.pp-name { flex: 1; font-weight: 600; }
.pp-spec { flex: 1; text-align: right; font-size: 12px; }

/* 多选模式（明细行的「选」按钮）：每行左侧一个勾选框 + 下面一条全选/计数条。
   方块尺寸抄 #prod-selall，但那条被 ID 限死在商品列表里，这里用不上。 */
#pp-list .pp-multi { align-items: center; }   /* 原来的 baseline 会让方块跟文字错位 */
#pp-list .pp-sel { flex: none; width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }
#pp-list .pp-on { background: #eef5ff; }      /* 勾中的整行染色，扫一眼就知道选了哪几条（盖得住 hover） */
#pp-bar { margin: 10px 0 0; }
#pp-bar .pp-selall { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }
#pp-bar .pp-selall:disabled { cursor: not-allowed; }

/* 导入预览行 */
.pp-imp-row {
  display: flex; align-items: center; gap: 8px; padding: 5px 4px;
  border-bottom: 1px dashed #eef1f6; font-size: 13px;
}
.pp-imp-row .mono { flex: none; min-width: 70px; }
.pp-imp-row > span:nth-child(3) { flex: 1; }
.pp-imp-row .chip { flex: none; }

/* ---------- 窄屏：竖排折叠（不破版即可） ---------- */
@media (max-width: 900px) {
  .layout { flex-direction: column; height: auto; min-height: 100vh; overflow: visible; }
  .sidebar {
    width: 100%; flex-direction: row; align-items: center;
    border-right: none; border-bottom: 1px solid var(--line); flex: none;
  }
  .sidebar-logo { height: auto; border-bottom: none; flex: none; }
  .sidenav { display: flex; overflow-x: auto; padding: 6px; flex: 1; }
  .sn-item { flex: none; margin-bottom: 0; }
  .tu-name { display: none; }
  .tabbar-user { padding-left: 6px; }
  .main-col { height: auto; min-height: calc(100vh - 52px); }
  .tabpane { padding: 12px; }
  .list-page { flex-direction: column; }
  .filters { width: 100%; position: static; max-height: none; }
  .search { width: 130px; }
  /* 窄屏：侧栏变成横条，版本号自然落到右端（是这一行的最后一个 flex 项）。
     去掉那条上边框 —— 横条里它会在两行小字上面多出一道竖不起来的分隔线。 */
  .app-ver {
    border-top: none; padding: 0 10px 0 6px; text-align: right;
    max-width: 42%; align-self: center;
  }
}

/* ---------- 商品资料：批量选择/删除（主管及以上） ---------- */
.selbar { display: flex; align-items: center; gap: 12px; margin: 0 0 10px; }
.selbar .prod-del:disabled { opacity: .5; cursor: not-allowed; }
#prod-table .sel-col { width: 34px; text-align: center; }
#prod-table .sel-col input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }
#prod-selall { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }

/* ---------- 订单列表：只勾选、不删除，所以只需要列宽 + 方块两件事 ---------- */
/* 订单表没有 id（是 data-tblk="orders"），上面那三条 #prod-table 的选择器吃不到它。
   勾选列的类名必须逐字是 sel-col：列宽机制（decorateTable / UI_CW）靠它把这一列
   排除在「可拖列」之外，改个名字会让所有老用户已存的订单列宽被静默丢掉。 */
[data-tblk="orders"] .sel-col { width: 34px; text-align: center; }
[data-tblk="orders"] .sel-col input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }
#ord-selall { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }

/* ---------- 补货计划：勾选列（增量八十七，只为「批量下单」服务） ---------- */
/* 类名同样必须逐字是 sel-col：列宽机制靠它把这一列排除在「可拖列」之外。 */
[data-tblk="jst-replenish"] .sel-col { width: 34px; text-align: center; }
[data-tblk="jst-replenish"] .sel-col input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }

/* ---------- 四个列表页：锁高布局（左筛选+表头钉住，只滚表格区） ---------- */
.tabpane.flush.active {
  display: flex; flex-direction: column;
  height: 100%; overflow: hidden; padding: 14px 18px 16px;
}
.tabpane.flush.active .page-title { flex: none; }
.tabpane.flush.active .list-page { flex: 1 1 auto; min-height: 0; }
.tabpane.flush.active .filters { align-self: flex-start; }
.tabpane.flush.active .content {
  align-self: stretch; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; overflow: hidden;
}
.tabpane.flush.active .tblwrap {
  flex: 1 1 auto; min-height: 0; overflow: auto;
  background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
/* 滚动容器内钉住表头：用 separate，避免 border-collapse 让 sticky 失效。
   只作用于 tblwrap 的直接子表（行内展开的嵌套表不粘） */
.tabpane.flush.active .tblwrap > table.tbl { border-collapse: separate; border-spacing: 0; }
.tabpane.flush.active .tblwrap > table.tbl > thead th {
  position: sticky; top: 0; z-index: 2; background: #fff;
}
.tabpane.flush.active .pagerbar { flex: none; }

/* 底部状态条：共 N 条 · 每页 X 条 · 第 Y/Z 页 · 翻页按钮 */
.pagerbar {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 10px 2px 0; font-size: 13px;
}
.pagerbar .pg-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.pagerbar .pg-btns { display: inline-flex; gap: 5px; }
.pg-psel {
  padding: 3px 6px; border: 1px solid var(--line); border-radius: 7px;
  background: #fff; color: var(--text); font-size: 13px; cursor: pointer;
}
.pg-psel:focus { outline: none; border-color: var(--brand); }
.pg-btn {
  min-width: 30px; height: 26px; padding: 0 9px;
  border: 1px solid var(--line); background: #fff; border-radius: 7px;
  font-size: 13px; line-height: 1; color: var(--text); cursor: pointer;
}
.pg-btn:hover:not(:disabled) { border-color: var(--brand); color: var(--brand-dk); }
.pg-btn:disabled { opacity: .45; cursor: not-allowed; }

/* 窄屏：恢复整页滚动，不做锁高 */
@media (max-width: 900px) {
  .tabpane.flush.active { display: block; height: auto; overflow: visible; padding: 12px; }
  .tabpane.flush.active .list-page { flex: none; }
  .tabpane.flush.active .content { display: block; overflow: visible; }
  .tabpane.flush.active .tblwrap { max-height: 62vh; }
}

/* ---------- 列宽拖拽 + 右下角悬浮操作钮 ---------- */
table[data-tblk] th { position: relative; }
table[data-tblk].tbl-fix { table-layout: fixed; }
table[data-tblk] .lk-hdl {
  position: absolute; top: 0; right: -3px; height: 100%; width: 9px;
  cursor: col-resize; z-index: 4; touch-action: none; user-select: none;
}
table[data-tblk] .lk-hdl::after {
  content: ""; position: absolute; right: 3px; top: 8px; bottom: 8px; width: 2px;
  background: rgba(31, 111, 235, 0); transition: background .12s; border-radius: 1px;
}
table[data-tblk] .lk-hdl:hover::after,
body.lkdrag table[data-tblk] .lk-hdl::after { background: rgba(31, 111, 235, .5); }
body.lkdrag { cursor: col-resize; user-select: none; }

#ui-cluster {
  position: fixed; right: 16px; bottom: 14px; z-index: 45;
  display: flex; align-items: center; gap: 8px;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  padding: 6px 8px; box-shadow: 0 4px 14px rgba(20, 30, 60, .14);
}
#ui-cluster.hidden { display: none !important; }
/* 悬浮钮常驻右下角：给 flush 列表底部分页条让出右侧空间，避免遮住“末页/下一页” */
.tabpane.flush.active .pagerbar { padding-right: 250px; }
/* 交付看板这一页不是 .flush（是一摞卡片），吃不到上面那条 ⇒ 分页条得单独让位。
   这一页的悬浮钮确实在（UI_SUPPORTED 含 dashboard）。 */
.card .pagerbar { padding-right: 250px; }
@media (max-width: 900px) {
  #ui-cluster { right: 8px; bottom: 8px; }
  .tabpane.flush.active .pagerbar { padding-right: 0; }
  .card .pagerbar { padding-right: 0; }
}

/* ---------- 数据看板（未完成订单按商品编码汇总，风格同订单详情） ---------- */
.bd-expand-row td { padding: 4px 14px 14px; background: #fafbfc; }
.bd-caret { font-size: 12px; white-space: nowrap; }

/* ---------- 半成品子系统 ---------- */
/* 新建订单「可用半成品 / 用半成品加工」两列 */
/* 新建订单「未交付」列（成品/定制）：只读数字，选完商品后由 refreshLineOpenQty 填。
   刻意**不设颜色** —— 数据看板那一列就是裸 <b>，本列照抄；涂橙/红会被读成「逾期」，
   而逾期是看板另一列 ov-badge 的事，两码事。也不设 color: var(--green)，
   免得跟右边「可用半成品」的绿看成一回事。 */
.open-qty-cell { min-width: 70px; white-space: nowrap; }
.semi-avail-cell { min-width: 84px; white-space: nowrap; }
.semi-avail-cell b { color: var(--green); }
.semi-use-cell { min-width: 108px; white-space: nowrap; }
.semi-use { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer; user-select: none; }
.semi-use input { accent-color: var(--brand); margin: 0; cursor: pointer; }
.semi-use input:disabled { cursor: not-allowed; }
/* 订单详情 / 行内展开 里“用半成品加工”小注 */
.line-note .semi-note { color: var(--green-dk, #1a7a3a); }
/* 关联成品：勾选清单 + 搜索框 */
#sl-list { max-height: 46vh; overflow: auto; margin-top: 6px; border: 1px solid var(--line); border-radius: 8px; }
.row-check {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; cursor: pointer;
  border-bottom: 1px solid var(--line); font-size: 13px; user-select: none;
}
.row-check:last-child { border-bottom: none; }
.row-check:hover { background: #f4f7fb; }
.row-check input { accent-color: var(--brand); margin: 0; cursor: pointer; }
.row-check .muted { margin-left: 2px; }

/* ---------- 商品/半成品 新增弹窗：款式编码 搜索带入 ---------- */
.pk-hint { font-size: 12px; color: var(--muted); margin-top: 4px; line-height: 1.5; }
/* ⚠ 候选**最多画 12 条**（`pkInline` 里那句 `arr.slice(0, 12)`），12 条就是 430 多 px 高。
   而 `.pk-drop` 是**正常流**里的块 —— 它长多高，底下的东西就被顶下去多少。
   多选那套（批量入库）的「添加选中的 N 条」状态栏**故意放在 `.pk-drop` 外面**
   （理由见 `pkInlineHTML` 上那段注释），于是被顶到弹窗体底边上（2026-10-08 线上量的：
   按钮 y=712.45、`.modal-body` 底 713），屏幕小一点就得先滚动才点得到它 ——
   而它正是多选唯一的出口。**勾完了点不到，用户的话就是「勾了没用」。**
   加一道高度上限，超出的自己滚，状态栏就一直在眼前。
   写法照抄 `#sl-list` 那条 46vh：`min(46vh, 320px)`，矮屏按 vh 缩、高屏不让它太长。
   `overflow-y: auto` **照样剪裁**（原来的 `overflow: hidden` 就是要把首尾两行的边框
   剪进那 8px 圆角里），所以圆角没变，只多一根滚动条。 */
.pk-drop { margin-top: 6px; border: 1px solid var(--line); border-radius: 8px; background: #fff;
  max-height: min(46vh, 320px); overflow-y: auto; overflow-x: hidden; }
.pk-none { padding: 8px 10px; font-size: 12px; color: var(--muted); }
.pk-item { display: flex; align-items: baseline; gap: 6px; padding: 7px 10px; cursor: pointer; font-size: 13px; line-height: 1.4; }
.pk-item + .pk-item { border-top: 1px solid var(--line); }
.pk-item:hover, .pk-item:focus { background: #f1f5ff; outline: none; }
.pk-item .pk-st { flex: none; color: #1f6feb; font-family: Consolas, monospace; font-weight: 600; min-width: 44px; }
.pk-item .pk-name { font-weight: 600; }
.pk-item .pk-sub { flex: 1; min-width: 0; color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 候选混了类型时标一下来源（半成品新增会连成品一起搜） */
.pk-item .pk-tag { flex: none; display: inline-block; margin-right: 6px; padding: 0 5px; border-radius: 4px; background: #eef2f7; color: #5b6b7f; font-size: 11px; }

/* 候选行左边那个多选框（批量入库的「勾中若干条再加」）。
   ⚠ 这个 `.field input { width:100%; padding:8px 10px; border:1px solid; border-radius:8px;
   background:#fff }` 是给**表格里的输入框**写的，多选框塞在 `.field` 里会照单全收：
   被撑成**一个满行的大白框**，同行的 `.pk-st` / `.pk-name` / `.pk-sub` 全被挤成零宽
   —— 用户看到的就是「只看到一排大的复选框，其它的都看不到」（2026-10-08 线上报的）。
   ⚠ `.field input` 权重是 (0,1,1)，这里必须比它高：`.field .pk-item input.pk-ck` 是 (0,2,1)。
   同族的坑在第 74 行那段注释（`.finish-ck`）已经踩过一次，修法照抄它。 */
.field .pk-item input.pk-ck {
  flex: none; align-self: center; width: 15px; height: 15px;
  padding: 0; margin: 0; border: 0; border-radius: 0; background: none;
  accent-color: var(--brand); cursor: pointer;
}

/* ---------- 内联搜索下拉里的翻页条（增量六十三 · 2026-10-08） ----------
   候选超过一页时，画在下拉**里面**、候选行**后面**。用户原话：
   「批量入库对话框，输入款式编码，如果出来符合条件太多，下面增加翻页条」。
   sticky + bottom:0 —— 钉在下拉**可视区的底边**：
     ① 12 条候选把下拉撑到高度上限时，它不会被挤出滚动区
        （要翻页的人不必先滚到底才点得到「下一页」）；
     ② 它**不改变下拉自身的高度**（上限仍由上面那条规则管），所以多选那颗
        「添加选中的 N 条」还在原位置 —— 2026-10-08「候选把按钮顶出弹窗底边」
        那次（线上量的：按钮 y=712.45、.modal-body 底 713）不会复发。
   背景必须不透明：贴住时下面的候选行从它身后滚过去，透出来就花了。
   ⚠ 选择子全新（`.pk-pager*`），上面那条下拉的规则一个字都不碰 ——
     两套 e2e/反向验证逐字钉着它。 */
.pk-pager {
  position: sticky; bottom: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 5px 8px; border-top: 1px solid var(--line); background: #fff;
  font-size: 12px; color: var(--muted);
}
.pk-pager .pk-pg-btns { display: inline-flex; gap: 4px; }
.pk-pager .pk-pg {
  min-width: 26px; height: 22px; padding: 0 7px;
  border: 1px solid var(--line); background: #fff; border-radius: 6px;
  font-size: 13px; line-height: 1; color: var(--text); cursor: pointer;
}
.pk-pager .pk-pg:hover:not(:disabled) { border-color: var(--brand); color: var(--brand-dk); }
.pk-pager .pk-pg:disabled { opacity: .45; cursor: not-allowed; }

/* ---------- 关联成品 弹窗底部 全选 ---------- */
.sl-master { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--text); cursor: pointer; padding: 2px 6px; user-select: none; border-radius: 6px; }
.sl-master:hover { background: #f1f5ff; }
.sl-master input { accent-color: var(--brand); margin: 0; cursor: pointer; }

/* ---------- 半成品资料库 行内“关联详情”展开 ---------- */
.sl-expand-row td { padding: 4px 14px 14px; background: #fafbfc; }
.sl-expand-row .sl-head { font-size: 13px; font-weight: 600; color: var(--text); margin: 2px 0 8px; }
.sl-expand-row .sl-head b { color: var(--brand); }

/* ---------- 报价参数（系统 → 报价参数 六个标签页 + 每页一个编辑弹窗） ---------- */
/* ⚠ 这一套**刻意新开 `.qp-tab*` 命名空间**，不复用 `.tab` / `.tablist` / `.tabbar` /
   `.tabpane` / `.tab-x` —— 那几个类名全被文档页签系统（switchTab）占着，复用的话
   两边样式互相串，而且 CSS 不报错、只是某个地方悄悄变样。 */
.qp-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.qp-tab {
  padding: 6px 12px; font-size: 13px; cursor: pointer; font-family: inherit;
  border: 1px solid var(--line); background: #fff; color: var(--text); border-radius: 8px;
}
.qp-tab:hover { border-color: var(--brand); }
.qp-tab.is-on { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
.qp-tabpane { }
.qp-edit { float: right; }
/* 总表：一页一张卡，每组一张两列小表（参数名 / 当前值） */
.qp-grp + .qp-grp { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; }
.qp-grp-head { font-size: 13px; font-weight: 700; margin-bottom: 6px; display: flex; align-items: baseline; gap: 8px; }
.qp-grp-head .qp-unit { font-weight: normal; color: var(--muted); font-size: 12px; }
.qp-tbl th { width: 240px; }
.qp-tbl tr.qp-sub th { color: var(--text); font-weight: 600; background: #f7f9fc; }
.qp-human { font-weight: normal; color: #1f6feb; font-family: Consolas, monospace; }
/* 编辑弹窗：宽一点，每格一个输入框，能自动折行；参数多所以 .modal-body 要能滚（见 .modal-tall） */
.modal.modal-tall { width: 900px; }
.modal.modal-tall .modal-body { min-height: 0; }
.qp-fgrp + .qp-fgrp { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.qp-fgrp-head { font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.qp-fsub { font-size: 12px; font-weight: 600; color: var(--text); margin: 10px 0 6px; }
.qp-fgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px 12px; }
.qp-fcell { display: block; }
.qp-fcell > span { display: block; font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.qp-fcell input, .qp-fcell select { width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: 8px; font-size: 13px; font-family: inherit; background: #fff; }
.qp-fcell input:focus, .qp-fcell select:focus { outline: none; border-color: var(--brand); }
/* 能增删的那几页：每条记录外面一层框，右上角 ↑ ↓ 删除。
   ⚠ ↑↓/删除 是**绝对定位**的，所以格子那一栏必须留出右边的空档（padding-right），
     不留的话最后那一列输入框会滑到按钮底下 —— 打得进去、也点得着删除，看着像没事。 */
.qp-frow { position: relative; border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; margin: 8px 0; background: #fcfdfe; }
.qp-frow > .qp-fgrid { padding-right: 200px; }
.qp-facts { position: absolute; top: 8px; right: 8px; display: flex; gap: 4px; }
.qp-fadd { margin-top: 8px; }
/* 盒型展开系统那一页的「公式行」（2026-09-15 增量五·第二轮）。一个部件 = 部件名 + 两条公式
   + 两个常数 + 三个动作，在 900px 的弹窗里排在同一个网格里。
   下面四样（qp-fhead / qp-fwide / qp-fbtn / qp-fhint）都**没有 data-k**，
   只负责排版与动作，不进提交表；`kind:"hint"` 也**不是**错误提示，只是一句提醒，
   所以是灰底不是红底（红底留给「公式算不出来」那条 #flatWarn）。 */
.qp-fcell.qp-fhead { grid-column: 1 / -1; font-size: 12px; font-weight: 700; color: var(--muted);
  border-top: 1px dashed var(--line); padding-top: 6px; margin-top: 4px; }
.qp-fcell.qp-fwide { grid-column: 1 / -1; }
.qp-fcell.qp-fbtn { display: flex; gap: 6px; }
.qp-fcell.qp-fbtn > .btn { white-space: nowrap; }
/* 「公式里没写 C，这个常数不算数」。⚠ 这一格**必须与输入框等高**（下面那句话是对着
   右边那个常数框说的），矮一截就会串到上一行的对齐上去。 */
.qp-fcell.qp-fhint { display: flex; align-items: center; padding: 6px 8px; border-radius: 8px;
  background: #f4f7fb; font-size: 12px; color: var(--muted); }

/* ---------- 定制报价（报价 → 定制报价） ---------- */
/* 这一整段是从 _mockup_quote.html 的 <style> 搬过来的。搬运时四处**刻意的改动**：
     ① 原型那句 body { padding: 18px 20px 60px } 没搬 —— style.css 的 body 不设 padding，
        搬过来会给全站每一页（含门户 public.html）都加一圈内缩。
     ② 原型那句 svg { display:block; max-width:100%; height:auto } 收窄成 .mk-figbox svg。
        今天全项目 0 个内联 svg，但全局 svg 选择器是哑的，哪天加个图标就会静默变形。
     ③ 原型的 td.craft-cell / td.craft-cell input 改名 td.qc-craft-cell
        （只改选择器，数值一个没动；2026-09-15 那一格又整个换了形状，见下面那条规则）。
        style.css 上面已有 .craft-cell 给订单行的产品工艺格用，而原型的 td.craft-cell 是
        (0,1,1) 优先级更高 —— 同名搬过来会把订单行那一格变成 display:flex; flex-wrap:wrap，
        不报错、只是别的页悄悄变了样。
     ④ 原型顶栏 .mk-bar 那三条没搬 —— ERP 的页头是 .page-title，没有任何元素带 mk-bar。
   另外 table.mk td.num input 那条也没搬：工艺单价卡改成只读之后，table.mk 里再没有输入框了。 */

/* 报价页顶上的横幅：告诉用户这一屏是按哪套参数在算 */
.qp-banner {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 9px 12px; border-radius: 8px;
  background: #f1f5ff; border: 1px solid #cdddfb; color: #24405e; font-size: 13px;
}
/* 「按快照」必须是显眼的黄条 —— 否则用户改完参数回来会以为报价页坏了 */
.qp-banner-snap { background: #fff8e6; border-color: #f3d99b; color: #7a5200; }
/* 快照读不出来、退回当前参数算：红条。这一条比黄条更要紧 —— 黄条说的是「按老参数算」，
   红条说的是「按**新**参数算，金额可能跟当初不一样」，不说清楚就没人看得出来。 */
.qp-banner-bad { background: #fdecec; border-color: #f0b4b4; color: #8c2b2b; }

/* 四张卡：上排三张并排（展开图 / 报价单 / 报价表），报价输入整宽沉到最下面。
   排位靠 flex 的 order —— 源文件里报价输入写在最前，读代码顺着填单顺序走。 */
.mk-page { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.mk-page > .card { flex: 1 1 0; min-width: 280px; margin-bottom: 0; }
.mk-page > .card.mk-wide { flex: 1 1 100%; min-width: 0; order: 2; }
@media (max-width: 1000px) {
  .mk-page > .card { flex: 1 1 100%; }
}

/* 表单区：自适应多列（现在整宽了，列数随屏幕走） */
.mk-in { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px 16px; }
.mk-in .field { margin-bottom: 0; }
.mk-in .wide { grid-column: 1 / -1; }

/* 最后一行：现在只剩「加价」一个字段了。
   原先同行的「配件」那一整排（绳子/提手/配饰）2026-09-14 整排撤掉，
   改成了部件表里逐行的列（绳子/提手/其它）—— 配件本来就是按部件走的，
   摊在整单那一行只能整单一个价，跟「天盒用绳、地盒不用」对不上。
   加价还是那个小数字框，单独占一整行太浪费，所以照旧定宽。 */
.mk-bottom { display: flex; gap: 12px 20px; align-items: flex-start; flex-wrap: wrap; }
/* 150 不是随手挑的：加价那个标签「加价（元）不乘系数」约 108px 宽（原先那句尾巴
   「，直接加总额」占了 70px，那句删了，宽度跟着收）。
   卡在 108 上会随子像素左右横跳（有时一行、有时折两行），一折行输入框就矮 28px。
   给足余量，标签稳定一行。 */
.mk-bottom .bt-extra { flex: 0 0 150px; }
/* 客户 / 备注：跟「加价」同一行。客户名短，给 220；备注是可长的自由文本，让它吃掉富余。
   存单按钮排在最后靠右 —— 它是这一页的收尾动作，不该跟输入框混在一堆。 */
.mk-bottom .bt-cust { flex: 0 0 220px; }
.mk-bottom .bt-remark { flex: 1 1 260px; min-width: 200px; }
.mk-bottom .bt-save { flex: 0 0 auto; margin-left: auto; text-align: right; }
.mk-bottom .bt-save .btn { padding: 7px 16px; }
.mk-bottom .bt-save .mk-sub { margin-top: 4px; }

/* 报价记录页的搜索条 */
.qrec-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.qrec-bar .search {
  flex: 0 1 320px; padding: 6px 10px; border: 1px solid var(--line);
  border-radius: 8px; font-size: 13px; background: #fff; color: var(--text);
}
.qrec-bar .mk-sub { margin-left: 2px; }

/* 第一行：提交方式 + 盒型 + 数量（整单一级的参数） */
.mk-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mk-top .ot-radios { margin: 0; }
.mk-top .sep { width: 1px; height: 20px; background: var(--line); margin: 0 6px; }
.mk-top label.lb { color: var(--muted); font-size: 12px; }
.mk-top select, .mk-top input {
  padding: 5px 8px; border: 1px solid var(--line); border-radius: 8px;
  font-size: 13px; background: #fff; color: var(--text);
}
.mk-top select { width: 128px; }
.mk-top input { width: 96px; text-align: right; font-variant-numeric: tabular-nums; }

/* 部件表：一个部件一行。行数跟着盒型的部件数走，2 个就 2 行、4 个就 4 行 */
table.mk-pt { width: 100%; border-collapse: collapse; margin-top: 2px; }
/* 列加到 17 列之后，窄屏压到 600 上下就到底了（那是各格控件自己的最小宽度，再压就装不下）。
   与其让整页横向滚（把上面三张卡片一起拖出去），不如让这张表在自己的框里滚：
   宽屏时它照样撑满、不出现滚动条；只有真的窄到装不下时才在表内滚。 */
#parts { overflow-x: auto; }
/* 展开公式算不出来时那条红字（挂在部件表上方）。刻意做成**看得见的一行**而不是
   toast：toast 三秒就没了，而这张表的金额会一直按 0 算下去。
   红框红字 + 加粗，扫一眼就知道「这里有个必须先解决的东西」。 */
#flatWarn {
  margin: 2px 0 8px; padding: 6px 10px; border: 1px solid #f3c1c1; border-radius: 7px;
  background: #fdeaea; color: var(--red); font-size: 12px; line-height: 1.7;
}
#flatWarn b { font-weight: 600; }
/* 「登记入库」弹窗里「是否完结」那段说明（两个「单行入库」弹窗共用一份文案）。
   照 #flatWarn 的红框红字：勾上就是把**整张订单**从待交付 / 逾期 / 看板统计里撤下来，
   代价必须在勾**之前**就看清 —— 所以它是常驻的一行，不是勾了才出现的提示，也不是
   三秒就没的 toast。 */
.finish-hint {
  margin: 0 0 12px; padding: 8px 10px; border: 1px solid #f3c1c1; border-radius: 7px;
  background: #fdeaea; color: var(--red); font-size: 12px; line-height: 1.75;
}
.finish-hint b { font-weight: 600; }
/* 补货计划页顶上那两块「手上这份数不可信」的常驻提示：同步失败（红）与同步盲区（琥珀）。
   跟 #flatWarn / .finish-hint 同一个套路、也同一个理由：**不用 toast** —— toast 三秒就没了，
   而这两条恰恰是「照它决定补多少货」之前的必要条件，要一直看得见、直到修好。 */
.jst-err {
  margin: 0 0 10px; padding: 6px 10px; border: 1px solid #f3c1c1; border-radius: 7px;
  background: #fdeaea; color: var(--red); font-size: 12px; line-height: 1.75;
}
.jst-note {
  margin: 0 0 10px; padding: 6px 10px; border: 1px solid #f0d9a8; border-radius: 7px;
  background: #fff3e0; color: var(--amber); font-size: 12px; line-height: 1.75;
}
table.mk-pt th {
  font-size: 11px; color: var(--muted); font-weight: 600; text-align: left;
  padding: 4px 4px 6px; white-space: nowrap; line-height: 1.35;
}
table.mk-pt td { padding: 3px 4px; }
table.mk-pt tbody tr + tr td { border-top: 1px solid #eef1f6; }
table.mk-pt td.nm { font-size: 12px; color: var(--muted); white-space: nowrap; padding-left: 0; }
table.mk-pt input, table.mk-pt select {
  width: 100%; padding: 4px 6px; border: 1px solid var(--line); border-radius: 7px;
  font-size: 12.5px; background: #fff; color: var(--text);
}
table.mk-pt input[type=number] { text-align: right; font-variant-numeric: tabular-nums; }
table.mk-pt th.num { text-align: right; }
/* 禁用（"不印刷"时那个专色数）和只读（公式算出来的展开尺寸）都长这样 */
table.mk-pt input[disabled], table.mk-pt input[readonly] {
  background: #f7f8fa; color: #6b7280; border-style: dashed;
}
table.mk-pt input[disabled] { color: #c2c8d2; }
table.mk-pt input[readonly]:focus, table.mk-pt input[disabled]:focus {
  border-color: var(--line); box-shadow: none;
}
td.ink-cell { display: flex; gap: 4px; align-items: center; }
td.ink-cell .pk-spots { width: 44px; flex: none; }

/* 「工艺」那一格（在「总价」右边）：一个「设置」按钮 + 选完之后的摘要文字，
   下面压着六个**隐藏**的数字框（qpSnapshotParts 的 own() 照旧从它们读）。
   2026-09-15 用户要求：工艺从「一个下拉 + 一列小框」改成「一列 + 一个四选弹窗」，
   所以原来那条 160px 的「工艺数量」列连同 td.mk-craft-cell 两条规则一起下岗了 ——
   那几个 46px 的小框搬进了弹窗（见下面 .qc-in）。 */
td.qc-craft-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.qc-craft-val { font-size: 11px; line-height: 1.35; color: #555; word-break: break-word; }
.qc-craft-val.none { color: #aaa; }

/* ---- 表面工艺弹窗（.qc-sec 四块：烫金 / UV / 压纹 / 激凸） ---- */
.qc-sec { border: 1px solid var(--line); border-radius: 8px;
          padding: 9px 11px; margin-bottom: 8px; }
.qc-sec-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.qc-on-lab { display: flex; align-items: center; gap: 5px;
             font-weight: 600; font-size: 13.5px; min-width: 62px; cursor: pointer; }
.qc-rate { font-size: 12px; color: #6b7280; font-variant-numeric: tabular-nums; }
.qc-sec-in { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 7px; }
.qc-in-lab { display: flex; align-items: center; gap: 4px; font-size: 12.5px; color: #555; }
/* 46px —— 跟原来表格里那一列同一个小框尺寸，靠 style.css 第 17 行那个全局
   * { box-sizing: border-box }「写 46 就占 46」。别在这条上再叠 box-sizing。 */
.qc-in { width: 46px; flex: none; }
.qc-note { font-size: 12px; color: #6b7280; line-height: 1.6; margin-top: 4px; }

/* 用纸面积 / 该行总价：都是算出来的数，不是可填字段 —— 所以是 span 不是 input，
   长着 Text 的样子而不是输入框的样子（不然用户会去点它、以为填了能改） */
table.mk-pt td.calc { font-size: 12.5px; text-align: right; white-space: nowrap;
                      font-variant-numeric: tabular-nums; padding-right: 7px; }
/* 该行总价 = 这一行的成本。加粗一点，它跟「成本合计」对得上 */
table.mk-pt td.calc.sum { font-weight: 600; }

/* 行尾那个 ×：删行。整行都得能点中（手机上 4px 的 × 谁点得着），但别喧宾夺主 */
table.mk-pt td.del-cell { text-align: center; padding-left: 0; }
table.mk-pt .mk-del {
  border: none; background: none; color: #c2c8d2; cursor: pointer;
  font-size: 15px; line-height: 1; padding: 2px 6px; border-radius: 6px;
}
table.mk-pt .mk-del:hover { color: #d64545; background: #fdecec; }
/* 只剩最后一行时禁用它 —— 一个部件都没有的报价单没法算（展开图也画不出来） */
table.mk-pt .mk-del:disabled { color: #e6e9ee; background: none; cursor: not-allowed; }

/* 新增行按钮：贴在部件表下面、靠左，跟表格的左边线对齐 */
.mk-addrow { margin-top: 8px; }

/* 明细/报价表：两列表 */
table.mk { width: 100%; border-collapse: collapse; font-size: 13px; }
table.mk th { text-align: left; color: var(--muted); font-weight: 600; font-size: 12px;
              padding: 6px 8px; border-bottom: 1px solid var(--line); }
table.mk td { padding: 7px 8px; border-bottom: 1px solid #eef1f6; }
table.mk tr.sum td { border-top: 1px solid var(--line); border-bottom: none; padding-top: 9px; }
table.mk tr.grand td { border-top: 2px solid var(--text); font-weight: 700; font-size: 14px; }
table.mk td.k { color: var(--muted); }
/* 明细里的小字分项（上机/专色），不参与视觉层级 */
table.mk tr.subline td { color: var(--muted); font-size: 12px; padding: 2px 8px 2px 24px; border-bottom: none; }
table.mk tr.subline + tr td { border-top: 1px solid #eef1f6; }
.mk-idx { display: inline-block; width: 16px; color: #aab2bf; }

/* 报价单卡内左右分：左边明细表，右边说明 */
.mk-doc { display: flex; gap: 16px; align-items: flex-start; }
.mk-doc-table { flex: 1 1 auto; min-width: 0; }
.mk-doc-side { flex: 0 0 150px; min-width: 0; }
.mk-doc-side .mk-note { margin-top: 0; }
/* 报价单的明细表：行距收紧，把卡再压矮一点 */
table.mk.tight td { padding: 5px 6px; }
table.mk.tight tr.subline td { padding: 1px 6px 1px 20px; }
table.mk.tight tr.grand td { padding-top: 7px; }
@media (max-width: 560px) {
  .mk-doc { flex-wrap: wrap; }
  .mk-doc-table, .mk-doc-side { flex: 1 1 100%; }
  .mk-doc-side { border-top: 1px dashed var(--line); padding-top: 12px; }
}

/* 客户表里的部件明细：小一号的紧凑表 */
table.mkp { width: 100%; border-collapse: collapse; font-size: 12.5px; margin: 4px 0 2px;
            border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
table.mkp th { text-align: left; font-size: 11px; color: var(--muted); font-weight: 600;
               padding: 5px 8px; background: #fafbfd; border-bottom: 1px solid var(--line); }
table.mkp td { padding: 5px 8px; border-bottom: 1px solid #eef1f6; }
table.mkp tr:last-child td { border-bottom: none; }
table.mkp td.num, table.mkp th.num { text-align: right; font-variant-numeric: tabular-nums; }

.mk-sub { color: var(--muted); font-size: 12px; }
.mk-incl td { color: var(--muted); background: #fafbfd; }

/* 示意图的框 */
.mk-figbox { border: 1px solid var(--line); border-radius: 8px; background: #fcfdff;
             padding: 8px; display: flex; justify-content: center; align-items: center; min-height: 118px; }
.mk-figbox svg { display: block; max-width: 100%; height: auto; }   /* 列变窄时图自己缩 */

.mk-note { font-size: 12px; color: var(--muted); margin-top: 8px; line-height: 1.7; }

/* ============================================================================
   站内聊天（2026-09-30 增量四十四）
   ----------------------------------------------------------------------------
   右下角常驻浮层，跟 `#ui-cluster` 是同一类东西（固定定位、不进页签、不进权限表）。
   ⚠ `bottom: 70px` 是让开 `#ui-cluster` 那个右下角小簇（它在 `bottom: 14px`、约 44px 高）
     —— 两个都贴右下会直接叠在一起。
   ⚠ `z-index: 80` 夹在 `.sn-flyout`(70) 和 `#modal-root`(100) 之间：弹窗要能盖住聊天面板，
     聊天面板要能盖住侧栏飞层。全站那条梯子见 #chat-root 上面这行注释的出处。
   ============================================================================ */
#chat-root { position: fixed; right: 16px; bottom: 70px; z-index: 80; }
#chat-root.hidden { display: none !important; }

/* ---- 折叠态：一个圆钮 + 未读角标 ---- */
#chat-bubble {
  position: relative; width: 52px; height: 52px; border-radius: 50%;
  border: 1px solid var(--line); background: #fff; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; line-height: 1;
  box-shadow: 0 4px 14px rgba(20, 30, 60, .18);
  transition: transform .12s ease, box-shadow .12s ease;
}
#chat-bubble:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(20, 30, 60, .22); }
#chat-badge {
  position: absolute; top: -5px; right: -5px; min-width: 19px; height: 19px;
  padding: 0 5px; border-radius: 10px; background: #e5484d; color: #fff;
  font-size: 11px; font-weight: 700; line-height: 19px; text-align: center;
  box-shadow: 0 0 0 2px #fff;
}
#chat-badge.hidden { display: none !important; }

/* ---- @ 角标（增量五十一）：和上面那个未读角标**是两个数**，见 app.js 的 `chatRenderBadge`。
   故意做成一左一右 + 另一个颜色：两个都挂在右上角的话，用户会以为后面那个把前面那个
   盖住了（数字小于 10 时大小几乎一样，叠上去就是「怎么少了一条」）。 ---- */
#chat-at-badge {
  position: absolute; top: -5px; left: -5px; min-width: 19px; height: 19px;
  padding: 0 5px; border-radius: 10px; background: #1f6feb; color: #fff;
  font-size: 11px; font-weight: 700; line-height: 19px; text-align: center;
  box-shadow: 0 0 0 2px #fff;
}
#chat-at-badge.hidden { display: none !important; }

/* ---- 本项目**第一个** @keyframes。之前全站一个动画都没有，只有 transition。---- */
@keyframes chat-blink {
  0%, 100% { background: #e5484d; box-shadow: 0 0 0 2px #fff; }
  50%      { background: #ff9aa2; box-shadow: 0 0 0 4px rgba(229, 72, 77, .28); }
}
#chat-badge.pulse { animation: chat-blink 1.1s ease-in-out infinite; }
@keyframes chat-ring {
  0%, 100% { box-shadow: 0 0 0 0 rgba(31, 111, 235, .45); }
  50%      { box-shadow: 0 0 0 10px rgba(31, 111, 235, 0); }
}
#chat-bubble.pulse { animation: chat-ring 1.4s ease-out infinite; }

/* ★ 前庭症的人会因为一个一直闪的图标难受。这一条把「闪」降级成「一直在」——
   信息一点没丢（数字还在），只是不动了。成本是一行 CSS，所以没有理由不写。
   ⚠ 这条必须排在两条 animation 之后：同优先级下后者胜，写到前面等于没写。 */
@media (prefers-reduced-motion: reduce) {
  #chat-badge.pulse, #chat-bubble.pulse { animation: none; }
  #chat-badge.pulse { background: #e5484d; box-shadow: 0 0 0 2px #fff; }
}

/* ---- 展开态 ---- */
#chat-panel {
  width: 384px; height: 540px; max-height: calc(100vh - 100px);
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 10px 32px rgba(20, 30, 60, .22);
  display: flex; flex-direction: column; overflow: hidden;
}
#chat-panel.hidden { display: none !important; }
.chat-head {
  display: flex; align-items: center; gap: 6px; padding: 8px 10px;
  border-bottom: 1px solid var(--line); background: #fafbfd; flex: 0 0 auto;
}
.chat-head .t { font-weight: 600; font-size: 13.5px; margin-right: auto; }
.chat-head button {
  border: 1px solid var(--line); background: #fff; border-radius: 7px;
  padding: 3px 8px; font-size: 12px; cursor: pointer; color: #333;
}
.chat-head button:hover { background: #eef3fc; }
.chat-head button.hidden { display: none !important; }
/* 三个页签（会话 / 好友 / ＋发起）里当前那一个。
   ⚠ 靠 `class="on"` 标，**不能**只靠 `:first-child` 之类的次序 —— 「＋发起」不在第一位，
     而且对话页要算在「会话」那一档里（见 `chatRender`）。 */
.chat-head button.on {
  background: #2b6cf6; border-color: #2b6cf6; color: #fff; font-weight: 600;
}
.chat-head button.on:hover { background: #1f5be0; }
.chat-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.chat-pane { display: none; }
.chat-pane.active { display: block; }

/* 会话列表 / 好友列表 */
.chat-row {
  display: flex; align-items: flex-start; gap: 8px; padding: 9px 11px;
  border-bottom: 1px solid #eef1f6; cursor: pointer;
}
.chat-row:hover { background: #f6f9ff; }
.chat-av {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  background: #e8eefb; color: #24509b; font-weight: 700; font-size: 13px;
  display: flex; align-items: center; justify-content: center;
}
.chat-row .mid { flex: 1 1 auto; min-width: 0; }
.chat-row .nm { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
.chat-row .pv {
  font-size: 12px; color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-row .rt { flex: 0 0 auto; text-align: right; }
.chat-row .tm { font-size: 11px; color: var(--muted); }
.chat-unread {
  display: inline-block; min-width: 17px; height: 17px; padding: 0 5px; margin-top: 3px;
  border-radius: 9px; background: #e5484d; color: #fff; font-size: 11px;
  font-weight: 700; line-height: 17px; text-align: center;
}
.chat-pill {
  display: inline-block; padding: 0 6px; border-radius: 6px; font-size: 11px;
  background: #eef3fc; color: #24509b; line-height: 17px;
}
.chat-empty { padding: 26px 16px; text-align: center; color: var(--muted); font-size: 12.5px; line-height: 1.9; }
.chat-sec { padding: 8px 11px 3px; font-size: 11px; color: var(--muted); font-weight: 600; }

/* 「群聊」页签顶部那一行（现在只放「＋ 新建群聊」） */
.chat-actions { padding: 8px 11px 2px; }
/* 建群面板 `#chat-newgrp` 里的几档。面板本身是 `#chat-mini` 的兄弟写法（同一套外观），
   但它是**表单**，所以多这几条针对内容的规则。 */
.chat-ng-namewrap { padding: 10px 14px 0; }
.chat-ng-namewrap input {
  width: 100%; box-sizing: border-box; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 8px; font: inherit;
}
.chat-ng-hint { font-size: 11.5px; color: var(--muted); padding: 5px 1px 0; min-height: 16px; }
.chat-ng-list { max-height: 44vh; overflow-y: auto; }
.chat-ng-pick {
  display: flex; align-items: center; gap: 8px; padding: 7px 12px;
  cursor: pointer; font-size: 13px; border-bottom: 1px solid #eef1f6;
}
.chat-ng-pick:hover { background: #f6f9ff; }
.chat-ng-pick .nm { font-weight: 600; }
.chat-ng-pick input { flex: 0 0 auto; }

/* 对话区 */
/* ⚠ `position: relative` 是承重的（增量五十一）：`@` 的挑人面板（`.chat-at-pop`）挂在
   这一层下面、用 `absolute` 贴在输入框上方。少了它，面板会去认最近的那个**带定位**的
   祖先 —— 也就是 `#chat-root`（`position: fixed`）—— 于是它飞到整个悬浮球旁边去，
   而不是待在聊天框里。不报错，只是位置离谱。 */
.chat-thread { position: relative; display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.chat-msgs { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 10px 11px; background: #f7f9fc; }
.chat-m { margin-bottom: 9px; display: flex; flex-direction: column; align-items: flex-start; }
.chat-m.me { align-items: flex-end; }
.chat-m .who { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
.chat-m .bub {
  max-width: 78%; padding: 6px 10px; border-radius: 11px; background: #fff;
  border: 1px solid #e6eaf2; font-size: 13px; line-height: 1.55;
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
}
.chat-m.me .bub { background: #e3edff; border-color: #cfe0ff; }
/* 图片消息的 `.bub` 是**空的**（正文让给图片了）。不收掉的话，一张图上面会顶着一个
   空泡泡 —— 那个泡泡有 padding 和边框，看着就像「谁发了一条空消息」。 */
.chat-m .bub:empty { display: none; }
.chat-m .at { font-size: 11px; color: var(--muted); margin-top: 2px; }

/* 图片消息。max-width 跟泡泡同一个量级，免得一张图把整屏撑开；
   `cursor:zoom-in` 是唯一的「点得开」提示（点开看大图）。 */
.chat-m .chat-img {
  max-width: min(78%, 320px); max-height: 260px; border-radius: 10px;
  border: 1px solid #e6eaf2; display: block; cursor: zoom-in; background: #fff;
}
.chat-img-big { display: block; width: 100%; height: auto; border-radius: 8px; }
/* 「＋图片」那颗。用 label 包一个隐藏 input —— 点它等于点 input，不用脚本去 click。 */
.chat-imglbl {
  flex: 0 0 auto; align-self: flex-end; padding: 0 10px; height: 46px; line-height: 44px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; font-size: 13px;
  color: #35507d; cursor: pointer; user-select: none;
}
.chat-imglbl:hover { background: #eef3fc; border-color: #cfe0ff; }
/* 已读回执：私聊一个勾、群聊「N人已读」。只长在自己发的消息上。 */
.chat-m .rc { margin-left: 5px; color: #2f6bd8; font-weight: 600; }
.chat-m .rc-c { cursor: pointer; text-decoration: underline dotted; }
/* ⚠ `position: relative` 是承重的（增量五十一）：@ 的挑人面板挂在这一格下面、
   用 `bottom: 100%` 贴在**输入行的正上方**。挂到 `.chat-thread` 上是不行的 ——
   那一层的最底下是输入行，但 `bottom:100%` 会把面板顶到整块对话区（含标题栏）之上。 */
.chat-input {
  position: relative; flex: 0 0 auto; border-top: 1px solid var(--line);
  padding: 8px; display: flex; gap: 6px;
}
.chat-input textarea {
  flex: 1 1 auto; resize: none; height: 46px; padding: 7px 9px; font-size: 13px;
  border: 1px solid var(--line); border-radius: 8px; font-family: inherit;
}
.chat-audit { flex: 0 0 auto; padding: 5px 11px; border-top: 1px solid var(--line);
              font-size: 11.5px; color: var(--muted); cursor: pointer; background: #fafbfd; }
.chat-audit:hover { background: #eef3fc; color: #24509b; }
/* 删了好友之后的私聊是只读的（决策 C）。这一条**取代**输入框，占住同样的位置 ——
   不然会话底部会空掉一块，看着像界面坏了。 */
.chat-ro {
  flex: 0 0 auto; border-top: 1px solid var(--line); padding: 14px 11px; background: #fafbfd;
  font-size: 12.5px; color: var(--muted); text-align: center;
}
/* 好友头像 = 删除入口（用户 2026-09-30：「点头像删除，要确认」）。
   悬停变红是唯一的提示 —— 那个动作不可逆，不能让人误点。 */
.chat-av-del { cursor: pointer; }
.chat-av-del:hover { background: #fdecec; color: #c0343a; }
/* 群聊页签行尾那颗「退出」。跟列表其它按钮同尺寸，不抢视觉。 */
.chat-leave { flex: 0 0 auto; }
.chat-row .rt .chat-leave { margin-top: 3px; }

/* ============================================================================
   增量五十一：@提及 / 正在输入 / 搜索 / 群设置
   ============================================================================ */

/* 别人在打字那一行。**高度是钉死的**（不是 `min-height`）：它是 flex 列里的一个兄弟，
   高度一变，上面的消息区就跟着弹一下 —— 每来一次「谁在打字」抖一整屏，
   比不显示这行还烦。 */
.chat-typing {
  flex: 0 0 auto; height: 18px; line-height: 18px; padding: 0 11px;
  font-size: 11.5px; color: var(--muted); font-style: italic;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-typing.hidden { display: none !important; }

/* 输入行里那颗「@」。做得比旁边的「＋图片」窄，因为它就是**一个字符** ——
   给它同样的宽度会让输入框白白少掉一截。 */
.chat-input .chat-atbtn { flex: 0 0 auto; width: 32px; padding: 0; font-weight: 700; }

/* 被 @ 到的名字在气泡里高亮一下，好让人一眼找到「叫我的那句在哪」。 */
.chat-m .bub .chat-at { color: #1f5be0; font-weight: 600; }
.chat-m.me .bub .chat-at { color: #143c8f; }

/* 「@」的挑人面板：挂在输入那一行（`.chat-input` 有 `position: relative`）底下，
   用 `bottom: 100%` 贴在输入行的正上方。
   ⚠ **必须挂在输入行上**，不能挂 `.chat-thread` 上 —— 那一层里边还套着标题栏和消息区，
     `bottom:100%` 会把面板顶到整个对话区之外（飞到面板外面去）。 */
.chat-at-pop {
  position: absolute; left: 8px; right: 8px; bottom: 100%; margin-bottom: 6px;
  max-height: 40vh; overflow-y: auto;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 -6px 22px rgba(20, 30, 60, .16);
}
.chat-at-pop .chat-at-row {
  display: flex; align-items: center; gap: 9px; padding: 7px 11px; cursor: pointer;
}
.chat-at-pop .chat-at-row:hover { background: #eef3fc; }
.chat-at-pop .chat-at-row .nm { font-size: 13px; font-weight: 600; }

/* 搜索弹层里的结果列表。日期不单独占一列，跟在会话名后面（这一条要的是
   「哪一场、什么时候、说了什么」，一列一列排反而扫不动）。 */
.chat-srch-out { flex: 1 1 auto; overflow-y: auto; padding: 0 0 10px; }
.chat-srch-row { padding: 8px 13px; cursor: pointer; border-bottom: 1px solid #f0f3f9; }
.chat-srch-row:hover { background: #f6f9ff; }
.chat-srch-row .nm { font-size: 12.5px; font-weight: 600; }
.chat-srch-row .nm .tm {
  font-size: 11px; color: var(--muted); font-weight: 400; margin-left: 8px;
  display: inline-block; max-width: 62%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
}

/* 群设置页。它就是普通一屏（**不是** `.chat-thread` 那套 flex 列）——
   设置项多，让它跟着 `.chat-body` 整页滚就行，不必自己管滚动区。 */
.chat-gset { padding-bottom: 16px; }
.chat-gset .chat-row { padding-left: 11px; padding-right: 11px; }
/* 群名那一行：输入框吃掉剩余宽度，保存按钮贴着右边。**和建群弹窗里那行同宽**
   （`.chat-ng-namewrap` 左右各 14px 内边距），这样两个界面看着是一套的。 */
.chat-gs-name { display: flex; align-items: center; gap: 8px; padding: 6px 14px 0; }
.chat-gs-name input {
  flex: 1 1 auto; min-width: 0; height: 32px; padding: 0 9px;
  border: 1px solid var(--line); border-radius: 7px; font-size: 13px;
  background: #fff; color: inherit;
}
.chat-gs-name .chat-gs-hint { flex: 1 1 auto; font-size: 11.5px; color: var(--muted); }
/* 不是群主时群名只读显示 —— 不给输入框（给一个打不开的框比给一行字更气人）。 */
.chat-gs-ro { padding: 7px 15px 0; font-size: 13.5px; font-weight: 600; }
.chat-gset .chat-empty { padding: 16px 14px; text-align: left; }
.chat-gs-add { max-height: 40vh; overflow-y: auto; }

/* 管理员查记录：一块整屏的抽屉（**不是**浮层小面板 —— 看全文要宽度） */
#chat-log-root {
  position: fixed; inset: 0; z-index: 90; background: rgba(18, 26, 44, .38);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
#chat-log-root.hidden { display: none !important; }
.chat-log-box {
  width: 100%; max-width: 940px; height: 100%; max-height: 760px; background: #fff;
  border-radius: 12px; display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 20px 60px rgba(10, 18, 38, .34);
}
.chat-log-box .hd {
  display: flex; align-items: center; gap: 10px; padding: 11px 14px;
  border-bottom: 1px solid var(--line); background: #fafbfd;
}
.chat-log-box .hd .t { font-weight: 600; font-size: 14px; margin-right: auto; }
.chat-log-main { flex: 1 1 auto; display: flex; min-height: 0; }
.chat-log-list { flex: 0 0 300px; overflow-y: auto; border-right: 1px solid var(--line); }
.chat-log-view { flex: 1 1 auto; overflow-y: auto; padding: 12px 14px; background: #f7f9fc; }
.chat-log-warn {
  margin: 0; padding: 8px 14px; background: #fff8e6; border-bottom: 1px solid #f2e2b8;
  font-size: 12px; color: #8a6412; line-height: 1.6;
}

/* 对话态：`.chat-body` 不许自己滚（滚的是消息区），而且要让那一个窗格撑满高度 ——
   消息区滚动、输入框钉在底下不动，全靠这一条。少了它 `.chat-thread` 的 flex 没有
   可分配的高度，输入框会跑到面板外面去（`.chat-pane.active` 默认是 display:block）。 */
.chat-body.threadmode { overflow: hidden; }
.chat-body.threadmode .chat-pane.active {
  display: flex; flex-direction: column; height: 100%; min-height: 0;
}

/* ============================================================================
   手机上把聊天铺满整屏（2026-10-01 增量四十九）
   ----------------------------------------------------------------------------
   触发者是 `body.chat-full`。这个 class 由 `chatFullApply()`（app.js）加/摘，条件恒为
   「聊天开着 **且** 命中 `(max-width: 900px) and (pointer: coarse)`」——
   `pointer: coarse` 把「把桌面窗口拖窄」挡在外面，所以电脑端一点不变。

   ⚠ 定位写的是「`#chat-root` 铺满视口 + `#chat-panel` 用 `absolute` 撑满它」，
     **不是**把 panel 自己改成 `fixed`。两种写法在这里视觉等价，但 `fixed` 的包含块是
     **视口** —— 除非祖先带 transform / filter / contain。也就是说，哪天有人给
     `#chat-root` 加一条 `transform`（哪怕只是个小动画），panel 会当场缩成一个贴在小
     盒子上的方块，**而且不报任何错**。`fixed` 的祖先**永远**是 `absolute` 后代的
     包含块，这条不依赖别的属性。

   ⚠ 高度走 `--chat-vvh` 而不是写死 `100dvh`：软键盘弹出时 `dvh` **不缩**
     （Android 默认 `interactive-widget=resizes-visual`；iOS 的 `dvh` 只跟地址栏），
     写死的话输入框正好被键盘盖住 —— 那这个功能就白做了。变量由 app.js 里
     `visualViewport` 那两行喂（没有它的老浏览器依次退到 `100dvh`、`100vh`）。

   ⚠ 现在 index.html 的 viewport **没有** `viewport-fit=cover`，所以 `inset:0` 不会钻到
     刘海/Home 指示条底下。哪天有人加上它，这里必须补 `env(safe-area-inset-*)`。
   ============================================================================ */
body.chat-full #app,
body.chat-full #ui-cluster { display: none !important; }

body.chat-full #chat-root {
  left: 0; right: 0; bottom: auto;
  top: var(--chat-vvtop, 0px);
  height: 100vh;                      /* 不认 dvh/变量时的兜底 */
  height: var(--chat-vvh, 100dvh);
}
body.chat-full #chat-panel {
  position: absolute; inset: 0;
  width: auto; height: auto; max-height: none;
  border: 0; border-radius: 0; box-shadow: none;
}

/* ---- 窄屏（手机；也含把桌面窗口拖得比手机还窄的情况）----
   ⚠ 400px 这个数是算出来的、不是拍的：面板写死 384px，右边距 16px ⇒ 视口窄过 400px
     时左边会被切掉一截（375px 的屏上正好切掉 25px）。这里改成左右各 8px、宽度交给
     它自己撑满 ⇒ 从 400 往下连续过渡，**不会再有任何宽度下溢出**。
     上一版没这条：手机上聊天面板比屏幕还宽，左边被裁，看着就像界面坏了。
   ⚠ 头部那两条是必需的：页签（会话/群聊/好友/＋发起）+ 查记录 + ⚙ + ✕ 在 12px 字号下
     要约 353px，而面板 `overflow:hidden` ⇒ 溢出方向在**右边**，被裁掉的正是 ✕
     （唯一那个退出按钮）。收窄后 320px 的屏也放得下。 */
@media (max-width: 400px) {
  #chat-root { left: 8px; right: 8px; bottom: 8px; }
  /* ★ 2026-10-05 修（用户报：「某图标该在右边，某些手机浏览器跑到左边」）。
     上面那条是为**展开的面板**写的（不铺满，375px 的屏上左边被切 25px），
     但它对**收起态**一样生效 —— 而收起态里 #chat-root 只有一个圆钮，圆钮是块级盒
     （`display:flex`、宽 52px），在铺满的父盒里按 CSS 2.1 §10.3.3 左外边距算 0
     ⇒ 默认贴**左边**。于是屏幕 CSS 宽度 ≤400px 的机型上图标落在左下角，
     >400px 的机型（少见）才在右下角 —— 这就是「某些手机浏览器」的真身。
     `margin-left:auto` 让左外边距吃掉富余空间，把圆钮钉回右边：
     #chat-root 照旧铺满（面板不溢出的那条修法原样保留），只是圆钮靠右站。
     ⚠ 上面那句 `left:8px` 不能删 —— 面板的宽度还靠它撑满。 */
  #chat-bubble { margin-left: auto; }
  #chat-panel { width: auto; max-height: calc(100vh - 16px); }
  .chat-head { padding: 6px 8px; gap: 4px; }
  .chat-head button { padding: 3px 6px; font-size: 11.5px; }
  /* ★ 用户 2026-10-01：「我希望手机截图也可以发送」。手机发图的**唯一**可靠入口就是
     这颗（系统截图 → 相册 → 这里选）。所以手机上它得一眼看得见，不能是灰扑扑一行小字。
     ⚠ 不要给它加 `capture` 属性：那会把「相册」从系统弹的选择里去掉，变成只能现拍。 */
  .chat-imglbl {
    padding: 0 12px; font-size: 14.5px; font-weight: 600;
    background: #eef3fc; border-color: #cfe0ff; color: #24509b;
  }
}

/* 销售报表左栏的数据日历（2026-10-20 增量七十七）。
   红勾只表示「这一天导过」，不表示卖了多少 —— 所以它小、贴着日期角上，
   不抢日期本身的数字。 */
.sr-cal-head { display:flex; align-items:center; justify-content:space-between; }
.sr-cal-nav { display:flex; gap:2px; }
.sr-cal-btn { border:1px solid #dcdfe3; background:#fff; border-radius:4px;
  width:20px; height:20px; line-height:1; font-size:12px; color:#555;
  cursor:pointer; padding:0; }
.sr-cal-btn:hover { background:#eef2f7; }
.sr-cal-month { font-size:12px; color:#666; text-align:center; margin:4px 0 2px; }
.sr-cal { display:grid; grid-template-columns:repeat(7, 1fr); gap:2px; }
.sr-cal-w { text-align:center; font-size:11px; color:#9aa0a6; line-height:18px; }
.sr-cal-d { position:relative; border:0; background:transparent; font:inherit;
  font-size:12px; height:26px; line-height:26px; border-radius:4px;
  cursor:pointer; color:#333; padding:0; }
.sr-cal-d:hover { background:#eef2f7; }
.sr-cal-d.off { cursor:default; }
.sr-cal-d.has { color:#111; font-weight:600; }
.sr-cal-d.today { box-shadow:inset 0 0 0 1px #c8ccd1; }
.sr-cal-d.sel { background:#e8f0fe; }
.sr-cal-ok { position:absolute; right:2px; top:-1px; color:#d32f2f;
  font-size:10px; font-weight:700; font-style:normal; line-height:1; }
.sr-cal-tip { margin-top:6px; line-height:1.6; }
.sr-cal-ok-txt { color:#d32f2f; font-weight:700; }
