/* ============================================================================
   仲名药业 · 工作台 —— 零依赖样式表（不用任何 CDN，内网离线可用）

   设计语言（参考「今日工作台」那版）：
     · 浅灰底 + 大量留白，靠间距分层，不靠边框分隔
     · 卡片化：圆角 16px、极轻阴影；一屏里最多一张**深色卡**当主角
     · 全大写、小字距的「眉标」（eyebrow）给区块命名
     · 数字要大（38px），单位要小 —— 一眼看到量，而不是读到字
     · 主按钮是**深色药丸**，不是蓝色方块
   ============================================================================ */

:root {
  /* 冷灰底 + 纯白面：底和面差一档，靠 1px 冷灰描边分层 */
  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-2: #f2f4f7;
  --surface-3: #e8ebf0;
  --ink: #0f1720;
  --ink-2: #47525f;
  --ink-3: #6c7887;
  --line: #e3e7ee;
  --line-2: #eef1f5;

  /* 品牌：近黑（主按钮 / 主角卡）——简约靠它 */
  --brand: #0f1720;
  --brand-ink: #fff;            /* 压在品牌色上的字：深空主题里 --brand 是浅色，白字会看不见 */
  --brand-hover: #1f2a37;
  --accent: #f97316;

  /* 主强调色 = 电光蓝（科技感靠它）；--blue 是历史命名，实际就是"主色" */
  --blue: #0b6bf2;
  --blue-soft: #e8f1ff;
  --blue-soft-hover: #dfeafd; /* 柔和主按钮的悬停底（深空里必须是暗的，浅蓝会把蓝字吃掉） */
  --blue-deep: #0a4fbe;
  /* 第二强调色：只在品牌标记 / 进度等极少数地方出现 */
  --violet: #5b5bd6;
  --violet-soft: #eeedfd;
  --lime: #b6f24a;
  --red: #d92d20;
  --red-soft: #fdeceb;
  --yellow: #b25e09;
  --yellow-soft: #fdf3e3;
  --green: #0d7a4a;
  --green-soft: #e7f6ee;

  /* 项目页（项目卡 + 流程矩阵）用的一组"状态色薄层"。
     亮色主题里它们是浅浅一层琥珀/蓝/绿，深空主题下必须压暗 —— 所以也交给主题变量，
     页面里再不许写死十六进制（web.test.js 有一条测试盯着项目页这一段）。 */
  --row-hover: #fbfbfc;         /* 表格 / 矩阵一行悬停的底 */
  --line-hover: #d8dae0;        /* 卡片悬停时的描边 */
  --star-gold: #e8a33d;         /* 星标那颗金星 */
  --due-warn: #b07b2a;          /* 「还剩 N 天」的黄字（比 --yellow 沉，白底上才读得清） */
  --step-line-todo: #f0d9ae;    /* 待办中：卡片描边 */
  --step-line-running: #bcd6fb; /* 进行中：卡片描边 */
  --step-line-done: #cfe9db;    /* 已完成：卡片描边 */

  /* 待办卡的三档底色（红 / 黄 / 逾期）。
     浅色主题里这三档本来就是近白的一层薄色，写死也不要紧；但深空主题下它们还是近白，
     而字色跟着 --ink 变浅 —— 实测标题对比度掉到 1.12，等于看不见（2026-09-17 修）。
     所以提成令牌，深色那套单独给一组压暗的值。 */
  --card-red-bg: #fffcfc;
  --card-red-line: #f3cfd0;
  --card-yellow-bg: #fffdf8;
  --card-yellow-line: #f0e0c4;
  --card-overdue-bg: #fff6f6;
  --card-overdue-line: #eeb0b4;

  /* 科技感的三件套：冷光底、点阵网格、玻璃 */
  --bg-image: none;
  --grid-color: rgba(15, 23, 32, 0.045);
  --glass: rgba(255, 255, 255, 0.72);
  --glow: 0 0 0 3px rgba(11, 107, 242, 0.14);

  /* ---------------------------------------------------------------- 毛玻璃（2026-09-22）
     用户口径：「整体的设计，融合进去毛玻璃 ui 设计的风格」。

     ① **玻璃下面必须先有东西可糊** —— `--aurora` 就是那层光场，画在 `html::before`
        的固定层上（只栅格化一次，加多少层渐变都不心疼）。没有它，磨砂就是在磨一块纯色，
        白花 GPU 而已。原来那圈写死的蓝色冷光也并进来了，现在四套主题各配各的光。
     ② **只在"背后真有内容"的地方挂 backdrop-filter**。这条是这个仓库拿性能换来的：
        2026-09-18 侧栏用的就是 `var(--glass)` + `blur(14px)`，因为用户说「整体滑动起来很卡」
        被撤掉了 —— 它在 flex 里是一列、不盖在滚动内容上，背后只有纯色，糊了画面没区别，
        却让合成器一直背着 100vh 的模糊层（CDP 光栅 1226ms → 997ms）。
        `web.test.js` 里还有一条断言盯着，**不许再给 .sidebar 加 backdrop-filter**。
        所以分工是：
          · 浮层（弹窗 / 右键菜单 / 下拉）· 吸顶表头 · 卡片  → 真模糊（背后是光场和滚动内容）
          · 静态整列（侧栏）                                → 只半透明，不挂模糊
     ③ 玻璃面越白，字越稳。正文对比度 ≥ 4.5:1 是硬线，浅色底上一律 58% 起步。 */
  /* ⚠️ 一个反直觉的点（第一版就栽在这）：**把一块平滑渐变做高斯模糊 = 没模糊**。
     所以光场要**给够颜色**，玻璃才有"透出来"的看头；真正靠模糊吃饭的是
     吸顶表头 / 弹窗 —— 那里背后有文字和表格在滚，糊不糊一眼就能看出来。
     另一条：`--glass-shadow` 里那个 `inset 0 1px 0` 是**玻璃的上边缘高光**，
     比阴影更重要 —— 少了它，半透明面板只会像"没填色的框"，不像玻璃。
     ⚠️ 光场**刻意避开顶部那条页头带（y < 200）**：副标题用的是 `--ink-3`，
        在纯底色 #F6F7F9 上就只有 4.19:1，底色再一花就跌破 4.5:1
        （实测染到 #E9EFF8 时掉到 3.89:1）。所以色只铺在**看板 / 内容区**。 */
  --aurora:
    radial-gradient(900px 640px at -4% 62%, rgba(11, 107, 242, 0.3), transparent 60%),
    radial-gradient(820px 600px at 106% 60%, rgba(124, 92, 255, 0.26), transparent 58%),
    radial-gradient(1000px 700px at 40% 104%, rgba(0, 176, 148, 0.28), transparent 60%);
  --glass-bg: rgba(255, 255, 255, 0.58);         /* 卡片 / 面板：透得出光场 */
  --glass-bg-soft: rgba(255, 255, 255, 0.74);    /* 侧栏这种大面（不挂模糊，浓一点） */
  --glass-bg-strong: rgba(255, 255, 255, 0.86);  /* 弹窗 / 浮层：要读长文，白一点 */
  --glass-line: rgba(255, 255, 255, 0.72);       /* 玻璃的边：比底色亮 = "厚度" */
  --glass-blur: 18px;
  --glass-blur-sm: 12px;                         /* 吸顶表头这种窄条用小的 */
  --glass-sat: 165%;
  --glass-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0) 46%);
  /* 上边缘高光 + 落影，一起的（高光那一笔别删） */
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85), 0 12px 36px -16px rgba(15, 23, 32, 0.24);

  --radius: 12px;
  --radius-sm: 8px;
  --radius-lg: 16px;
  --shadow: 0 1px 2px rgba(15, 23, 32, 0.05);
  --shadow-md: 0 4px 14px -6px rgba(15, 23, 32, 0.18);
  --shadow-lg: 0 24px 60px -20px rgba(15, 23, 32, 0.35);
  --card-shadow: var(--shadow);
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sidebar-w: 216px;
  /* ★ 2026-09-22：图标形变的统一节奏（学自 @叨叨AI《UI设计必备的3个图标库》里的 Morphicons）——
     "图标之间的状态变化"要有一个节拍：所有展开/收起箭头、以后的双态图标都走它。
     带一点点回弹（1.28），所以是"变形"而不是"翻面"。只在 :root 定义一次：
     主题块只换颜色/圆角，不碰这条。 */
  --morph: 0.26s cubic-bezier(0.34, 1.28, 0.5, 1);

  /* ★ 2026-09-23（学自抖音《一款非常好使的动态图标库》= Morphicons）：**弹簧物理**四档手感。
     视频里的 spring / smooth / snappy / bouncy（机翻成"春天 / 光滑的 / 轻快的 / 有弹性的"）
     对应下面四条。停靠点是拿**二阶弹簧的阶跃响应解出来的**（不是手调的贝塞尔），
     ζ = c / (2√k) 就是阻尼比：
       smooth  ζ=1.00  不过冲，收得住          —— 大面板、慢慢展开
       spring  ζ=0.75  轻微过冲（峰值 1.03）   —— 默认
       snappy  ζ=0.93  最快、几乎不过冲        —— 小控件，点一下就要有响应
       bouncy  ζ=0.40  明显过冲（峰值 1.23）   —— 星标这种"值得高兴一下"的地方
     ⚠️ `linear()` 要 Safari 17.2+；每条前面那行 cubic-bezier 是给老引擎的兜底
        （不认 `linear()` 那条声明会整条作废、退回上一条，不会两边都不生效）。 */
  --spring-smooth: 0.48s cubic-bezier(0.22, 0.61, 0.36, 1);
  --spring-smooth: 480ms linear(0 0%, 0.021 4.2%, 0.074 8.3%, 0.146 12.5%, 0.226 16.7%, 0.309 20.8%, 0.391 25%, 0.469 29.2%, 0.541 33.3%, 0.606 37.5%, 0.665 41.7%, 0.717 45.8%, 0.763 50%, 0.803 54.2%, 0.838 58.3%, 0.868 62.5%, 0.894 66.7%, 0.916 70.8%, 0.935 75%, 0.951 79.2%, 0.964 83.3%, 0.976 87.5%, 0.985 91.7%, 0.993 95.8%, 1 100%);
  --spring-spring: 0.56s cubic-bezier(0.34, 1.4, 0.5, 1);
  --spring-spring: 560ms linear(0 0%, 0.042 4.2%, 0.142 8.3%, 0.271 12.5%, 0.409 16.7%, 0.541 20.8%, 0.659 25%, 0.759 29.2%, 0.841 33.3%, 0.904 37.5%, 0.951 41.7%, 0.984 45.8%, 1.006 50%, 1.019 54.2%, 1.025 58.3%, 1.027 62.5%, 1.026 66.7%, 1.023 70.8%, 1.019 75%, 1.015 79.2%, 1.011 83.3%, 1.007 87.5%, 1.004 91.7%, 1.002 95.8%, 1 100%);
  --spring-snappy: 0.38s cubic-bezier(0.3, 1.12, 0.5, 1);
  --spring-snappy: 380ms linear(0 0%, 0.028 4.2%, 0.096 8.3%, 0.185 12.5%, 0.282 16.7%, 0.381 20.8%, 0.474 25%, 0.559 29.2%, 0.635 33.3%, 0.701 37.5%, 0.758 41.7%, 0.806 45.8%, 0.847 50%, 0.88 54.2%, 0.907 58.3%, 0.929 62.5%, 0.946 66.7%, 0.96 70.8%, 0.971 75%, 0.979 79.2%, 0.986 83.3%, 0.991 87.5%, 0.995 91.7%, 0.998 95.8%, 1 100%);
  --spring-bouncy: 0.64s cubic-bezier(0.34, 1.56, 0.64, 1);
  --spring-bouncy: 640ms linear(0 0%, 0.092 4.2%, 0.312 8.3%, 0.582 12.5%, 0.838 16.7%, 1.042 20.8%, 1.172 25%, 1.23 29.2%, 1.227 33.3%, 1.184 37.5%, 1.12 41.7%, 1.052 45.8%, 0.995 50%, 0.954 54.2%, 0.932 58.3%, 0.927 62.5%, 0.934 66.7%, 0.948 70.8%, 0.965 75%, 0.981 79.2%, 0.993 83.3%, 1 87.5%, 1.003 91.7%, 1.003 95.8%, 1 100%);

  /* ★ 2026-09-23：图标描边粗细提成令牌（视频里那个能拖着调的 stroke 1 / 1.5 / 2 / 2.5）。
     原来写死在 icon() 的 `stroke-width="1.7"` 属性上；属性留着当兜底，
     实际生效的是 .zm-i 里那条 CSS（CSS 盖得住表现属性）。
     整站调粗细改这一个值；只想调某处，在容器上覆写 `--icon-stroke` 即可。 */
  --icon-stroke: 1.7;
}

/* 线图标（内联 SVG）：跟着字号走、颜色跟着文字走，和文字基线对齐 */
.zm-i {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  vertical-align: -0.14em;
  stroke: currentColor;
  /* 描边粗细走令牌（见 :root 的 --icon-stroke）—— 这条 CSS 盖得住 icon() 里的
     `stroke-width="1.7"` 表现属性，所以调粗细只要改令牌，不用动 JS。 */
  stroke-width: var(--icon-stroke, 1.7);
}

/* ============================================================================
   图标三条规矩（2026-09-22 学自抖音 @叨叨AI《UI设计必备的3个图标库》，并对着本仓库审计过）

   ① Lucide —— 管"基础统一"。
      本仓库本来就守住了：导航/按钮/表格里的图标**全部**出自 app.js 的 ICONS（54 枚线稿，
      0 枚 emoji、0 个写了名字却没有的图标）。审计脚本见 commit 说明。
      继续守的方式是**尺寸只允许这几档**（视频原话：「整体很统一，不容易让页面变乱」）：
          12 / 13 / 14  —— 行内（表格、按钮、标签里的）
          15 / 16 / 17  —— 结构性（侧栏导航、页头品牌标）
          18 / 20+      —— 大图标（空态、启动页），要单独说明用途
      不写尺寸 = 跟着字号走（1.05em），这是默认值，优先用它。
      ⚠️ 历史遗留还散着 10 / 11 / 22 / 26 / 34 几档，别照抄；新代码只从上面前三档里挑。

   ② Morphicons —— 管"状态变形"：同一个位置的两个状态（展开↔收起、日↔月）走 `--morph` 形变，
      不要硬换图标。见下面各 caret 的 transition。

   ③ Lordicon —— 管"现成动效"：加载中 / 完成反馈这类"不用自己写动画就有"的效果，
      用下面这三个工具类。用之前先问一句"这地方真的需要一个动效吗"，别满屏都在动。

   动效的无障碍兜底在文件上方那条 `@media (prefers-reduced-motion: reduce)`，
   三个类都会自动降到 0.01ms，不用各自再写一遍。
   ============================================================================ */

/* 转：加载中 */
.zm-spin { display: inline-block; animation: zm-spin 0.9s linear infinite; }
@keyframes zm-spin { to { transform: rotate(360deg); } }

/* 描着画出来：成功勾 / 失败叉（toast 用）。
   dasharray 给 28 —— check 那条 path 实测约 23，x 的两条各约 15，都够盖住。 */
.zm-draw path {
  stroke-dasharray: 28;
  stroke-dashoffset: 28;
  animation: zm-draw 0.42s cubic-bezier(0.2, 0.7, 0.3, 1) 0.08s forwards;
}
@keyframes zm-draw { to { stroke-dashoffset: 0; } }

* { box-sizing: border-box; }

/* 数字一律等宽（表格 / 日期 / 计数不会因为字形宽度跳动）—— 后台系统的"专业感"一半在这儿 */
.tabular, .mono, .pc-no, .sc-no, td, th, .pj-count, .nav-badge, time { font-variant-numeric: tabular-nums; }

/* 键盘可达性：焦点必须看得见（鼠标点击不出现，免得脏） */
:focus-visible { outline: none; box-shadow: var(--glow); border-color: var(--blue); }

/* 尊重系统的"减少动态效果" */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

html, body {
  margin: 0;
  height: 100%;
  color: var(--ink);
  font: 14px/1.6 -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}
/**
 * ★ 2026-09-22（做毛玻璃时挖出来的老 bug）：底色原来 `html, body` 一起写，
 *   **body 那层把 html::before 整个盖住了** —— 那一层是 `z-index: -1` 的负层，
 *   按绘制顺序，body 的背景块画在它**上面**。所以底下那圈"点阵 / 冷光"
 *   （现在叫 `--aurora` 光场）从写下来那天起就没露过面，一直是纯色底。
 *   修法：底色只留在 `html`（它会铺成画布底色），`body` 必须透明。
 *   ⚠️ 以后别再给 body 加不透明背景，加一次光场就瞎一次。
 */
html { background-color: var(--bg); }
body { background-color: transparent; }

/**
 * 点阵网格 + 顶部一层冷光（科技感在这儿，不在花花绿绿上）。
 *
 * ★ 2026-09-18（用户口径「整体滑动起来很卡」）：原来这三层背景写在 html/body 上、
 *   配 `background-attachment: fixed` —— 看着"背景不动"，代价是**每滚一帧都要把整屏
 *   （视网膜 ×2）重新光栅化**。CDP Tracing 实测（无头软件光栅、1440×900@2x、滚 2.5 秒）：
 *      现状 光栅 1226ms / 33720 个光栅任务  →  挪成"固定层"后 149ms / 12196（省 88%）
 *   固定层（position: fixed 的独立元素）只栅格化一次，滚动时合成器直接复用，画面一模一样。
 *   z-index: -1：画在 body 底色之上、所有内容之下（负 z-index 的绘制顺序就是这样）。
 */
html::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--bg-image, none), var(--aurora), radial-gradient(var(--grid-color) 1px, transparent 1px);
  /**
   * 三层图：底色图的 1 层 + 光场的 3 层 + 点阵的 1 层 = 5 层，尺寸/位置/平铺都得**一一对上**，
   * 少一个 `auto` 后面的层就会串位（原来那圈冷光是写死的，现在拆进 `--aurora` 由主题管）。
   */
  background-size: auto, auto, auto, auto, 22px 22px;
  background-position: 0 0, 0 0, 0 0, 0 0, -1px -1px;
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, repeat;
}

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { margin: 0 0 8px; font-weight: 600; }
h1 { font-size: 20px; }
h2 { font-size: 15px; }
h3 { font-size: 14px; }
p { margin: 0 0 8px; }
code, .mono { font-family: var(--mono); font-size: 12px; }

/* 眉标：全大写 + 字距，用来给区块命名（DAILY OVERVIEW / TODAY FOCUS）*/
.eyebrow {
  /* 小字距的全大写眉标：给区块命名用，别当装饰 */
  margin: 0;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ------------------------------------------------------------ 登录 */

#login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  /**
   * ★ 2026-09-22（用户口径：「把输入框放界面正中间」）：
   *   `#login` 是 `#app`（display:flex）里的**一个 flex 子项** —— 不写 flex:1 它就只缩到卡片
   *   那么宽，于是登录框贴在左边，看着像"没居中"。撑满即居中。
   */
  flex: 1 1 auto;
  width: 100%;
  background: var(--bg);
  padding: 24px;
}
.login-card {
  width: 100%;
  max-width: 372px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 32px 28px;
  box-shadow: var(--shadow-md);
}
.login-card h1 { font-size: 21px; margin-bottom: 4px; letter-spacing: -0.01em; }
.login-card .sub { color: var(--ink-3); margin-bottom: 22px; font-size: 13px; }
.login-card .field { margin-bottom: 14px; }
.login-card button { width: 100%; margin-top: 8px; justify-content: center; }
.login-hint { margin-top: 16px; color: var(--ink-3); font-size: 12px; line-height: 1.7; }

/* ------------------------------------------------------------ 布局 */

#app { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  /**
   * ★ 2026-09-18（「整体滑动起来很卡」）：原来是 `background: var(--glass)` +
   *   `backdrop-filter: blur(14px)`。但这个侧栏是 flex 里的一列、**不盖在滚动内容上**
   *   （窄屏 ≤860px 直接 display:none，没有抽屉），背后只有页面底色 —— 模糊一份纯色，
   *   画面几乎没区别，却让合成器一路背着个全高（100vh）的模糊层。
   *   CDP Tracing：只关这一项，滚动 2.5 秒的光栅从 1226ms 降到 997ms。
   *   换成不透明的 `--surface`：它和「72% 白玻璃叠在底色上」的合成结果差 ≤3/255
   *   （四套主题都验过：默认 #fdfdfe→#ffffff、warm #fefefb→#fffdfa、
   *    mint #fdfefe→#ffffff、dark #0f131a→#11151d），肉眼分不出。
   */
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}

/* 侧栏右边缘的拖拽把手：拖动调宽（双击恢复默认）。hover/拖动时才显形，平时不打扰 */
.sidebar-resizer {
  position: absolute;
  top: 0;
  right: -3px;
  bottom: 0;
  width: 7px;
  cursor: col-resize;
  z-index: 3;
}
.sidebar-resizer::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 3px;
  width: 1px;
  background: transparent;
  transition: background 0.12s ease;
}
.sidebar-resizer:hover::after,
body.resizing .sidebar-resizer::after {
  background: var(--blue);
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  /* ★ 2026-09-21（用户口径）：「左侧往上挪一下」——上边距 18 → 10，整列跟着上移 */
  padding: 10px 16px 12px;
  color: var(--ink);
}
.brand:hover { text-decoration: none; }
.brand .mark {
  /* 公司标志（/logo-mark.png —— 品牌原图处理出来的透明 PNG）。
     底板用令牌：浅色主题是白、深色主题是深灰，蓝标志在两套主题下都看得清。
     （原来是蓝紫渐变底 + 一个「仲」字，2026-09-15 换成真 logo。） */
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: 9px;
  background: var(--surface);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
}
.brand .mark img { width: 22px; height: 22px; display: block; }
.brand-text { min-width: 0; }
.brand b { display: block; font-size: 14px; letter-spacing: -0.01em; line-height: 1.25; }
.brand span { display: block; font-size: 11px; color: var(--ink-3); }

#page-back { display: inline-flex; gap: 4px; align-items: center; }
#page-back:not(:empty) { margin-right: 6px; }

/* 侧栏导航：分组小标题 + 每项左边一条"选中杠"，条目多了也不糊成一团 */
.nav { padding: 2px 10px 12px; flex: 1; overflow: auto; }
.nav-group {
  padding: 11px 10px 4px;
  color: var(--ink-3);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.08em;
}
.nav-group:first-child { padding-top: 6px; }
.nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-size: 13.5px;
  margin-bottom: 1px;
}
.nav a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.nav a.active { background: var(--blue-soft); color: var(--blue-deep); font-weight: 600; }
.nav a.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, var(--blue), var(--violet));
  box-shadow: 0 0 10px rgba(11, 107, 242, 0.45);
}
/**
 * 侧栏图标（2026-09-23，用户口径：「这些图标优化一下，加上交互效果」）
 *
 * 原来 `.ico` 挂着 `opacity: 0.85` —— 图标永远比旁边的字浅一档，整列看着发虚。
 * 现在图标和文字**同一个墨色重量**，状态差别交给颜色和形变去表达。
 * 描边在导航这一层单独加粗到 1.8（走 --icon-stroke 令牌，别处不受影响）。
 */
.nav a .ico {
  width: 19px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  flex: none;
  --icon-stroke: 1.8;
}
.nav a .ico .zm-i {
  width: 18px;
  height: 18px;
  vertical-align: 0;
  transition: transform var(--spring-snappy), color 0.16s ease;
}
/* 鼠标上来：图标**弹一下** + 变品牌色（"这里能点，而且点的就是它"） */
.nav a:hover .ico .zm-i { transform: scale(1.12); color: var(--blue); }
/* 按下去：缩一点 —— 手感，不是位移 */
.nav a:active .ico .zm-i { transform: scale(0.94); }
/**
 * 切到这一页：图标**弹簧落定**。
 * 导航每次换页会给新条目加 `.active`（app.js 的 renderRoute），加了就会重放一次动画。
 * ⚠️ 关键帧**只写 from**：这样结束态回落到元素自己的 transform（悬停时是 1.12），
 *    不会因为 `both` 把悬停放大锁死在 scale(1)。
 */
.nav a.active .ico .zm-i { animation: zm-nav-pop 0.38s var(--spring-snappy); }
@keyframes zm-nav-pop { from { transform: scale(0.78); } }

/* 侧栏条目能拖着换顺序（顺序记本机，见 app.js 的 bindNavDrag）
   拖的时候：抓手光标 + 被拖的那条淡出 + 目标位置画一条落点线 */
.nav a[draggable='true'] { cursor: grab; }
.nav a[draggable='true']:active { cursor: grabbing; }
.nav a.is-dragging { opacity: 0.4; }
.nav a.drop-before::after,
.nav a.drop-after::after {
  content: '';
  position: absolute;
  left: 6px;
  right: 6px;
  height: 2px;
  border-radius: 2px;
  background: var(--brand);
  box-shadow: 0 0 0 2px var(--blue-soft);
  pointer-events: none;
}
.nav a.drop-before::after { top: -1px; }
.nav a.drop-after::after { bottom: -1px; }

/* 导航角标：不点进去也知道有几件事等着 */
.nav a .nav-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 「我的工作台」上的新事项标记：一个小圆点（呼吸一下，但不吵） */
.nav-new {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--blue);
  box-shadow: 0 0 0 3px var(--blue-soft);
  animation: navNewPulse 2.4s ease-in-out infinite;
}
.nav-new[hidden] { display: none; }
@keyframes navNewPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.nav-badge {
  margin-left: 6px;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 650;
  line-height: 18px;
  text-align: center;
}
.nav-badge:empty { display: none; }
.nav-badge.hot { background: var(--red); color: #fff; }

/* 点侧栏模块后浮出来的二级入口（贴着侧栏右边缘，和参考图那套一样） */
.module-flyout {
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  position: fixed;
  z-index: 245;
  width: 190px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.module-flyout .mf-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 8px;
  border-bottom: 1px solid var(--line-2);
  margin-bottom: 6px;
}
.module-flyout .mf-icon { font-size: 15px; }
.module-flyout .mf-title { font-size: 13px; font-weight: 650; }
.module-flyout .mf-list { display: flex; flex-direction: column; }
.module-flyout .mf-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-size: 13px;
}
.module-flyout .mf-item:hover { background: var(--blue-soft); color: var(--blue); text-decoration: none; }
.module-flyout .mf-item.active { background: var(--blue-soft); color: var(--blue); font-weight: 600; }
.module-flyout .mf-item-icon { width: 18px; text-align: center; flex: none; }
.module-flyout .mf-item-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 模块页里的二级入口：**统一成一条分段控件**（`.tabs`，见 app.js 的 subTabsHtml）。
   原来这里是一套卡片网格（.sub-nav / .sub-card），2026-09-15 用户口径「采购那条不错，订单 / 商品 / 仓储也照这个来」
   —— 卡片那套没人用了，样式一并删掉，免得以后有人照着它再画一份。 */

/* 侧栏里有二级入口的模块：右边一个小箭头，提示点了会浮出东西 */
.nav a .nav-more { flex: none; display: inline-flex; align-items: center; color: var(--ink-3); font-size: 13px; margin-left: 2px; transition: transform var(--spring-snappy); }
.nav a:hover .nav-more { color: var(--blue); transform: translateX(2px); }
.nav a.active .nav-more { color: var(--ink-2); }

/* 占位板块（客户 / 订单 / 商品 / 采购 / 仓储）：一个像样的空态，不是一片空白 */
.ph-wrap { display: flex; justify-content: center; padding: 28px 0 40px; }
.ph-card {
  width: min(620px, 100%);
  padding: 20px 22px 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.ph-icon { font-size: 30px; line-height: 1; }
.ph-title { display: flex; align-items: center; gap: 8px; margin: 8px 0 5px; font-size: 18px; }
.ph-tag {
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-3);
  font-size: 11px;
  font-weight: 600;
}
.ph-lead { margin: 0 0 12px; color: var(--ink-2); font-size: 13px; line-height: 1.6; }
.ph-list { margin: 0; padding-left: 18px; color: var(--ink-2); font-size: 13px; line-height: 1.9; }
.ph-list li::marker { color: var(--ink-3); }
.ph-foot { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--line-2); color: var(--ink-3); font-size: 12px; }

.sidebar-foot {
  padding: 14px 16px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-3);
}
.sidebar-foot .me { color: var(--ink); font-weight: 600; font-size: 13px; }
.sidebar-foot a { color: var(--ink-2); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* 顶栏只留「返回 + 状态 + 操作」——标题归页面自己（参考图没有顶栏标题）*/
.topbar {
  /* ★ 2026-09-21：顶栏只剩返回键（风格/身份已删），没事时别白占一行高 */
  padding: 6px 32px 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap; /* 窄了让整块换行，不要把标签挤成竖排 */
  gap: 8px 10px;
  min-height: 0;
}
.topbar:empty { display: none; }
.topbar .spacer { flex: 1; }

/* 免登录模式下的「身份」切换：和顶栏徽标一个体量，安静地待在那儿 */
.identity-switch {
  position: relative; /* 自绘下拉的定位基准 */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--ink-3);
}
.identity-switch .identity-label { user-select: none; }

/* 按钮：上下两行（部门在上、姓名在下），沿用 .badge 的安静体量 */
.identity-btn {
  /* 这是"选择器"不是状态药丸：跟其它控件一样 8px 方角 */
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  min-width: 84px;
  padding: 3px 10px;
  font: inherit;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
}
.identity-btn:hover { border-color: var(--ink-3); }
.identity-btn[aria-expanded='true'] { border-color: var(--ink-3); }
.identity-dept {
  display: block;
  font-size: 10px;
  line-height: 1.25;
  color: var(--ink-3);
  max-width: 170px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.identity-name {
  display: block;
  font-size: 11.5px;
  line-height: 1.3;
  color: var(--ink-2);
  max-width: 170px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 弹出面板：绝对定位挂在按钮下沿，z-index 要高过内容区 / 粘性表头 / 弹窗，
   但又低于 toast（200）。顶栏本身不裁剪，所以不会被切掉。 */
.identity-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 150;
  min-width: 150px;
  max-width: 240px;
  max-height: 60vh;
  overflow: auto;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(20, 24, 34, 0.16);
}
.identity-menu[hidden] { display: none; }
.identity-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  padding: 5px 8px;
  font: inherit;
  font-size: 11.5px;
  text-align: left;
  color: var(--ink-2);
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.identity-option:hover { background: var(--surface-2); }
.identity-option.on { background: var(--surface-2); }

/* ---- 原生外壳（WKWebView）顶栏避让 ------------------------------------------
   外壳把网页顶部 52pt 当窗口标题栏：native/main.swift 的 DragAwareWebView.mouseDown
   在 y >= 高度-52 && x >= 84 时直接 performDrag，那一下 mousedown 不会交给网页。
   顶栏里的「身份」下拉 / 「返回」按钮就落在这 52pt 里（实测 bottom=35），所以点不动。
   只在**外壳里**把顶栏内容推下去；浏览器里布局完全不变。
   52 这个数来自外壳的 titleBarHeight，改那边时记得同步。 */
html.wk-shell .topbar { padding-top: 56px; }

/* ★ 2026-09-17（用户截图）：「工作台 / 仲名药业 · 第 0 期」也被那三个圆点压住了 ——
   顶栏让开了 56px，**侧栏却还是从 y=0 开始画**，品牌块（logo + 两行字）正好落在交通灯
   底下（灯在 y≈12~32，logo 从 y=18 起，实测就是压着的）。
   这里把侧栏**整块**推下去同样的高度：背景/磨砂照旧铺满这一条，看着就是一条正常的
   macOS 标题栏（左边空出来放灯），导航区自己缩一点，底部那张用户卡还在最下面。
   ⚠️ `.sidebar` 是 height:100vh + 全局 border-box，加 padding-top 不会把它顶出视口。
   浏览器里（没有 .wk-shell）一个像素都不变；窄屏（≤860px）侧栏本来就是 display:none。 */
html.wk-shell .sidebar { padding-top: 56px; }

/* ------------------------------------------------------------ 协同窗口（一横条）

   ★ 2026-09-27（用户口径）：「这里不要动态，做一个协同窗口，每个人设置自己的协同人员，
   方便快速沟通或催办事项」。位置和高度**沿用原来那条横条**（顶栏下面一行、不占竖向空间），
   换掉的是里面装什么：我设的协同人员 + 每人身上的「几条消息没读 / 几件事没办 / 我催过几次」。

   ⚠️ 颜色 / 圆角 / 阴影一律走 `var(--x)` 令牌 —— 换主题不掉色（这条是硬要求）。 */

/* 一行、不换行、定高：人再多也只是一横条（超出横向裁掉，不该把页面撑高）。 */
.activity-strip { padding: 6px 32px 0; min-width: 0; }
.activity-strip[hidden] { display: none; }
.activity-line {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  height: 42px;
  padding: 0 10px 0 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  font-size: 11.5px;
  color: var(--ink-2);
  overflow: hidden;
  white-space: nowrap;
}
.activity-line .activity-tag {
  flex: none;
  font-size: 10px;
  font-weight: 650;
  letter-spacing: 0.1em;
  color: var(--ink-3);
}
.activity-items {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  overflow: hidden;
}
.activity-muted { color: var(--ink-3); }

/**
 * 「协同」= 收起 / 展开这一横条（用户口径 2026-09-21：「不用一直挂着，需要了点击展开，
 * 不用的时候缩起来省空间」）。收起后只留「协同 · 几条新消息 · 几件事没办」。
 */
.collab-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 2px 6px 2px 0;
  border-radius: var(--radius-sm);
  color: inherit;
}
.collab-toggle:hover { background: var(--surface-2); }
/**
 * 箭头是**真图标**（朝右 chevron = 收起 / 朝下 chevron-down = 展开），点的时候由
 * `ZM.morphIcon` 做形状插值 —— 形状本身就表示状态，所以 CSS 不管旋转，只管占位对齐。
 */
.collab-caret {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-3);
}
/* ⚠️ 收起时那句「几条新消息 · 几件事没办」**一直在 DOM 里**，只在收起态显示：
   点收起那一下只切 class（要留着箭头做形状插值，不能重画这一条）。 */
.collab-fold-info { display: none; font-size: 11.5px; color: var(--ink-2); white-space: nowrap; }
/* 收起：人 / 「编辑」一起收掉（`display:none` 而不是 `visibility` —— 不占位才算真省地方） */
.collab-line.is-folded { height: 26px; }
.collab-line.is-folded .collab-items,
.collab-line.is-folded .collab-edit { display: none; }
.collab-line.is-folded .collab-fold-info { display: inline; }

/* 一张小卡：上下两行（部门小字在上、姓名在下），和原来那条「协同」条同一个版式 */
.collab-item {
  flex: 0 0 auto;
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
  padding: 3px 10px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  font: inherit;
  line-height: 1.15;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
}
.collab-item:hover { background: var(--surface); border-color: var(--line); }
.collab-item.on { background: var(--surface); border-color: var(--line); }
.collab-item .collab-top { display: inline-flex; align-items: center; gap: 4px; }
.collab-item .collab-dept { font-size: 10px; color: var(--ink-3); }
.collab-item .collab-name {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-2);
}
.collab-item:hover .collab-name { color: var(--ink); }
/* 有未读消息的人：名字变深 + 一枚红数字（走行内，绝对定位会被 overflow 裁掉） */
.collab-item.has-new .collab-name { color: var(--ink); }
.collab-item .collab-msg {
  display: inline-block;
  min-width: 13px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--red);
  color: #fff;
  font-size: 9.5px;
  font-weight: 650;
  line-height: 14px;
  text-align: center;
}
/* 「未办 N」/「N 件逾期」/「催 N」：三档颜色，一眼看出谁该被催 */
.collab-item .collab-todo {
  display: inline-block;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-3);
  font-size: 9.5px;
  font-weight: 600;
  line-height: 14px;
}
.collab-item .collab-todo.is-red { background: var(--red-soft); color: var(--red); }
.collab-item .collab-todo.is-nudged { background: var(--yellow-soft); color: var(--yellow); }

/* 横条最右边那个「编辑」：换人 / 加人 / 删人 / 排序 */
.collab-edit {
  flex: none;
  padding: 2px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 11px;
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
}
.collab-edit:hover { border-color: var(--ink-3); color: var(--ink); }

/* 按住拖动换位置：被抓住的那一张浮起来，整页光标变抓手、别选中文字 */
.collab-item.dragging {
  position: relative;
  z-index: 6;
  background: var(--surface);
  border-color: var(--blue);
  box-shadow: var(--shadow-lg);
  cursor: grabbing;
  transition: none;
}
.collab-items .collab-item { transition: transform 0.12s ease; }
body.collab-dragging { cursor: grabbing; user-select: none; }
body.collab-dragging .collab-item,
body.collab-dragging .ce-item { cursor: grabbing; }

/* 点某个人弹出来的小面板（fixed 挂 body：横条自己是 overflow:hidden，挂在里面会被裁掉） */
.collab-menu {
  position: fixed;
  z-index: 240;
  width: 232px;
  padding: 10px 12px 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  font-size: 12px;
  color: var(--ink-2);
}
.collab-menu-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.collab-menu-head b { font-size: 13.5px; color: var(--ink); }
.collab-menu-sub { font-size: 11px; color: var(--ink-3); }
.collab-menu-state { margin: 6px 0 8px; font-size: 11.5px; color: var(--ink-3); line-height: 1.6; }
.collab-menu-note input {
  width: 100%;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  font-size: 12px;
  color: var(--ink);
}
.collab-menu-note input:focus { outline: none; border-color: var(--blue); background: var(--surface); }
.collab-menu-acts { display: flex; align-items: center; gap: 6px; margin: 8px 0 6px; flex-wrap: wrap; }
.collab-menu-remove {
  width: 100%;
  padding: 4px 0;
  border: 0;
  border-top: 1px solid var(--line-2);
  background: transparent;
  color: var(--ink-3);
  font-size: 11.5px;
  text-align: left;
  cursor: pointer;
}
.collab-menu-remove:hover { color: var(--red); }

/* 「编辑」弹窗：左边通讯录（复用 .who-list / .who-opt 那套选人面板的样式）+ 右边已选 */
.ce-editor .ce-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
  margin-top: 8px;
}
/**
 * 选人面板复用 `.who-menu`（`.who-list` / `.who-opt` 那套已验证的样式），
 * 但这里它是**弹窗里的一栏**、不是浮在窗口上的浮层 —— 所以把 fixed / 固定宽高都收掉，
 * 改成撑满这一格、由 `.who-body` 自己滚。
 */
.who-menu.inline {
  position: static;
  width: auto;
  max-height: none;
  box-shadow: none;
}
.who-menu.inline .who-body { flex: 1 1 auto; min-height: 0; }
.ce-editor .ce-cand { height: 340px; }
.ce-picked { display: flex; flex-direction: column; min-width: 0; height: 340px; }
.ce-picked .ce-head {
  flex: none;
  padding: 4px 2px 6px;
  font-size: 11.5px;
  color: var(--ink-3);
}
.ce-picked .ce-head b { color: var(--ink); font-size: 13px; }
.ce-list-box {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.ce-list { display: flex; flex-direction: column; gap: 3px; }
.ce-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  cursor: grab;
}
.ce-item:hover { border-color: var(--ink-3); }
.ce-item.dragging {
  position: relative;
  z-index: 6;
  border-color: var(--blue);
  box-shadow: var(--shadow-lg);
  cursor: grabbing;
}
.ce-list .ce-item { transition: transform 0.12s ease; }
.ce-item .ce-grip { flex: none; display: inline-flex; color: var(--ink-3); }
.ce-item .ce-name { flex: 1 1 auto; min-width: 0; font-size: 12.5px; font-weight: 600; color: var(--ink); }
.ce-item .ce-sub { flex: none; font-size: 11px; color: var(--ink-3); }
.ce-item .ce-x {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-3);
  cursor: pointer;
}
.ce-item .ce-x:hover { border-color: var(--red); color: var(--red); }
.ce-full { padding: 6px 4px 2px; font-size: 11px; color: var(--yellow); }

/* 窄屏（手机 <430px 也在这一档）：横条上省掉小标签、收紧内边距，只留「部门 + 姓名」，
   别挤成一团（手机上一行放不下 8 个人是正常的，右边「编辑」永远钉在最右边） */
@media (max-width: 860px) {
  .activity-strip { padding: 6px 12px 0; }
  .collab-item { padding: 3px 8px; }
  .collab-item .collab-todo { display: none; }
  .ce-editor .ce-cols { grid-template-columns: minmax(0, 1fr); }
  .ce-editor .ce-cand,
  .ce-picked { height: 260px; }
}


/* ------------------------------------------------------------ 页头 */

.page-head {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 0 6px; /* 紧凑：原来 6/22，光这一块就吃掉近百像素 */
}
.page-head .ph-left {
  min-width: 0;
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: 4px 10px;
  flex-wrap: wrap; /* 窄屏就整段换行，不硬挤 */
}
.page-head .ph-right { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.page-title {
  font-size: 19px;
  font-weight: 640;
  letter-spacing: -0.02em;
  margin: 0;
  line-height: 1.25;
}
/* 标题带品牌标时（`titleIcon`，目前只有千问办公）：标和字排一行、居中对齐。
   没标的标题不加这个类 —— 保持原来的纯文本行盒，别的页面一个字都不动。 */
.page-title.has-mark { display: inline-flex; align-items: center; gap: 8px; }
/* 页头的品牌标：浅蓝圆角方块 + 线稿标。颜色走令牌，换主题不掉色。 */
.page-title-mark {
  width: 26px;
  height: 26px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--blue-soft);
  color: var(--blue);
}
.page-sub { color: var(--ink-3); font-size: 12.5px; line-height: 1.3; }

/* 区块标题（眉标 + 大标题 + 右侧动作）*/
.sec { margin-bottom: 22px; }
.sec-head {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-bottom: 12px;
}
.sec-head .sh-left { flex: 1; min-width: 0; }
.sec-head h2 { font-size: 17px; margin: 4px 0 0; letter-spacing: -0.01em; }
.sec-head .sh-right { display: flex; gap: 8px; align-items: center; }

/* ------------------------------------------------------------ 通用块 */

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  margin-bottom: 12px;
}
.panel-head {
  padding: 12px 18px;
  border-bottom: 1px solid var(--line-2);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.panel-head h2, .panel-head h3 { margin: 0; }
.panel-head .spacer { flex: 1; }
.panel-body { padding: 16px 18px; }
.panel-body.tight { padding: 0; }

.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.row.end { justify-content: flex-end; }
.grid { display: grid; gap: 14px; grid-template-columns: repeat(12, 1fr); }
.col-2 { grid-column: span 2; }
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-6 { grid-column: span 6; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }

/* 日期字段：输入框 + 日历按钮；点开的是自绘弹层（原生的太丑） */
.date-field { display: flex; align-items: center; gap: 6px; min-width: 0; }
.date-field input { flex: 1 1 auto; min-width: 0; }
.date-btn {
  flex: none;
  width: 28px;
  height: 28px;
  padding: 0;
  line-height: 1;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  cursor: pointer;
}
.date-btn:hover { border-color: var(--ink-3); background: var(--surface-2); }

.date-pop {
  position: fixed;
  z-index: 240;
  /**
   * ★ 2026-09-19（用户口径：「时间选择的日历太小了」）——
   * 原来 252px 宽、格子 32×28px：**每个日子只有 32 像素宽**，鼠标要点得准、手指更点不准
   * （工作台手机端是开着。Apple 的触摸目标建议 ~44px，桌面版取中间值）。
   * 现在 300px 宽、格子 ~38×34px。
   * `max-width` 是给窄屏（<430px）兜底的：弹层定位那边已经会左右夹住，这里再保一道别顶穿。
   */
  width: 300px;
  max-width: calc(100vw - 16px);
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.date-pop .dp-head { display: flex; align-items: center; gap: 6px; padding: 2px 2px 8px; }
.date-pop .dp-title { flex: 1 1 auto; text-align: center; font-size: 13px; font-weight: 650; }
.date-pop .dp-nav {
  flex: none;
  width: 24px;
  height: 24px;
  padding: 0;
  line-height: 1;
  font-size: 14px;
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  cursor: pointer;
}
.date-pop .dp-nav:hover { border-color: var(--ink-3); color: var(--ink); }
.date-pop .dp-week { display: grid; grid-template-columns: repeat(7, 1fr); padding-bottom: 4px; }
.date-pop .dp-week span { text-align: center; font-size: 11.5px; color: var(--ink-3); }
.date-pop .dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.date-pop .dp-day {
  height: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.date-pop .dp-day:hover { background: var(--surface-2); color: var(--ink); }
.date-pop .dp-day.other { color: var(--ink-3); opacity: 0.65; }
.date-pop .dp-day.today { border-color: var(--blue); color: var(--blue); font-weight: 650; }
.date-pop .dp-day.on { background: var(--blue); border-color: var(--blue); color: #fff; font-weight: 650; }
.date-pop .dp-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line-2);
}


.form-section {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 0 -4px;
  color: var(--ink-3);
  font-size: 11px;
  letter-spacing: 0.08em;
}
.form-section::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--line-2); }
.form-section:first-child { margin-top: 0; }
/* 只读字段（比如子项目的「项目分类」）：看得出来能看不能改 */
.field.is-locked .lbl { color: var(--ink-3); }
.field.is-locked select,
.field.is-locked input { background: var(--surface-2); color: var(--ink-3); cursor: not-allowed; }

/* 产品图片：左边预览、右边输入 + 上传；也可以直接在这里粘贴一张图 */
.pic-field { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.pic-prev {
  flex: none;
  width: 54px;
  height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.pic-prev .pic-img { width: 100%; height: 100%; object-fit: contain; } /* 标签照片要整张看全 */
.pic-prev .pic-emoji { font-size: 24px; line-height: 1; }
.pic-prev .pic-empty { font-size: 10.5px; color: var(--ink-3); }
.pic-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.pic-main input[type='text'] { width: 100%; }
.pic-acts { display: flex; align-items: center; gap: 10px; }
.pj-img { width: 16px; height: 16px; object-fit: cover; border-radius: 4px; vertical-align: -3px; }
.muted { color: var(--ink-3); }
/* ★ 2026-09-25：「全部事项」表里「已入」收齐了（≥ 采购数）那个数用绿色点一下（用户口径那张表的口径） */
.ok-text { color: var(--green); font-weight: 600; }
.small { font-size: 12px; }
.right { text-align: right; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.mt8 { margin-top: 8px; }
.mb8 { margin-bottom: 8px; }
.mb14 { margin-bottom: 14px; }
.hidden { display: none !important; }

/* ------------------------------------------------------------ 数据概览卡 */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 12px;
  margin-bottom: 26px;
}
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px 18px 15px;
  display: flex;
  flex-direction: column;
  min-height: 132px;
}
.stat .k {
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 6px;
}
.stat .k .ico { margin-left: auto; opacity: 0.55; font-size: 13px; letter-spacing: 0; }
.stat .v {
  font-size: 38px;
  font-weight: 650;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin-top: auto;
  display: flex;
  align-items: baseline;
  gap: 5px;
}
.stat .v .unit { font-size: 14px; font-weight: 500; color: var(--ink-3); letter-spacing: 0; }
.stat .note { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
.stat .foot {
  display: flex;
  justify-content: space-between;
  font-size: 11.5px;
  color: var(--ink-3);
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--line-2);
}
.stat.red .v { color: var(--red); }
.stat.yellow .v { color: var(--yellow); }
.stat.green .v { color: var(--green); }
.stat.brand .v { color: var(--blue); }

/* 主角卡：一屏只有一张，深色 */
.stat.hero {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.stat.hero .k { color: rgba(255, 255, 255, 0.55); }
.stat.hero .v { color: #fff; }
.stat.hero .v .unit { color: rgba(255, 255, 255, 0.5); }
.stat.hero .note { color: rgba(255, 255, 255, 0.6); }
.stat.hero .foot { color: rgba(255, 255, 255, 0.5); border-top-color: rgba(255, 255, 255, 0.12); }
.stat.hero .k .ico { opacity: 1; }

/* 进度条 */
.bar {
  height: 4px;
  border-radius: 999px;
  background: var(--surface-3);
  overflow: hidden;
  margin-top: 10px;
}
.bar > i { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.stat.hero .bar { background: rgba(255, 255, 255, 0.16); }
.stat.hero .bar > i { background: var(--lime); }

/* 便签卡：稍微歪一点，像真的贴在屏幕上 */
.note-card {
  background: #fdf8e6;
  border: 1px solid #f2e9c9;
  border-radius: var(--radius-lg);
  padding: 16px 18px 15px;
  display: flex;
  flex-direction: column;
  min-height: 132px;
  transform: rotate(-0.5deg);
  box-shadow: 0 2px 10px rgba(120, 100, 30, 0.07);
}
.note-card .k {
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9a8a4e;
  display: flex;
  align-items: center;
  gap: 6px;
}
.note-card .k .ico { margin-left: auto; letter-spacing: 0; }
.note-card .quote { font-size: 12.5px; line-height: 1.62; color: #6b5f33; margin-top: auto; }
.note-card input {
  border: 0;
  border-bottom: 1px dashed #d9cd9e;
  border-radius: 0;
  background: transparent;
  padding: 6px 0;
  font-size: 13px;
  color: #4a421f;
}
.note-card input:focus { outline: none; border-bottom-color: #b9a862; background: transparent; }

/* ------------------------------------------------------------ 按钮 */

button, .btn {
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  /* 次要按钮用 8px 方角；只有主按钮（深色）和小的状态药丸保留圆角，主次一眼分得出 */
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  transition: background 0.12s ease, border-color 0.12s ease;
}
button:hover, .btn:hover { background: var(--surface-2); text-decoration: none; }
button:disabled { opacity: 0.45; cursor: not-allowed; }
button.primary, .btn.primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink); /* 深空主题里品牌色是浅的 —— 写死 #fff 会变成白底白字 */
  font-weight: 500;
  border-radius: 999px; /* 主按钮是深色药丸 —— 这是这套界面的signature */
  padding: 6px 15px;
}
button.primary:hover { background: var(--brand-hover); }
button.danger { color: var(--red); border-color: #f0cdcd; background: var(--surface); }
button.danger:hover { background: var(--red-soft); }
button.ghost { border-color: transparent; background: transparent; color: var(--ink-2); }
button.ghost:hover { background: var(--surface-2); }
button.sm, .btn.sm { padding: 4px 10px; font-size: 12px; }
button.primary-soft { background: var(--blue-soft); border-color: transparent; color: var(--blue); font-weight: 500; }
button.primary-soft:hover { background: var(--blue-soft-hover); }

/* 行内纯文字按钮（卡片右下角用）*/
button.link-btn {
  border-color: transparent;
  background: transparent;
  color: var(--ink-3);
  padding: 3px 7px;
  font-size: 12px;
}
button.link-btn:hover { background: var(--surface-2); color: var(--ink); }

/* ------------------------------------------------------------ 表单 */

label.field { display: block; margin-bottom: 10px; }
label.field > span.lbl { display: block; font-size: 12px; color: var(--ink-2); margin-bottom: 5px; }
label.field > span.lbl i { color: var(--red); font-style: normal; margin-left: 2px; }
input, select, textarea {
  font: inherit;
  font-size: 13px;
  width: 100%;
  padding: 8px 11px;
  border: 1px solid #dfe1e6;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23, 24, 28, 0.07); }
textarea { min-height: 76px; resize: vertical; }
input[type="checkbox"] { width: auto; }
.inline { display: flex; gap: 8px; align-items: center; }
.filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.filters input, .filters select { width: auto; min-width: 130px; }

/* ------------------------------------------------------------ 表格 */

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
th {
  color: var(--ink-3);
  font-weight: 650;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--row-hover); }
tbody tr.clickable { cursor: pointer; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.empty {
  padding: 34px 16px;
  text-align: center;
  color: var(--ink-3);
  font-size: 13px;
  line-height: 1.65;
}
.table-scroll { overflow: auto; max-height: 72vh; }

/* ------------------------------------------------------------ 徽标 / 标签 */

.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  line-height: 17px;
  font-weight: 500;
  background: var(--surface-2);
  color: var(--ink-2);
  white-space: nowrap;
}
.badge.blue { background: var(--blue-soft); color: var(--blue); }
.badge.green { background: var(--green-soft); color: var(--green); }
.badge.red { background: var(--red-soft); color: var(--red); }
.badge.yellow { background: var(--yellow-soft); color: var(--yellow); }
.badge.violet { background: var(--violet-soft); color: var(--violet); }
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; flex: 0 0 7px; }
.dot.red { background: var(--red); }
.dot.yellow { background: #f0a020; }
.dot.green { background: var(--green); }
.dot.gray { background: #c9cdd6; }

/* ------------------------------------------------------------ 任务卡 */

.task-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  gap: 10px;
}
.task-grid.cols-1 { grid-template-columns: 1fr; }

.task-card {
  position: relative; /* 环节名水印（.tc-wm）要贴着卡片底边定位 */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--card-inner, inset 0 1px 0 rgba(255, 255, 255, 0.9));
  padding: 12px 13px 11px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* 整张卡可点开详情（只有卡里的按钮各走各的）—— 手型是唯一的"这里能点"提示 */
  cursor: pointer;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.task-card:hover { border-color: var(--blue); box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.9); }
/* 卡片里的按钮/链接自己还是手型、且不该继承卡片那层 hover 的歧义 */
.task-card button, .task-card a { cursor: pointer; }
/* 卡片自己的底色存成变量：环中间那个"洞"要跟着它走（写死的话深色主题里就是个白圈）。
   三档薄色都走令牌，深空那套在主题区里另给一组压暗的值。
   （注意：这里**不要**写出字面的主题选择器 —— web.test.js 用 indexOf 找深空那块，
   注释里出现一次就会被当成"深色主题从这里开始"。） */
.task-card { --card-bg: var(--surface); }
.task-card.is-red { --card-bg: var(--card-red-bg); border-color: var(--card-red-line); background: var(--card-bg); }
.task-card.is-yellow { --card-bg: var(--card-yellow-bg); border-color: var(--card-yellow-line); background: var(--card-bg); }
.task-card.is-done { opacity: 0.72; }
/* 逾期：比「紧急」更重 —— 左边一条红杠 + 更红的底，一眼分得出 */
.task-card.is-overdue { --card-bg: var(--card-overdue-bg); border-color: var(--card-overdue-line); background: var(--card-bg); box-shadow: inset 3px 0 0 var(--red); }
/* 选中的那件事（从编号预览跳进「我的事」时用它标出来：`/todo?id=13`）。
   用 outline 不用 box-shadow —— 逾期卡自己也占着 box-shadow，别互相盖掉。 */
.task-card.active { border-color: var(--blue); outline: 2px solid var(--blue-soft); outline-offset: 1px; }

/* 环节名水印：项目流的这一步（配货 / 仓库入库 / 审核…）压在**卡片底部**，大而淡。
   用户口径 2026-09-17：「项目流里的具体流程名字…以水印模式在卡片底部显示，要够大」。
   标题里不再带环节名（见 pages.js 的 todoDisplayTitle）。
   —— 它压在最底下一层（z-index 0），所以下面把所有内容行抬到它上面；贴底的进度条
   本来就有更高优先级的绝对定位，不受影响。 */
.tc-wm {
  position: absolute;
  left: 0;
  right: 0; /* 用户口径：水印放中间 */
  bottom: 6px; /* 再往上一点，整行都要露出来（用户口径：「要显示全」） */
  z-index: 0;
  overflow: hidden;
  white-space: nowrap;
  text-align: center;
  font-size: 46px; /* 实际字号由页面按字数内联给（wmFontSize），这里只是兜底 */
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--ink);
  opacity: 0.13;
  pointer-events: none;
  user-select: none;
}
/* 小方块矮，水印跟着收一档，并且要压在那条贴底进度之上 */
.task-card.sm .tc-wm { bottom: 8px; font-size: 38px; opacity: 0.12; }
.task-card > .tc-top,
.task-card > .tc-title,
.task-card > .tc-sub,
.task-card > .tc-time,
.task-card > .tc-progress,
.task-card > .tc-when,
.task-card > .tc-meta,
.task-card > .tc-foot { position: relative; z-index: 1; }

.tc-top { display: flex; align-items: center; gap: 7px; }
.tc-check {
  width: 17px;
  height: 17px;
  flex: 0 0 17px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid #ccd0d8;
  background: var(--surface);
  cursor: pointer;
}
.tc-check:hover { border-color: var(--green); background: var(--green-soft); }
.tc-check.done { border-color: var(--green); background: var(--green); }
.tc-no {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-3);
  letter-spacing: 0.02em;
  /* ★ 2026-09-18：编号**永远不换行** —— 一串 `XS-DEMO-WORK-86636` 在窄卡片里
     会被连字符断成四行，把卡片撑高一倍（用户截图里那张卡就是这么来的）。
     放不下就省略号，完整号在 title 里、点开还有预览。 */
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 编号前面那个小字：这是**哪一类单**（项目卡 / 待办…）。
   光一个 KAI-006 / TD-024 看不出是什么单；点它先出浮窗预览（见 pages.js 的 openNoPreview）。 */
.tc-no-kind { margin-right: 4px; color: var(--ink-3); opacity: 0.72; letter-spacing: 0; }
/* 编号浮窗预览：一行一个字段（标题 / 状态 / 负责人 / 时间 / 来源…） */
.pv { display: flex; flex-direction: column; gap: 9px; }
.pv-row { display: flex; gap: 10px; align-items: baseline; }
.pv-k { flex: 0 0 72px; color: var(--ink-3); font-size: 12px; }
.pv-v { flex: 1; min-width: 0; font-size: 13px; line-height: 1.5; word-break: break-word; }
.pv-pic { width: 100%; max-height: 200px; object-fit: contain; border-radius: var(--radius); background: var(--surface-2); }
/* 没图时的占位：**不许空一块** —— 三张卡的预览要一个模子（用户口径 2026-09-17） */
.pv-pic-none {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 120px;
  border: 1px dashed var(--line);
  background: transparent;
  color: var(--ink-3);
  font-size: 12px;
}
.tc-title {
  font-size: 14.5px;
  font-weight: 550;
  line-height: 1.42;
  letter-spacing: -0.01em;
  word-break: break-word;
}
.tc-title .star { color: #e8a33d; font-size: 12px; }
.tc-sub { font-size: 12px; color: var(--ink-2); }
.tc-due { font-size: 12px; color: var(--ink-3); }
.tc-due.hot { color: var(--red); font-weight: 550; }
.tc-due.warm { color: var(--yellow); font-weight: 550; }
.tc-foot {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
  padding-top: 8px;
  border-top: 1px solid var(--line-2);
}
.tc-foot .grow { flex: 1; }

/* 标签（优先级）：参考图里是柔和的圆角小块 */
.tag {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  background: var(--surface-2);
  color: var(--ink-3);
  white-space: nowrap;
}
.tag.urgent { background: #ffe9e6; color: #c3350f; }
.tag.low { background: var(--blue-soft); color: var(--blue); }
/**
 * ★ 2026-09-24：`.tag` 原来只有 urgent / low 两条着色，可代码里到处都是
 *   `class="tag blue"` / `tag yellow`（采购卡面的「待补价 / 部分入库 / 在办 / 已审批 · 待下单」、
 *   跟单面板里采购单的验收状态…）—— 那些**一直是没上色的裸灰 tag**（等于这枚章白挂了）。
 *   补齐和 `.badge.*` 同一套的语义色（只用令牌，四套主题跟着走）。
 */
.tag.blue { background: var(--blue-soft); color: var(--blue); }
.tag.green { background: var(--green-soft); color: var(--green); }
.tag.yellow { background: var(--yellow-soft); color: var(--yellow); }
.tag.red { background: var(--red-soft); color: var(--red); }
.tag.violet { background: var(--violet-soft); color: var(--violet); }

/* ------------------------------------------------------------ 看板 */

.kanban { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 14px; align-items: flex-start; }
.lane {
  flex: 0 0 262px;
  width: 262px;
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 10px;
  min-height: 150px;
}
.lane-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px 10px;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.lane-head .count { margin-left: auto; font-weight: 600; letter-spacing: 0; font-size: 11.5px; }
.lane .task-card { margin-bottom: 8px; }

/* 兼容 platform 的 laneCard 组件（跟单看板还在用它）*/
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid #c9cdd6;
  border-radius: var(--radius-sm);
  padding: 10px 11px;
  margin-bottom: 8px;
  cursor: pointer;
}
.card:hover { border-color: #d8dae0; box-shadow: var(--shadow-md); }
.card.red { border-left-color: var(--red); background: #fffcfc; }
.card.yellow { border-left-color: #f0a020; background: #fffdf8; }
.card .no { font-weight: 600; font-size: 13px; font-family: var(--mono); }
.card .who { color: var(--ink-2); font-size: 12px; margin-top: 3px; }
.card .meta { color: var(--ink-3); font-size: 11.5px; margin-top: 5px; display: flex; justify-content: space-between; gap: 6px; }
.card .amt { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ 时间轴 */

.timeline { position: relative; padding-left: 20px; }
.timeline::before {
  content: "";
  position: absolute;
  left: 5px; top: 6px; bottom: 6px;
  width: 2px; background: var(--line);
}
.tl-item { position: relative; padding-bottom: 14px; }
.tl-item::before {
  content: "";
  position: absolute;
  left: -19px; top: 5px;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid #c9cdd6;
}
.tl-item.open::before { border-color: var(--blue); background: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.tl-item.red::before { border-color: var(--red); background: var(--red); box-shadow: 0 0 0 3px var(--red-soft); }
.tl-item .tt { font-weight: 600; font-size: 13px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.tl-item .td { color: var(--ink-3); font-size: 12px; }

/* ------------------------------------------------------------ 弹窗 / 提示 */

.mask {
  /* 弹窗底：磨砂 + 冷色，不是纯黑蒙层 */
  backdrop-filter: blur(3px) saturate(120%);
  -webkit-backdrop-filter: blur(3px) saturate(120%);
  position: fixed; inset: 0;
  background: rgba(20, 21, 26, 0.42);
  backdrop-filter: blur(2px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 56px 16px;
  overflow: auto;
  /* 滚到底别再把手底下的工作台页面一起带着滚（"整体被上下滑动"的另一半原因） */
  overscroll-behavior: contain;
  z-index: 100;
}
.modal {
  background: var(--surface);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 560px;
  box-shadow: var(--shadow-lg);
  animation: rise 0.16s ease-out;
}
@keyframes rise { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
.modal.wide { max-width: 880px; }
.modal-head { padding: 16px 20px 12px; display: flex; align-items: center; }
.modal-head h2 { margin: 0; font-size: 17px; letter-spacing: -0.01em; }
.modal-head .x { margin-left: auto; }
/* 标题右边的小动作（比如大图窗的「复制图片」）：紧挨着关闭按钮 */ 
.modal-head .head-extra { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.modal-head .head-extra + .x { margin-left: 8px; }
.modal-head .head-btn { font-size: 12.5px; padding: 3px 10px; }
.modal-body { padding: 4px 20px 18px; max-height: 68vh; overflow: auto; }
.modal-foot {
  padding: 13px 20px;
  border-top: 1px solid var(--line-2);
  display: flex; gap: 10px; justify-content: flex-end;
}

#toasts { position: fixed; right: 20px; bottom: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
.toast {
  background: var(--ink); color: #fff;
  padding: 10px 15px; border-radius: 999px;
  font-size: 13px; max-width: 400px;
  box-shadow: var(--shadow-lg);
  animation: pop 0.18s ease-out;
}
.toast.err { background: var(--red); }
.toast.ok { background: var(--green); }
/* ★ 2026-09-22：成功 / 失败前面缀一枚**自己画出来**的小图标（视频里的 Lordicon 那套
   "现成动效"，对应"发送完成"）。图标由 app.js 的 ZM.toast 插进来 ——
   toast 的文字仍然是纯文本（有回归测试盯着 node.textContent = message），别改成拼 HTML。 */
.toast.has-glyph { display: flex; align-items: center; gap: 7px; }
.toast-glyph { display: inline-flex; flex: none; }

/* 确认框 / 提示框里的正文：**保留换行**。
   这些文案天然是多行的（一行说明 + 几条"· 出路"），而 <p> 默认把 \n 折成一个空格 ——
   于是四条出路挤成一大坨，看着像乱码（2026-09-16 修）。 */
.dlg-msg { white-space: pre-line; margin: 0; }
@keyframes pop { from { transform: translateY(6px); opacity: 0; } to { transform: none; opacity: 1; } }

/* ------------------------------------------------------------ 明细行编辑 */

.items-table { width: 100%; }
.items-table th { position: static; }
.items-table td { padding: 5px 6px; }
.items-table input, .items-table select { padding: 5px 8px; font-size: 12.5px; }
/**
 * ★ 2026-09-19（用户口径）：「数量框太小，单价框又太大」——
 * 数量 / 单价这种小数字格**藏掉原生上下箭头**（Safari 的 `inner-spin-button`）。
 * 两个好处：① 箭头本来就有半个格子宽，占了地方还显得数字没对齐（截图里数量框那个圈就是它）；
 *          ② 这几格都是**键盘直接打数字**的（`type=number` 仍保留数字键盘 / 校验），
 *             没人会去点那两个 1px 的箭头。
 * 金额那格不是输入框，不受影响。
 */
.items-table input[type="number"]::-webkit-outer-spin-button,
.items-table input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.items-table input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.items-table .amt { font-variant-numeric: tabular-nums; color: var(--ink-2); }
/* 单价那格 = 采购价输入框 + 「暂缺」小勾（用户口径 2026-09-17：暂缺紧跟采购价） */
.items-table .ie-price { display: flex; align-items: center; gap: 6px; }
.items-table .ie-price input[data-f="price"] { flex: 1 1 auto; min-width: 0; }
.items-table .ie-price input[data-f="price"]:disabled { background: var(--surface-2); color: var(--ink-3); }
.items-table .ie-pending {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  font-size: 11.5px;
  color: var(--ink-3);
  white-space: nowrap;
  cursor: pointer;
}
.items-table .ie-pending input { padding: 0; margin: 0; }
.items-table .ie-pending:has(input:checked) { color: var(--red); font-weight: 600; }

/* 盘点明细：差异那一列（盘多了绿、盘少了红、正好 muted）—— 一眼看出哪些格子对不上 */
.stk-table td.num { font-variant-numeric: tabular-nums; }
.stk-table .stk-up { color: var(--green); }
.stk-table .stk-down { color: var(--red); }
.items-total { text-align: right; font-size: 13px; padding: 8px 12px; }
.items-total b { font-size: 16px; }

/* ------------------------------------------------------------ 权限矩阵 */

.perm-group { border-top: 1px solid var(--line-2); padding: 10px 0; }
.perm-group:first-child { border-top: 0; }
.perm-group .gt { font-weight: 600; font-size: 13px; margin-bottom: 6px; }
.perm-actions { display: flex; gap: 14px; flex-wrap: wrap; }
.perm-actions label { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--ink-2); }

/* 分段控件式的 tabs */
.tabs {
  display: inline-flex;
  gap: 2px;
  background: var(--surface-2);
  border-radius: 8px;
  padding: 2px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.tabs button {
  border: 0;
  background: transparent;
  border-radius: 6px;
  padding: 5px 13px;
  color: var(--ink-2);
  font-size: 13px;
}
.tabs button:hover { background: transparent; color: var(--ink); }
.tabs button.active { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--shadow); }
/* 二级入口条也会用 <a>（hash 链接，见 app.js 的 subTabsHtml）—— 样式和 button 一套 */
.tabs a {
  display: inline-flex;
  align-items: center;
  border-radius: 6px;
  padding: 5px 13px;
  color: var(--ink-2);
  font-size: 13px;
}
.tabs a:hover { color: var(--ink); text-decoration: none; }
.tabs a.active { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--shadow); }
/* 页面内顶部的二级入口条：间距交给页面自己的 gap，别再加 20px */
.tabs.sub-tabs { margin-bottom: 0; flex: none; }
/* 模块页（订单 / 商品 / 采购 / 仓储）的分段控件再紧一点：和页头贴着 */
.tabs.sub-tabs { padding: 2px; }
.tabs.sub-tabs a { padding: 4px 12px; font-size: 12.5px; }
/* 收进「设置」的二级入口（仓库管理 / 仓位管理…）：页签行最右边一颗小字按钮，不抢眼 */
.tabs .tab-setup {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--ink-3);
  font-size: 12px;
  padding: 4px 10px !important;
}
.tabs .tab-setup:hover { color: var(--ink); background: var(--surface); }

.doc-head-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px 20px; }
.doc-head-grid .k {
  color: var(--ink-3);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 3px;
}
.doc-head-grid .v { font-size: 13.5px; }

.chain { display: flex; flex-direction: column; gap: 6px; }
.chain-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
}
.chain-item .rel { color: var(--ink-3); font-size: 12px; }

/* ------------------------------------------------------------ 两栏推进区 */

.focus-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(292px, 1fr));
  gap: 22px;
  align-items: start;
}
.focus-col > .fh {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
}
.focus-col > .fh .name { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.focus-col > .fh .n { font-size: 12px; color: var(--ink-3); }
.focus-col > .fh .spacer { flex: 1; }
.focus-col .task-grid { grid-template-columns: 1fr; }

@media (max-width: 860px) {
  .content { padding: 6px 18px 60px; }
  .topbar { padding: 12px 18px 0; }
  html.wk-shell .topbar { padding-top: 56px; }
  .activity-strip { padding: 8px 18px 0; }
  .page-title { font-size: 25px; }
  .sidebar { display: none; }
}

/* ============================================================================
   通讯录（组织框架 + 员工）—— 沿用同一套设计语言：浅灰底、卡片化、眉标大写。
   命名前缀：org（左栏树 / 右栏员工）
   ============================================================================ */

.grow { flex: 1; }

.org-layout { display: flex; gap: 16px; align-items: flex-start; }
/* 252px 太窄：树到第四层（生产部 > 车间）时缩进吃掉 64px，
   「1号灌装车间」被截成「1号」。加宽到 300px，深层的名字才看得全。 */
.org-side {
  position: relative;
  /* 宽度可由拖拽把手调（记在本机）；--org-w 由 pages-org.js 设 */
  flex: 0 0 var(--org-w, 300px);
  width: var(--org-w, 300px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 12px 10px 8px;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 14px;
  min-height: min(68vh, 600px);
}
/* 拖宽把手：贴在左栏右边缘上 */
.org-side-resizer {
  position: absolute; top: 0; right: -3px; bottom: 0;
  width: 7px; cursor: col-resize; z-index: 2;
  border-radius: 4px;
}
.org-side-resizer:hover { background: var(--blue-soft); }
.org-side-head { display: flex; align-items: center; gap: 8px; padding: 2px 6px 10px; }
.org-side-head .ps-title { flex: 1; min-width: 0; }
.org-side-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; }
.org-side-foot { border-top: 1px solid var(--line-2); margin-top: 8px; padding-top: 8px; }

/* 树节点：缩进由内联 padding-left 给，层级越深越往里 */
.org-node { margin: 1px 0; }
/* 一行 = 收起箭头 + 节点按钮（箭头固定占位，四级缩进才对得齐） */
.org-node-line { display: flex; align-items: center; }
.org-caret-slot { flex: none; width: 18px; display: inline-flex; justify-content: center; }
.org-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-3);
  transform: rotate(90deg);
  transition: transform var(--spring-snappy), background 0.12s ease;
}
.org-caret:hover { background: var(--surface-3); color: var(--ink); }
.org-caret.closed { transform: rotate(0deg); }
.org-collapse-all {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--ink-3);
  font: inherit;
  font-size: 11.5px;
}
.org-collapse-all:hover { border-color: var(--ink-3); color: var(--ink); }
.org-node-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font-size: 13px;
  line-height: 1.35;
  padding: 6px 9px;
  text-align: left;
  cursor: pointer;
}
.org-node-btn:hover { background: var(--surface-2); color: var(--ink); }
.org-node.active > .org-node-btn { background: var(--blue-soft); color: var(--blue); font-weight: 600; }
/* 部门行右边的「＋」：在它下面建子部门（用户口径 2026-09-17）。
   平时不出现 —— 一屏几十行都挂个加号太吵；鼠标移到这一行 / 键盘 Tab 到它才亮。
   触屏没有 hover，直接常显（不然手机上根本点不到）。 */
.org-add {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.org-node-line:hover > .org-add,
.org-node-line:focus-within > .org-add { opacity: 1; }
.org-add:hover { background: var(--blue-soft); color: var(--blue); }
@media (hover: none) {
  .org-add { opacity: 1; }
}
.org-ico { flex: none; width: 16px; text-align: center; font-size: 12.5px; }
/* 名字**不许被挤成半个**：深层的节点缩进大，min-width:0 + flex:1 会被压到只剩一个字。
   给个下限（约 3 个字）—— 名字比右边的计数重要。 */
.org-name {
  flex: 1 1 auto;
  min-width: 3.5em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.org-note { flex: none; color: var(--yellow); font-size: 10.5px; }
.org-count {
  flex: none;
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-3);
}
.org-node.active > .org-node-btn .org-count { color: var(--blue); }
.org-hint { font-size: 11px; color: var(--ink-3); padding: 10px 8px 4px; line-height: 1.5; }

.org-main { flex: 1 1 auto; min-width: 0; }
.org-main-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.org-crumb { font-size: 15px; font-weight: 650; letter-spacing: -0.01em; }

/* 通讯录：员工头像。有照片显示圆角照片，没有就用姓名首字色块（.avatar-fallback） */
.avatar-wrap { display: inline-flex; width: 36px; height: 36px; flex: none; vertical-align: middle; }
.avatar {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--surface-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  user-select: none;
}
.avatar-fallback { border-radius: 10px; }
.avatar-on-fail { display: none; }
.avatar-cell { width: 52px; }

/* ============================================================================
   日程 / 项目 —— 第 0 期新增两块界面（按需求方给的参考图）
   沿用同一套设计语言：浅灰底、卡片化、眉标大写、主按钮深色药丸。
   命名前缀：quad（日程三块）、pj（项目左栏）、fl/fc（项目名称卡片 / 流程列）
   ============================================================================ */

/* 演示数据提示条：这版是纯界面，必须让人一眼看见"按钮是样子货" */
.demo-note {
  background: var(--yellow-soft);
  border: 1px solid #f0e0c4;
  color: #7a4405;
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: 12.5px;
  margin-bottom: 18px;
}
.demo-note b { color: #5c3303; }
.stats-3 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

/* ------------------------------------------------------------ 日程：四象限（首页） */

/* 首页声明了 fill + bleed：内容区不留左右边距、四块铺满剩下的高度，各自滚动 */
#app.page-bleed .content { padding-left: 0; padding-right: 0; }
#app.page-bleed .page-head { padding-left: 32px; padding-right: 32px; }

.quad-board { display: flex; flex-direction: column; gap: 8px; }
/* ★ 2026-09-19：四象限上面那排「按卡片属性筛」（全部 / 采购审批 / 跟单 / 采购…）—— 只占一条的高度 */
.quad-filter { flex: none; margin: 0 0 2px; }
/**
 * ★ 2026-09-21：销售看板「跟单中」列里的**小组头**（采购中 / 罐装中 / 组装中 / 跟单中）——
 * 一条虚线下划 + 计数徽标，把一列里的几小组分开，但不抢卡片的注意力。
 */
/** 2026-09-21：还没走到采购/罐装/组装的那几条（还在跟单）—— 摆在组头下面、子列之前 */
.sales-loose { display: flex; align-items: flex-start; gap: 8px; padding: 6px 8px 2px; }
.sales-loose-k { flex: none; padding-top: 2px; font-size: 11px; font-weight: 650; color: var(--ink-3); white-space: nowrap; }
.sales-loose .quad-cards { flex: 1; min-width: 0; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.sales-sub-head { display: flex; align-items: center; gap: 6px; margin: 6px 0 2px; padding-bottom: 2px; border-bottom: 1px dashed var(--line); font-size: 11.5px; font-weight: 650; color: var(--ink-2); }
.sales-sub-head:first-child { margin-top: 0; }
.sales-sub-n { padding: 0 5px; border-radius: 999px; background: var(--surface-3); color: var(--ink-3); font-size: 10.5px; font-weight: 600; }
/** ★ 2026-09-21：采购单卡面上的**商品明细**（采购中 / 待补差价那两列看它） */
.tc-po-items { display: flex; flex-direction: column; gap: 1px; margin-top: 1px; }
.tc-po-item { display: flex; align-items: baseline; gap: 6px; font-size: 11px; color: var(--ink-2); }
.tc-po-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tc-po-item b { flex: none; color: var(--ink); }
.tc-po-more { font-size: 10.5px; color: var(--ink-3); }
/* 采购类卡面上的那行时间（申请/需求、采购/到货）—— 比正文小一档，别抢标题 */
.tc-biz-time { color: var(--ink-3); }
/**
 * ★ 2026-09-21：跟单卡上「采购申请**被驳回**」那一行（用户口径：「驳回后没任何变动」）——
 * 申请单退回草稿后不派卡、不进工作台，只有这一行告诉跟单员为什么被打回来，所以做显眼（黄底黄字）。
 */
.dp-req-rejected { display: flex; align-items: center; gap: 6px; margin: 6px 0; padding: 7px 10px; border-radius: var(--radius-sm); background: var(--red-soft); color: var(--red); font-size: 12.5px; font-weight: 600; }
.dp-stock-rejected { display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px; border-radius: var(--radius-sm); background: var(--yellow-soft); color: var(--yellow); font-size: 12px; font-weight: 600; }
/**
 * ★ 2026-09-19：**销售订单总卡**（业务员工作台上那张）——
 * 一行「项目卡 xxx、yyy · 客户 · 下单 · 交货」+ 一条订单进度条。
 * 进度条是 N 段小格子（一段 = 单据流程的一步），走到第几步就点亮到第几段。
 */
.tc-order-line { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 4px; font-size: 11px; color: var(--ink-2); }
.tc-order-k { color: var(--ink-3); font-size: 10.5px; }
/* 「项目卡还没建」那句：**一行说完**（窄卡上也不许折行 —— 折一行就白吃一行高度） */
.tc-order-none { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 项目卡号：**小药丸**，一行排 2~3 个（用户口径 2026-09-19 第三轮：「缩小，考虑一次写2-3个」） */
.tc-proj-link { padding: 0 5px; border-radius: 4px; background: var(--violet-soft); color: var(--violet); font-family: var(--mono); font-size: 10px; line-height: 15px; text-decoration: none; white-space: nowrap; }
.tc-proj-link:hover { color: var(--brand-ink); background: var(--violet); }
/**
 * 订单卡的"门面"：客户名一行，**下单 / 交货两行两列对齐**（用户口径 2026-09-19 第四轮：
 * 「下单和交货做上下对齐」）—— 客户名跨两列，标签一列、日期一列，所以两个日期左边对齐。
 */
.tc-order-head { display: grid; grid-template-columns: auto auto; justify-content: start; align-items: baseline; gap: 0 7px; padding-left: 7px; border-left: 2px solid var(--blue); }
.tc-order-who { grid-column: 1 / -1; font-size: 13px; font-weight: 700; color: var(--ink); }
.tc-order-when { font-size: 11.5px; color: var(--ink-2); font-family: var(--mono); white-space: nowrap; }
/**
 * 「订单」卡的整卡瘦身（用户口径第三轮：「整体太大」）——
 * ① 内边距/行距各收一档；② 底部的「开始·截止 + 时间进度条」去掉：
 * 下单/交货已经在门面那块写了，再画一条是重复占高度。
 * 第四轮又补：③ 剩余时间**放大**（14px 加粗）；④ 前面那枚「待办」徽标**去掉** ——
 * 这张卡是"订单总卡"，进度条才是它的状态，写"待办"没人对得上（用户直接问了"是什么意思"）。
 *
 * ★ 2026-09-20（用户口径）：「**销售的事项卡太高了，排版和尺寸调整一下**」——再收一档：
 * 内边距 9/10/8 → 7/9/6、行距 4 → 3、进度条不再额外顶 3px 上边距
 * （加上"空的那行不渲染""项目卡那句一行说完"，一卡省掉 ~50px）。
 */
.task-card.is-order { padding: 7px 9px 6px; gap: 3px; }
.task-card.is-order > .tc-when,
.task-card.is-order > .tc-bar { display: none; }
.task-card.is-order > .tc-meta > .badge { display: none; }
/* ★ 2026-09-20（用户口径）：「订单被财务驳回后……在**原有订单事项卡**上标注状态就行」——
   驳回状态现在标在订单卡上，所以红的「驳回待整改」必须露出来（它是有信息量的）；
   同日又：「跟单员还未确定交期……跟单状态标记成橙黄色，代表等待跟单员确定」——
   黄色的「待跟单」同理。上面那行只压掉没信息量的「待办」（用户原话：「写"待办"没人对得上」）。 */
.task-card.is-order > .tc-meta > .badge.red,
.task-card.is-order > .tc-meta > .badge.yellow { display: inline-flex; }
.task-card.is-order > .tc-meta { margin: 0; }
.task-card.is-order > .tc-meta .tc-left { font-size: 14px; font-weight: 700; }
.task-card.is-order .tc-wm { bottom: 2px; opacity: 0.1; }
.tc-prog { display: flex; flex-direction: column; gap: 3px; margin-top: 3px; }
.task-card.is-order .tc-prog { margin-top: 0; gap: 2px; }
.tc-prog-bar { display: flex; gap: 2px; }
.tc-prog-bar i { flex: 1; height: 3px; border-radius: 2px; background: var(--surface-3); }
.tc-prog-bar i.on { background: var(--blue); }
/**
 * 每一步的**名字**写在格子下面（用户口径 2026-09-19 第三轮：「订单进度显示具体步骤」）——
 * 5 列等宽，走过的+当前那步蓝色（当前那步加粗），没走到的灰。
 */
.tc-steps { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 2px; }
.tc-step { font-size: 9.5px; line-height: 1.3; color: var(--ink-3); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tc-step.is-on { color: var(--blue); }
.tc-step.is-now { font-weight: 700; }
/**
 * ★ 2026-09-20（用户口径）：「跟单员还未确定交期——销售员工作台内的事项卡的跟单状态，
 *   标记成**橙黄色**，代表等待跟单员确定」—— 进度条上「跟单」那一格 + 它下面那段一起变橙黄；
 *   跟单员点过「确认交期」就不带这个类，自动回到蓝（见 pages.js 的 orderProgressHtml）。
 */
.tc-prog-bar i.is-wait { background: var(--yellow); }
.tc-step.is-wait { color: var(--yellow); }
#app.page-fill .quad-board { flex: 1; min-height: 0; }
/**
 * ★ 2026-09-19（用户口径）：「箭头处太浪费空间，要给事项卡留下更多空间」——
 *   加「按属性筛选」那排时，四象限被包进了 `[data-quad-board]`，**高度链断了**：
 *   `.quad-grid` 的 `flex: 1` 落在一个普通 div 上不生效，网格就只按内容高度（2 × min-height）
 *   显示，下面空出一大片。这里把中间那层也接上（筛选条固定、网格吃满剩下的高度）。
 */
#app.page-fill .quad-board > [data-quad-filter-bar] { flex: none; }
#app.page-fill .quad-board > [data-quad-board] { flex: 1; min-height: 0; display: flex; flex-direction: column; }

.quad-grid {
  display: grid;
  /* 四象限：2×2，窗口窄了变一列 */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
/**
 * ★ 2026-09-20（用户口径）：「将跟单的界面分为 待跟单 / 采购中 / 配货中 / 生产中 / 待发货」。
 *
 * 跟单员的看板是**一条流水线**：一段一列、并排铺开。
 * 用 `grid-auto-flow: column` 而不是写死 `repeat(5, …)` —— 以后流程加/减一段，这里一个字都不用改。
 * 段内部的样式**全部复用 `.quad`**（不加同义类，见 workbench-ui 的组件前缀规矩）。
 */
.quad-grid.track-grid {
  grid-template-columns: none;
  grid-template-rows: none;
  grid-auto-flow: column;
  /**
   * ★ 2026-09-20（用户口径，红框定稿）：一条流水线是**六段** ——
   * 待跟单 / 采购 / 配货 / 罐装 / 组装 / 发货。
   * 一列最窄 190px（再窄卡片就没法看），窗口不够宽时由下面那条 `overflow-x: auto`
   * 让整块板横向滚，而不是把六列硬挤成六条。
   */
  grid-auto-columns: minmax(190px, 1fr);
}
/* 段数多了，窄一点的窗口让它横向滚 —— 别把每列压成一条（`#app.page-fill .quad-grid` 是 overflow:hidden，这里要盖过它） */
#app.page-fill .quad-grid.track-grid { overflow-x: auto; }
/**
 * ★ 2026-09-21（用户口径）：「中间两列需要有一个头部：**采购中**，下面分成**待补价格**和
 *   **价格齐全**两列」—— 一块**跨两列**的组：上面一条组头（序号 / 组名 / 总张数 / 最紧），
 *   下面两列并排。列宽还是四列，只是中间那两列有共同的爹（见 pages.js 的 groupBlockHtml）。
 */
.track-grid > .quad-group { grid-column: span 2; }
.quad-group { display: flex; flex-direction: column; min-width: 0; border-right: 1px solid var(--line-2); }
.quad-group-head { display: flex; align-items: center; gap: 8px; flex: none; padding: 10px 12px 0; }
.quad-group-head .quad-title { font-size: 13px; font-weight: 650; letter-spacing: -0.01em; }
.quad-group-head .track-col-note { margin-top: 0; }
.quad-group-body { display: flex; flex: 1; min-height: 0; }
.quad-group-body > .quad { flex: 1 1 0; min-width: 0; border-right: 0; padding-top: 8px; }
/* 两列之间一条虚线：是"同一段的两个子列"，不是两段 */
.quad-group-body > .quad + .quad { border-left: 1px dashed var(--line-2); }
/* 子列的标题比组头小一号（组头是「采购中」，它是「待补价格 / 价格齐全」） */
.quad.is-sub .quad-title { font-size: 12px; font-weight: 600; color: var(--ink-2); }
/**
 * ★ 2026-09-21（用户口径）：**采购员工作台的卡面重排**（三段式版式，见 pages.js 的 buyCardHtml）——
 *   ① 项目卡号 ② 来源（销售单 / 订单商品 / 下单 · 交期）③ 自己那张单（单号 / 采购明细 / 时间）。
 *   卡面信息比通用小卡多，所以：一列一张（不挤小方块网格）、用小一号的字、区域之间一条细虚线。
 */
/* ================================================================ 采购员工作台的卡面（三段式）
   用户口径 2026-09-21：「整体的卡片 ui 很难看，重新设计一下」（内容照定稿那几条）。
   版式：① 头（加急 / 普通 + 项目卡号）② 来源（销售单 / 订单商品 / 下单·交期）
        ③ 采购（单号 + 状态章 / 明细 / 时间胶囊 / 采购数·已入数）④ 页脚「距需求时间」。
   全用 token 颜色，换主题不破相。 */
/* ⚠️ 必须 minmax(0, 1fr)：明细名是 nowrap + 省略号，min-content 就是整串名字，
   用 1fr 会把这条轨道撑得比列还宽（用户截图里卡片右边被切掉就是这个）。 */
.buy-grid .quad-cards { grid-template-columns: minmax(0, 1fr); gap: 8px; }
.task-card.sm.buy-card {
  min-height: 0;
  overflow: hidden;
  justify-content: flex-start;
  padding: 0;
  gap: 0;
  border-radius: var(--radius);
}
/**
 * ★ 2026-09-25（用户口径）：卡片上的产品图 —— 试过两版都被否：
 *   底纹版「产品图都看不到了」、顶部图带版「卡片太高了」。
 * 定稿：**图 = 抬头左边一枚 48px 的方图**（卡片的头像）——
 *   · 原图（不透明、不模糊），一眼认得出是哪个品；
 *   · 只占抬头那一行的高度（那行本来只有 20px 高、全是小字），整张卡**只为它长 ~26px**；
 *   · **不占正文宽度** —— 单号 / 买什么 / 时间照旧占满整张卡（放过左边单独一列：
 *     卡片窄的时候商品名被挤到只剩 6 个字，不行）；
 *   · 没图的行不摆它，卡面完全回到原样。
 */
.task-card.buy-card .bc-thumb {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-2);
  background-color: var(--surface-2);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.bc-head {
  display: flex;
  /* ★ 2026-09-25：窄卡（五列并排、窗口 ~1068px 时每列才 ~165px）里「项目卡 KAI-002」
     原来被挤成竖着排的「项/目/卡」—— 让它**整块换行**，别在词里断。 */
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  flex: none;
  min-width: 0;
  padding: 6px 10px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
}
.bc-proj { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }
.bc-proj svg { flex: none; opacity: 0.7; }
.bc-proj b { color: var(--ink-2); font-weight: 650; }
/* 一块（来源 / 采购）：上下留白一致，块之间一条实线分开 */
.bc-sect { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 7px 10px; border-top: 1px solid var(--line-2); }
.bc-sect:first-of-type { border-top: 0; }
/**
 * ★ 2026-09-25（用户口径）：「**申请单号 采购单号要显示全**」——
 * 单号那行加了「采购单号 / 申请单号」四个字之后，窄一点的窗口（五列并排时每列才 ~190px）
 * 会把号挤成 `CGDH-260925…`。现在三件事一起做：
 *   · 标签 11 → **10px**、号 12 → **11px**（省出十几像素，「申请单号 CGSQD-260924002」
 *     在 ~190px 的卡里正好排得下一行）；
 *   · 这一行**可以换行**（真放不下时让「剩 N 天」那枚胶囊掉到第二行，**号一个字符都不许省**）；
 *   · 号本身 `flex: 0 0 auto` —— 不再被压着走（不然浏览器宁可把号缩成省略号也不换行）；
 *     胶囊 `margin-left: auto`：独行时靠右，和并排时看着一致。
 * 号真比整张卡还长（理论上不会）才退回省略号。
 */
.bc-doc { display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px; min-width: 0; font-size: 10px; color: var(--ink-3); }
.bc-doc-k { flex: none; }
.bc-doc b { flex: 0 0 auto; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--ink); font-weight: 650; }
.bc-doc .bc-chip { margin-left: auto; }
.bc-doc .bc-state { flex: none; font-size: 10.5px; padding: 1px 6px; }
/**
 * 订单商品那行**撤了**（用户口径 2026-09-24：「这里应该突出的是采购申请单内容，
 * 销售单的展示减少些曝光度」）—— 来源压成下面那行小灰字 `.bc-src`，
 * 订单商品名收进它的 `title`。这条规则留着是给老卡/别处兜底用的。
 */
.bc-product {
  font-size: 12px;
  font-weight: 500;
  line-height: 1.35;
  color: var(--ink-3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/**
 * ★ 2026-09-24（用户口径）：「**突出采购申请单内容，销售单减少曝光度**」——
 * 来源（销售单 / 下单 / 交期）压成**一行小灰字**，摆在卡片最下面；单号用 ink-2、
 * 日期用 ink-3，整行 10.5px，别抢上面"要买什么"的戏。
 * ★ 同一天又一条：「这里**展示全下单时间和交期**」—— 拆成一块一块、放不下整块换行，
 * 绝不截字（原来 `nowrap` + 省略号会把「下单 09-…」吃掉）。
 */
.bc-src {
  display: flex;
  flex-wrap: wrap;
  gap: 1px 7px;
  margin-top: 1px;
  padding: 5px 10px 6px;
  border-top: 1px solid var(--line-2);
  font-size: 10.5px;
  color: var(--ink-3);
}
.bc-src-i { white-space: nowrap; }
.bc-src b { color: var(--ink-2); font-weight: 600; }
/* 自己那张单的明细 = 卡面的正题（用户口径：要突出采购申请单内容） */
.bc-items { display: flex; flex-direction: column; gap: 4px; }
.bc-item { display: flex; align-items: baseline; gap: 6px; min-width: 0; font-size: 12.5px; }
.bc-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font-weight: 650; }
.bc-item b { flex: none; font-weight: 700; color: var(--ink); }
.bc-more { font-size: 11px; color: var(--ink-3); }
/* 时间 / 剩余：小胶囊，一排排开 */
.bc-chips { display: flex; flex-wrap: wrap; gap: 3px; min-width: 0; }
.bc-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-3);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  /* ★ 2026-09-25：窄卡里胶囊**整体换行**，别在词里断开（截图上出现过「下单时 / 间 09-25」） */
  white-space: nowrap;
}
.bc-chip b { color: var(--ink-2); font-weight: 650; }
.bc-chip.is-red, .bc-chip.is-today { background: var(--red-soft); color: var(--red); }
.bc-chip.is-red b, .bc-chip.is-today b { color: var(--red); }
.bc-chip.is-yellow { background: var(--yellow-soft); color: #8a6100; }
.bc-chip.is-yellow b { color: #8a6100; }
/* 采购数 / 已入数：一条细进度 */
.bc-qty { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 11px; color: var(--ink-3); }
.bc-qty-k, .bc-qty-v { flex: none; white-space: nowrap; }
.bc-qty b { color: var(--ink-2); font-weight: 650; }
.bc-qty-bar { flex: 1; min-width: 24px; height: 4px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.bc-qty-bar i { display: block; height: 100%; background: var(--blue); border-radius: 999px; }
.bc-qty.is-part .bc-qty-bar i { background: #f0a020; }
.bc-qty.is-ok .bc-qty-bar i { background: var(--green); }
/* 页脚：距需求时间剩 X 天（按紧急上底色） */
.bc-need {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 5px;
  padding: 5px 10px;
  font-size: 11.5px;
  color: var(--ink-2);
  background: var(--surface-2);
  border-top: 1px solid var(--line-2);
}
/* 窄列（5 列之后一列只有 ~220px）里「距需求时间」会被从中间掰开成「距需求时 / 间」——
   整段不许折，要换行就整段换。 */
.bc-need > span, .bc-need > b { white-space: nowrap; }
.bc-need b { font-weight: 700; }
.bc-need.is-tight { background: var(--yellow-soft); color: #8a6100; }
.bc-need.is-late { background: var(--red-soft); color: var(--red); }

/* 跟单员那排工具条左边的"账"：共几单在跟 · 交期逾期几单 */
.track-sum { display: flex; align-items: center; gap: 8px; flex: none; min-width: 0; }
.track-sum b { font-weight: 700; white-space: nowrap; }
.track-late {
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
}
#app.page-fill .quad-grid {
  flex: 1;
  min-height: 0;
  /* 整屏时贴边：只留一条上边线 + 四块之间的细线，不再套一圈圆角边框 */
  border: 0;
  border-top: 1px solid var(--line-2);
  border-radius: 0;
}
@media (max-width: 720px) {
  /* 窄窗口：四块竖排，整块板自己滚（不然会被 overflow:hidden 裁掉） */
  .quad-grid { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; overflow: auto; }
  .quad { border-right: 0; border-bottom: 1px solid var(--line-2); min-height: 180px; }
  .quad:last-child { border-bottom: 0; }
  /**
   * 跟单员那条五列流水线在窄屏上也**竖排成一列**（一段一行）——
   * 五列并排塞进 430px 一列只有 86px，卡片根本没法看。
   * `grid-auto-flow: column` 是宽屏那条规则加的，这里必须显式改回 row。
   */
  /**
   * 跟单员那条六段流水线在窄屏上**横着滑、一次一列（带吸附）** —— 不是六块竖着堆。
   * 理由：流水线横着看才有意义（"卡在哪一段"），竖着堆就得滚过五个空格才看得到"发货"。
   */
  .quad-grid.track-grid {
    grid-auto-flow: column;
    grid-auto-columns: 78vw;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
  }
  .quad-grid.track-grid .quad { scroll-snap-align: start; }
}

.quad {
  display: flex;
  flex-direction: column;
  min-height: 160px;
  min-width: 0;
  padding: 10px 12px 8px;
  background: var(--surface);
  border-right: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
}
.quad:nth-child(2n) { border-right: 0; }
.quad:nth-child(n + 3) { border-bottom: 0; }
/**
 * ★ 2026-09-19（用户口径）：「工作台内四个分区的卡片，可以自行拖拽至不同分区」——
 * `.quad-card` 是拖拽的抓手（原生 HTML5 拖拽，见 pages.js 的 wireTodoRoot）：
 * 按住能拖、拖起来半透明；落点那整块（`.quad-body` 所在的 .quad）高亮一下，知道会落到哪。
 */
.quad.is-drop { background: var(--blue-soft); box-shadow: inset 0 0 0 2px var(--blue); }
.quad-card { display: flex; cursor: grab; }
.quad-card:active { cursor: grabbing; }
.quad-card > .task-card { flex: 1; min-width: 0; }
.quad-card.is-dragging { opacity: 0.4; }

/* 标题左边那道彩色短杠 —— 四块的颜色就靠它区分 */
.quad-accent { flex: none; width: 3px; height: 15px; border-radius: 2px; background: var(--ink-3); }
.quad.red .quad-accent { background: var(--red); }
.quad.blue .quad-accent { background: var(--blue); }
.quad.yellow .quad-accent { background: #f0a020; }
.quad.green .quad-accent { background: var(--green); }
/* 流水线用得到的第五个色：车间（罐装 / 组装同属车间，共用一个色，靠序号区分） */
.quad.violet .quad-accent { background: var(--violet); }

/**
 * ★ 2026-09-20（用户口径：「只看排版结构……你再根据功能自主设计 UI」）——
 *
 * 六段是一条**流水线**，横着看才有意义。所以每段头上有**序号**（1~6），
 * 让六个盒子读成"一条线"上的六个点，而不是六个不相干的区块；
 * 序号底色跟这一段的强调色走，左→右一眼扫得出流程。
 */
.quad-step {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  background: var(--surface-3);
  color: var(--ink-3);
}
.quad.red .quad-step { background: var(--red-soft); color: var(--red); }
.quad.blue .quad-step { background: var(--blue-soft); color: var(--blue); }
.quad.yellow .quad-step { background: var(--yellow-soft); color: var(--yellow); }
.quad.green .quad-step { background: var(--green-soft); color: var(--green); }
.quad.violet .quad-step { background: var(--violet-soft); color: var(--violet); }

/**
 * 每段的"账"：**这一段有没有要出事的**。
 * 光看一个张数看不出来（3 张也可能全是明天到期），所以补一句"逾期几 / 最紧还剩几天"。
 */
.track-col-note { display: flex; align-items: center; gap: 6px; flex: none; padding: 3px 0 1px 11px; }
.track-col-late {
  padding: 0 6px;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
.track-col-tight { color: var(--ink-3); font-size: 11px; white-space: nowrap; }

/**
 * ★ 2026-09-22（用户口径：「优化一下工作台的 UI」）：
 *   原来四象限就是**四块白底 + 1px 细线**，扫一眼分不出哪块是哪块；数据一空更是四张白板。
 *   这里给每块的头部一条**浅色头带**（和跟单页"每个车间一张独立卡片、自带浅色头带"同一个做法）：
 *   头带是中性浅底，颜色交给左边那条 3px 竖条 —— 四块同时摆在屏幕上也不会花。
 *   用负外边距让头带**贴到象限的上/左右边缘**（`.quad` 的内边距是 10px 12px 8px；
 *   分组里那几块 padding-top 是 8px，所以这里取 -8px 取整，误差 2px 看不出来）。
 */
.quad-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
  margin: -8px -12px 8px;
  padding: 7px 12px 7px 9px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line-2);
}
.quad-title { font-size: 13px; font-weight: 650; letter-spacing: -0.01em; }
.quad-count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-3);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 0 8px;
}
.quad-add {
  margin-left: auto;
  width: 22px;
  height: 22px;
  padding: 0;
  line-height: 1;
  font-size: 14px;
  color: var(--ink-3);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
}
.quad-add:hover { color: var(--ink); border-color: var(--ink-3); background: var(--surface-2); }

.quad-body { flex: 1; min-height: 0; overflow: auto; margin-top: 4px; }
.quad .empty { background: transparent; padding: 14px 2px; text-align: left; }
/**
 * ★ 2026-09-22（用户口径：「优化一下工作台的 UI」）：
 *   空的象限原来是"左上角孤零零一行灰字"，一整块板子看着像**没加载出来**。
 *   现在把这份"空"设计出来：内容在这块板里**垂直居中**，主句下面补一句"这一格是干什么用的"
 *   （原来那句 hint 只藏在 title 里，得悬停才看得见），外面套一圈虚线，
 *   告诉人"这是留给内容的位子，不是坏了"。
 */
.quad .quad-empty {
  /* 不撑满：靠 .quad-body 的居中把它放在这块板的正中间，虚线只圈住文字本身 ——
     撑满整格会变成四个巨大的虚线框，比原来还吵。 */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  width: 290px;      /* 定宽：四块空态必须是"同一个组件的四个实例"，跟着文字变宽就毛了 */
  max-width: 100%;
  margin: 0;
  padding: 14px 18px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: transparent;
  text-align: center;
}
/* 空态在格子里**垂直居中**（`:has()` 在本机 WKWebView 上有，仓库里弹窗那段已经在用） */
.quad-body:has(> .quad-empty) {
  display: flex;
  align-items: center;
  justify-content: center;
}
.quad-empty-note { font-size: 12.5px; color: var(--ink-2); }
.quad-empty-why { font-size: 11.5px; color: var(--ink-3); line-height: 1.55; }

/* 四象限里的事项：**小方块网格** —— 用户口径「一排 3 个小方块」
   （1440 宽的窗口下四象限栏宽 591px，150px 下限正好落成 3 列；再宽会自动多一列） */
.quad-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }

/* 小方块（工作台四象限）：1440 宽下 3 列，每块 ~192×152（接近正方形），
   337px 高的象限一眼两排 6 条。用户口径 2026-09-17：「调小点，最好是小方形」+「一排 3 个小方块」。
   版式**固定**：标题永远三行、元信息一行、时间一行、动作一行 —— 一块块对得齐；
   min-height 给足再把行距匀开，方块才是方的（内容本来就是固定行高）。
   信息一个没删：状态 + 剩余并进元信息行（环里是同一个数字），
   「详情」+ 未读角标留在动作行最右，环节按钮（登记入库 / 完成入库）一行站得下。 */
/**
 * ★ 2026-09-21（用户口径）：「进度条的样式改变，**将整个卡片作为底色，根据进度填充色块**」——
 * 整张卡铺一层"时间进度"底：从左往右按比例填充（宽 = 剩余时间比例 `remainPct`），
 * 颜色跟档位走（红=逾期/紧急、黄=接近、其余=蓝）。内容都在它上面（`.task-card > *` 已设 z-index:1）。
 */
/**
 * ★ 2026-09-21（用户口径）：「**没感觉到颜色的变化**」
 * 上一版为了"不抢文字"把透明度压到 0.09~0.16，结果基本看不出来。
 * 现在直接用**柔和底色令牌**实色填充（`--*-soft` 本来就是给"浅底"用的，够显眼又不刺眼），
 * 并在填充分界线那儿加一道**竖线** —— 这样"填了几成"一眼可量。
 */
/**
 * ★ 2026-09-21（用户口径）：「**不要那条杠，把填充颜色做成渐变色**」，
 * 随后明确方向：「进度填充色块，**从左向右，颜色逐步变重**」——
 * 所以去掉分界竖线，填充本身从**左浅 → 右深**：左边几乎透明、越往右越实
 * （右端就是"已经用到这儿了"的位置，颜色最重）。
 */
/**
 * 三段渐变：左端透明 → 中段柔和底色 → **右端主色**（越靠右越重），
 * 这样"进度填到哪儿"既没有硬边，右边又足够显眼。
 */
.task-card > .tc-fill { position: absolute; left: 0; top: 0; bottom: 0; z-index: 0; pointer-events: none; transition: width 0.2s;
  background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--blue-soft) 55%, var(--blue) 100%); opacity: 0.55; }
.task-card > .tc-fill.red { background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--red-soft) 55%, var(--red) 100%); opacity: 0.6; }
.task-card > .tc-fill.yellow { background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--yellow-soft) 55%, var(--yellow) 100%); opacity: 0.6; }
/* 有了整卡底色，小卡上那条贴底细进度条就不用了（「剩 N 天」那行字还在） */
.task-card.sm .tc-progress .tc-bar { display: none; }
.task-card.sm {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between; /* 行距匀开，别都挤在上半截 */
  min-height: 152px;
  padding: 8px 9px 2px; /* 底下留 2px：动作那一行别贴着进度条 */
  gap: 4px;
  overflow: hidden; /* 贴底那条进度要被方块的圆角裁掉 */
}
.task-card.sm .tc-top { align-items: center; gap: 5px; line-height: 18px; }
/**
 * 第二行（用户口径 2026-09-18：「详情换成项目卡号，销售单还要保持不变」）：
 * 左边**来源销售单号**、右边**详情**；编号（项目卡号 / 发货单号 / 待办号）挪到右上角去了。
 * 定高 14px：位置固定，卡片高度不受内容影响。
 */
.task-card.sm .tc-no-line {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 6px;
  height: 14px;
  min-width: 0;
  overflow: hidden;
}
.task-card.sm .tc-no-line .tc-no { font-size: 10px; }
/* 发货卡的「应发 x · 实发 y · 还差 z」**单独一行**（挤在销售单号那行会把单号压成「销…」） */
.task-card.sm .tc-ship-line { display: flex; align-items: center; height: 14px; min-width: 0; overflow: hidden; }
/**
 * ★ 2026-09-21（用户口径）：「采购审批卡片上还需要显示采购申请单号」——
 * 也**单独占一行**（跟销售单挤一行会被卡宽截掉尾号，见 pages.js 的 `reqNoTag`）。
 * 只有采购审批卡会渲染这一行，别的卡行高不变。
 */
.tc-req-line { display: flex; align-items: center; gap: 6px; min-width: 0; margin-top: 3px; }
.task-card.sm .tc-req-line { height: 14px; margin-top: 0; overflow: hidden; }
.tc-req-no { font-size: 10px; white-space: nowrap; color: var(--ink-2); }
/**
 * ★ 2026-09-27：「被催 N 次 · 谁 什么时候」**单独一行**（用户口径：催办要在待办上标出来、
 * 还要记次数）。只有被催过的卡渲染这一行 —— 别的卡行高一个像素都不变。
 * 颜色走 `--red`：催办是"提醒你这件事被盯上了"，和逾期同色系。
 */
.tc-nudge-line { display: flex; align-items: center; gap: 6px; min-width: 0; margin-top: 3px; }
.task-card.sm .tc-nudge-line { height: 14px; margin-top: 0; overflow: hidden; }
.tc-nudge-tag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--red);
}
.tc-nudge-tag svg { flex: none; }
/* 发货卡的「应发 x / 实发 y」（分次发货时卡不消失，靠它看进度）；发完了显示绿的 */
.tc-ship-qty { font-size: 10px; white-space: nowrap; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.tc-ship-qty.is-part { color: var(--due-warn); font-weight: 600; }
.tc-ship-qty.is-done { color: var(--green); }
/* 跟单卡的**左右布局**（用户口径 2026-09-18：「跟单流程的具体信息和下面的发货地址和一些
   操作细节，做成左右布局，跟单流程的宽窄可以拖拽缩放」）：
   左 = 跟单流程（自己滚、宽度可拖）＋ 分界线 ＋ 右 = 发货安排（计划 + 交期 + 操作条）。 */
.dp-track { flex: 1; min-height: 0; display: flex; align-items: stretch; }
/* 左右两区**按比例固定**（左 64% / 右 36%）—— 用户口径 2026-09-18：「这一块的左右两区固定下来比例」；
   拖分界线仍可临时改（行内 flex 会盖过这里），窄到 760px 以下自动上下堆叠。
   ★ 2026-09-19（用户口径）：「**跟单流程的板块默认占比最大**」—— 原来 58/42，实测 1240 的面板下
   跟单流程 392px、只比固定 336px 的讨论栏宽一点；现在 64/36，让它明确是这块卡的主角。 */
.dp-track-main { flex: 0 0 64%; min-width: 0; overflow: auto; padding-right: 10px; }
/* 分界线：7px 宽的一条，鼠标移上去变色、按住拖能改宽窄。
   两条长得一样：中间那条（跟单流程 ↔ 发货安排）、右边那条（中栏 ↔ 讨论，2026-09-19 新增）。 */
.dp-track-grip,
.dp-talk-grip { flex: none; width: 7px; cursor: col-resize; border-radius: 3px; background: linear-gradient(var(--line-2), var(--line-2)); background-size: 1px 100%; background-repeat: no-repeat; background-position: center; }
.dp-track-grip:hover, .dp-track-grip:active,
.dp-talk-grip:hover, .dp-talk-grip:active { background: var(--line-strong, var(--line)); background-size: 2px 100%; }
body.is-col-resizing { cursor: col-resize; user-select: none; }
/* 右边那块（发货安排）：自己不整块滚，里面的计划表自己滚 */
.dp-track-side { flex: 0 0 36%; min-width: 0; display: flex; flex-direction: column; gap: 6px; padding-left: 4px; border-left: 1px solid var(--line-2); }
.dp-track-side-body { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }
/**
 * 发货计划：**一个地址一块**（用户口径 2026-09-18：「收货地址这一栏的 ui 需要重新优化，
 * 最好直接显示齐全」）—— 地址整行铺开（想换几行换几行、不截断），
 * 联系人/电话/数量/已发 一行小字，发货单和它的按钮一行。
 */
/* 表头（标题 + 改计划 / 发货员 + 汇总）在窄栏里要能换行 —— 不然标题会被挤成一个字一行 */
.dp-ship-block { gap: 6px; }
.dp-ship-block > .dp-items-head { flex-wrap: nowrap; align-items: center; row-gap: 6px; }
.dp-ship-block > .dp-items-head .eyebrow { white-space: nowrap; }
/* 汇总那一行（发货员 · N 个地址 · 应发 · 已发）：一行小字，原来这几项占了整整三行 */
.dp-ship-sum { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; font-size: 11.5px; color: var(--ink-3); }
.dp-ship-sum b { color: var(--ink); }
.dp-ship-sum-sep { color: var(--line); }
.dp-ship-sum .link-btn { font-size: 11.5px; padding: 0; }
.dp-ship-list { display: flex; flex-direction: column; gap: 6px; }
.dp-ship-row { display: flex; flex-direction: column; gap: 3px; padding: 7px 9px; border: 1px solid var(--line-2); border-radius: var(--radius-sm); background: var(--surface-2); }
.dp-ship-row.has-out { border-color: var(--line); }
.dp-ship-row-head { display: flex; align-items: flex-start; gap: 7px; }
.dp-ship-seq { flex: none; width: 16px; height: 16px; margin-top: 1px; border-radius: 50%; background: var(--accent-weak, var(--surface-3)); color: var(--ink-2); font-size: 10.5px; line-height: 16px; text-align: center; }
.dp-ship-addr { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 500; color: var(--ink); line-height: 1.4; word-break: break-all; }
/* 联系人 / 应发 / 已发：名字占左边、两个数靠右**永远同一行**（用户口径 2026-09-19） */
.dp-ship-row-meta { display: flex; flex-wrap: nowrap; align-items: baseline; gap: 2px 8px; font-size: 11.5px; color: var(--ink-3); }
.dp-ship-who { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); }
.dp-ship-nums { flex: 0 0 auto; margin-left: auto; display: inline-flex; gap: 10px; white-space: nowrap; }
.dp-ship-qty { white-space: nowrap; }
.dp-ship-qty b { color: var(--ink); }
.dp-ship-row-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.dp-ship-row-acts .link-btn { display: inline-flex; align-items: center; gap: 3px; font-size: 11.5px; }
/* 表头那排按钮（改计划 / 发货员）显眼一点（用户口径：太不明显） */
.dp-ship-plan-btn, .dp-ship-shipper-btn { display: inline-flex; align-items: center; gap: 4px; }
@media (max-width: 760px) {
  /* 窄屏（手机 / 小窗）：落回上下堆叠，拖拽关掉 */
  .dp-track { flex-direction: column; }
  .dp-track-grip { display: none; }
  .dp-track-side { flex: 1 1 auto; min-width: 0; border-left: 0; padding-left: 0; }
  .dp-track-main { padding-right: 0; }
  /* ★ 2026-09-21：库存状态那行的缩略图 / 勾选格再小一号 —— 窄屏把宽度让给「料名」 */
  .dp-stock-main { gap: 6px; }
  .dp-stock-pic { width: 24px; height: 24px; }
}
/* 「我的工作台 → 历史」那一屏（#/home?tab=history）：抬头 + 卡片流 + 加载更早 */
.home-history { padding: 14px 18px 24px; overflow: auto; }
.home-history-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.home-history-head b { font-size: 15px; }
.home-history .task-grid { margin-top: 4px; }
.home-history-more { display: flex; justify-content: center; padding: 14px 0 4px; }
.home-history-loading { padding: 24px 18px; }
/* 库存状态：**按车间**一张小表（拌料 / 罐装 / 组装），要备的料全列出来 —— 缺的黄、够的写「够」 */
.dp-stock-stages { display: flex; flex-direction: column; gap: 12px; }
/**
 * ★ 2026-09-19（用户口径）：「跟单界面，车间与车间之间区分一下 UI 设计，现在看着很累」——
 *   原来三个车间就是三张并排的表、表头长得一样，读起来"糊成一片"。现在每个车间是一张**独立卡片**：
 *   描边 + 圆角 + **自己的浅色头带**，头带左边一条 3px 车间色（拌料=紫 / 罐装=蓝 / 组装=绿），
 *   外加序号（生产先后）和右侧状态徽标。色值全部走令牌，换主题不会破相。
 */
.dp-stock-stage {
  --stage-accent: var(--ink-3);
  --stage-soft: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.dp-stock-stage.is-mix { --stage-accent: var(--violet); --stage-soft: var(--violet-soft); }
.dp-stock-stage.is-fill { --stage-accent: var(--blue); --stage-soft: var(--blue-soft); }
.dp-stock-stage.is-assemble { --stage-accent: var(--green); --stage-soft: var(--green-soft); }
.dp-stock-stage-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 0;
  padding: 8px 12px 8px 9px;
  background: var(--stage-soft);
  border-bottom: 1px solid var(--line-2);
  border-left: 3px solid var(--stage-accent);
  /**
   * ★ 2026-09-24（用户口径）：「要买 N 种不够 + 已采购 M 种放一行；自己做 N 种不够 + 已做 xxx
   *   放一行」—— 账按摊分两行，整盒占头带的一整行（`.dp-stock-stage-chips { flex: 1 0 100% }`），
   *   所以这里得允许折行，且不会把车间名压成两行、也不会把最后一枚章裁掉半个字。
   */
  flex-wrap: wrap;
}
/**
 * 一段的账（车间头带里）：**整盒占一整行**（`flex: 1 0 100%` + 父级 wrap），靠右对齐 ——
 * 里面按摊再分两行（`.dp-chip-rows` 那套）。这样不管产出名多长都不会挤掉半枚章，
 * 各块之间也长得一样（不会一个有账一个没账）。
 */
.dp-stock-stage-chips { flex: 1 1 auto; }
/**
 * ★ 2026-09-24（用户口径）：「跟单的这里，**把配货 派单 催单放这里，按钮明显些**」——
 * 头带的**第二行**：左边是这一段的动作（配货 / 派单 / 催单，正经小按钮 `.sm`，
 * 不再是灰字 link-btn），右边还是这一段的两行账（`.dp-stock-stage-chips`）。
 * 一整行、和头带同一块底色 —— 进这块就看得到，不用去表下面找。
 */
.dp-stock-stage-head2 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 6px 12px 7px 9px;
  background: var(--stage-soft);
  border-bottom: 1px solid var(--line-2);
  border-left: 3px solid var(--stage-accent);
}
.dp-stage-acts-head { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.dp-stage-acts-head .tag { align-self: center; }
/**
 * ★ 2026-09-24（用户口径）：「**配货和派单，如果采购的东西，还没入库够订单所需数量，
 * 是没办法操作的**」—— 这两颗按住之后，光有个灰按钮说不清"为什么、等到什么时候"，
 * 所以在它们旁边缀一枚「等料入库」（悬停给差哪几种、差多少）。黄 = 缺（和账上那枚
 * 「要买 N 种不够」同一个色系），不折行。
 */
.dp-stage-wait {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--yellow-soft);
  color: var(--yellow);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: help;
}
.dp-stock-stage-head2 .dp-stock-stage-chips { margin-left: auto; }
/**
 * ★ 2026-09-24：「车间名」**不许折行**（要挤就先挤右边那串商品名 —— 它本来就带省略号）。
 *   右边多了「已采购 x 种 / 原料 · 不看库存」两枚章之后，「拌料车间」被压成了「拌料 / 车间」，
 *   两行一撑，头带就高一截。
 */
.dp-stock-stage-head b { flex: none; white-space: nowrap; font-size: 13.5px; color: var(--ink); }
/* 序号：车间是按生产先后排的（①拌料 → ②罐装 → ③组装），给个看得见的次序 */
/** ★ 2026-09-21：车间抬头后面那串「这一车间做的是什么」（料体 / 已灌装 / 成品名） */
.dp-stock-stage-goods { flex: 1 1 80px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); font-size: 12px; font-weight: 500; }
.dp-stock-stage-no {
  width: 18px;
  height: 18px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--stage-accent);
  /* 压在饱和色块上的字：亮色主题里是白的、深空主题里是近黑的（`--brand-ink` 就是干这个的） */
  color: var(--brand-ink);
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* 右侧状态徽标：缺=黄、够=绿 */
.dp-stock-stage-count {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
}
.dp-stock-stage-count.is-short { background: var(--yellow-soft); color: var(--yellow); }
.dp-stock-stage-count.is-ok { background: var(--green-soft); color: var(--green); font-weight: 500; }
/**
 * ★ 2026-09-24（用户口径）：「**x 种不够，后面再写上已采购 x 种**」——
 *   右边第二枚章：这一块已经有几支下了申请单 / 采购单（在跑的就用蓝色，和"缺=黄 / 够=绿"分开）。
 * ★ 同一轮：「**拌料车间的原料先不考虑库存，不做判断**」——
 *   拌料那块头带上写「原料 · 不看库存」（中性灰，表示"这一项不判"）。
 */
.dp-stock-stage-count.is-bought { background: var(--blue-soft); color: var(--blue); }
.dp-stock-stage-count.is-nostock { background: var(--surface-3); color: var(--ink-2); font-weight: 500; }
/**
 * ★ 2026-09-24：自己做那一摊的第二个数 = **这一道的报工进度**（`已做 已报 / 计划`）——
 *   还没开工给中性灰、已经做了给绿色（一眼看出上道动没动手）。
 */
.dp-stock-stage-count.is-todo { background: var(--surface-3); color: var(--ink-2); font-weight: 500; }
.dp-stock-stage-count.is-done { background: var(--green-soft); color: var(--green); }
/**
 * ★ 2026-09-24：`X 种不够` 按**摊**分开报之后，「自己做 N 种不够」是**车间那一摊**的账
 *   （这些不采购，缺 = 上游工序还没交）—— 给个和「要买（黄）/ 已采购（蓝）」分得开的颜色。
 */
.dp-stock-stage-count.is-make { background: var(--violet-soft); color: var(--violet); }
/* 卡片自己已经有描边了，里面那张表就别再套一层框（双框看着更乱） */
.dp-stock-stage .dp-items-table { border: 0; border-radius: 0; }
.dp-stock-stage .dp-stock-secret { padding: 7px 12px 0; }
.dp-stock-stage table td { font-size: 12.5px; }

/**
 * ★ 2026-09-21（用户口径）：「把需要采购的包材放在一起，一目了然」＋
 *   「这样布局感觉**不是一个板块**啊，分堆没错了，但需要再优化」。
 *
 * 第一版把小组头做成了**一条横贯的色带**（还带左色条）—— 分堆是分开了，可两条色带
 * 把整块**切成了三截**，看着就不像一个车间了。
 * 现在小组头退成**表内的一句小注**：透明底、只在上面留一条发丝线，右边靠那枚
 * 「要买 / 自己做」小标签本身带色 —— 分得清两堆，又还是**一整块**。
 */
.dp-stock-group td {
  padding: 9px 12px 3px 12px;
  background: transparent;
  border-top: 1px solid var(--line-2);
  font-size: 11px;
  color: var(--ink-3);
}
.dp-stock-group:first-child td { border-top: 0; padding-top: 5px; }
.dp-stock-group .dp-stock-kind { margin-right: 6px; }
.dp-stock-stage tr.is-short td { background: rgba(255, 214, 102, 0.07); }
/**
 * 库存状态那张表（用户口径 2026-09-18：「缺货需要自己做的卡片不明显」「整体文字排版都很小气」
 * 「半成品·自己做 和 包材·要买 排版位置统一」）：
 *   · 字号整体调大一档、行距松开；
 *   · 标签**固定另起一行**（名字长短都一样的位置）；
 *   · 「自己做 / 要买」× 「缺 / 不缺」四种组合各有底色 + **左边一条 3px 色带**，
 *     缺料（尤其自己做却缺料）一眼看得见。
 */
.dp-stock-stage table th { font-size: 12.5px; letter-spacing: 0.2px; }
.dp-stock-stage table td { font-size: 13.5px; padding-top: 8px; padding-bottom: 8px; }
.dp-stock-cell { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
.dp-stock-name { font-size: 13.5px; font-weight: 500; color: var(--ink); line-height: 1.45; }
.dp-stock-spec { margin-left: 5px; color: var(--ink-3); font-size: 12px; font-weight: 400; }
.dp-stock-buy { text-align: right; white-space: nowrap; }
/**
 * ★ 2026-09-21（用户口径）：「**排版太丑了**」——
 * 第一版每行一颗描边蓝药丸，一屏十几颗，整张表被按钮噪声淹了。改成**克制的幽灵按钮**：
 *   · 平时半透明灰底（几乎融进表里，扫数字时不被它抢），**鼠标移到这一行才实起来**；
 *   · 悬停那一下才变蓝（和 `.link-btn` 一套观感），左边挂个购物车小图标，一眼知道是"买这一种"。
 */
/** ★ 2026-09-21：「已经下过申请单的」那一格写「已申请」—— 安静点，别跟能点的按钮抢 */
.dp-buy-done { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; color: var(--green); opacity: .8; white-space: nowrap; }
/** ★ 2026-09-21：被驳回那几行 —— 行底色 + 右边那枚「驳回待整改」 */
.dp-stock-stage tr.is-rejected { background: var(--red-soft); }
.dp-stock-stage tr.is-rejected .dp-stock-name { color: var(--red); }
.dp-buy-rejected { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 600; color: var(--red); white-space: nowrap; }
/** ★ 2026-09-21：被驳回那张申请单那一行的进度 —— 第一步写「采购申请被驳回」，绿的改成红的 */
.dp-buy-step.is-rejected, .dp-buy-step.is-rejected i { color: var(--red); border-color: var(--red); }
.dp-buy-one {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 8px;
  border: 0;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.15s, background 0.15s, color 0.15s;
}
.dp-stock-stage tr:hover .dp-buy-one { opacity: 1; }
.dp-buy-one:hover { background: var(--blue); color: var(--brand-ink); opacity: 1; }
.dp-stock-unit { margin-left: 3px; color: var(--ink-3); font-size: 11.5px; }
/**
 * ★ 2026-09-21（用户口径）：「跟单这里，需要**显示图片 名称**；而且可以**一个一个选择**
 * 下采购申请单」——库存状态每一行的名字前面加两样：
 *   · 一枚**勾选框**（只给"要买 + 缺 + 还没下过申请单"的行；别的行留等宽占位，名字才对得齐）；
 *   · 一张**商品缩略图**（和「一键采购」弹窗里那一列同一个写法：悬停放大 / 双击看大图），表里小一号。
 * 勾完之后，这一块那把「一键采购」就只买勾中的那几种（见 comment.js 的 `syncStagePick`）。
 */
.dp-stock-main { display: flex; align-items: flex-start; gap: 8px; }
.dp-stock-text { min-width: 0; }
.dp-stock-pick {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  flex: none;
  padding-top: 2px;
  cursor: pointer;
}
.dp-stock-pick.is-none { cursor: default; }
.dp-stock-pick input { margin: 0; cursor: pointer; }
.dp-stock-pic {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface);
  cursor: zoom-in;
}
.dp-stock-pic img { width: 100%; height: 100%; object-fit: contain; display: block; }
.dp-stock-pic.is-empty { color: var(--ink-3); border-style: dashed; cursor: default; }
/**
 * ★ 2026-09-19（用户口径）：「采购员的事项卡内……**直接在这里面填写**采购数量 / 采购价 / 供应商 /
 * 预计到货日期」——采购申请卡里那张表因此带了输入控件。控件要**小一号**（这是卡内嵌表，
 * 不是表单页），宽度按列固定，数字右对齐，和只读的「申请」那一列一眼能分开。
 */
.dp-req-edit td { vertical-align: middle; }
.dp-req-edit input,
.dp-req-edit select {
  width: 100%;
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: 12px;
  font-family: inherit;
}
.dp-req-edit input.num,
.dp-req-edit input[type='number'] { text-align: right; font-family: var(--mono); }
/**
 * ★ 2026-09-25（用户口径）：「采购价那里是必填，但如果**暂时缺价，要有个选项**，
 *   后续三天内提醒必须完善」——采购价那一格 = 输入框 + 一枚「暂缺」小勾
 *   （版式和采购开单弹窗里那个 `.ie-pending` 一套口径：勾上就把单价格清空置灰）。
 *   采购价那列因此要宽一点（84 → 118），勾才不会把输入框挤没。
 */
.dp-req-edit .dp-req-price-cell { display: flex; align-items: center; gap: 6px; }
/* ⚠️ 窄屏（手机版 / 半屏窗口）那一列会被挤：给输入框留个下限，宁可让名字那列收窄换行，
   也不要把价那格压成 14px（什么都打不进去）。 */
.dp-req-edit .dp-req-price-cell .dp-req-price { flex: 1 1 auto; min-width: 44px; }
.dp-req-edit .dp-req-pending {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  font-size: 11px;
  color: var(--ink-3);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
/**
 * ⚠️ 上面那条 `.dp-req-edit input`（带边框 + 圆角 + 底色）会**糊到这个复选框上**，
 * 把它画成一个圆角空心方块（截图里就是）。这里把边框 / 底色清回去，让它还是**原生小勾**。
 */
.dp-req-edit .dp-req-pending input {
  width: 13px;
  height: 13px;
  flex: none;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
  accent-color: var(--yellow);
}
.dp-req-edit .dp-req-pending:has(input:checked) { color: var(--yellow); font-weight: 600; }
.dp-req-edit input:focus,
.dp-req-edit select:focus { outline: none; border-color: var(--blue); background: var(--blue-soft); }
.dp-req-edit input:disabled,
.dp-req-edit select:disabled { background: var(--surface-2); color: var(--ink-3); }
/**
 * ★ 2026-09-22（用户口径）：「采购审批这里 UI 设计再改善，**重要信息不够突出**」。
 *
 * 采购审批的中栏原来是"一片平铺"：单号 / 项目号 / 订单号挤在一行灰色小字里，需求到货与
 * 预计到货各占一行、值和说明同色同重，「转成采购单」飘在最右 —— 采购扫一眼看不出
 * "要买什么、多少、什么时候要、我还差哪几格没填"。重排成**三层**（内容一个没动）：
 *   ① `.dp-req-sum`   摘要条：单号（主角）+ 项目 / 订单（引用胶囊）+ 品种 · 申请合计（数字放大）
 *   ② `.dp-req-when`  时间条：需求到货 ⇄ 预计到货**并排**，各带一枚结论胶囊（可对比）
 *   ③ 动作与那句「下一步点哪儿」：统一在**页脚动作条**（`.dp-acts` / `.dp-acts-note`）——
 *      2026-09-25 之前卡里还另有一条 `.dp-req-order`（提示 + 一颗深色药丸），
 *      和动作条上那颗是同一个动作，重复 + 中间留白，已经撤掉。
 * 颜色全部走令牌，换主题不破相。
 */
.dp-req-sum { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dp-req-id { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
/* 单号是这一块的主角：比正文大一档，别再拿灰色小字埋掉 */
.dp-req-no { font-size: 14.5px; font-weight: 700; color: var(--ink); letter-spacing: 0.01em; }
.dp-req-refs { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dp-req-ref {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 8px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface-2); color: var(--ink-2);
  font-size: 11.5px; font-family: var(--mono); white-space: nowrap;
}
.dp-req-ref svg { color: var(--ink-3); }
.dp-req-kpis { display: flex; align-items: baseline; gap: 16px; margin-left: auto; }
.dp-req-kpi { display: inline-flex; align-items: baseline; gap: 3px; }
.dp-req-kpi .k { font-size: 11px; font-weight: 600; color: var(--ink-3); }
/* 数字大写、单位小写（工作台版式习惯） */
.dp-req-kpi .v { font-size: 19px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; line-height: 1; }
.dp-req-kpi .u { font-size: 11px; color: var(--ink-3); }

/* 时间条：两格并排 —— 采购就是靠"需求 vs 预计"这一对判断来不来得及 */
.dp-req-when { display: flex; gap: 10px; flex-wrap: wrap; }
.dp-req-when-cell {
  flex: 1 1 230px; min-width: 0;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.dp-req-when-cell .k { font-size: 11.5px; font-weight: 650; color: var(--ink-2); white-space: nowrap; }
.dp-req-when-cell .v { font-size: 15px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
/* 需求到货最要紧（来不来得及看它）——整格按紧急度上色 */
.dp-req-when-cell.is-need.is-late { background: var(--red-soft); border-color: var(--red); }
.dp-req-when-cell.is-need.is-late .v,
.dp-req-when-cell.is-need.is-late .k { color: var(--red); }
.dp-req-when-cell.is-need.is-today,
.dp-req-when-cell.is-need.is-tight { background: var(--yellow-soft); border-color: var(--yellow); }
.dp-req-when-cell.is-need.is-today .v,
.dp-req-when-cell.is-need.is-tight .v { color: var(--yellow); }
.dp-req-when-cell.is-need.is-none { border-style: dashed; }
.dp-req-when-cell.is-need.is-none .v { color: var(--ink-3); }
/* 结论胶囊：逾期红 / 剩几天黄 / 赶得上绿 / 没法定灰 */
.dp-when-chip {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  background: var(--surface-3); color: var(--ink-2);
  font-size: 11.5px; font-weight: 650; white-space: nowrap;
}
.dp-when-chip.is-late { background: var(--red-soft); color: var(--red); }
.dp-when-chip.is-today,
.dp-when-chip.is-tight { background: var(--yellow-soft); color: var(--yellow); }
.dp-when-chip.is-ok { background: var(--green-soft); color: var(--green); }
.dp-when-chip.is-none { background: var(--surface-3); color: var(--ink-3); }

/**
 * ★ 2026-09-25（用户口径）：「**主要这里看着感觉很突兀**」——
 * 采购申请卡页脚那句说明原来贴最左、按钮贴最右，中间横着空一大截，**两头都不挨着**。
 * 现在 `margin-left: auto` 把「说明 + 后面的按钮」整组推到右边，像对话框底部那排动作一样成组；
 * 说明本身也收短了一句（"填全上面三列 → …"）。
 */
.dp-acts .dp-acts-note { margin-left: auto; flex: 0 1 auto; min-width: 0; line-height: 1.5; }
/**
 * ★ 2026-09-25（用户口径）：「**主要这里看着感觉很突兀**」——采购申请卡的页脚动作条离卡片
 * 隔着一条缝 + 一道分隔线，看着像"底下又摆了另一个空条"。
 * 这一档把它**并进卡片**：缝收掉、分隔线去掉（内容长时这一条仍然钉在栏底，
 * 只是"-req"这种卡里通常就一行明细，靠在一起更像卡片自己的动作）。
 */
.dp-form.is-req .dp-fields { padding-bottom: 2px; }
.dp-form.is-req .dp-foot { border-top: 0; padding-top: 4px; }

/* 表头点出"要填的三列"；没填的格子自己也高亮（黄底虚线边）—— 还差哪几格一眼扫得出来 */
.dp-req-edit th.is-fill { color: var(--yellow); }
.dp-req-edit .is-todo { background: var(--yellow-soft); border-style: dashed; border-color: var(--yellow); }
.dp-req-edit .dp-req-price:placeholder-shown { background: var(--yellow-soft); border-style: dashed; border-color: var(--yellow); }
/* 还没审批（没轮到采购填）时那几格是 disabled 的 —— 别再用琥珀色喊"待填"，那是下一态的事。
   带上 `.dp-req-price` / `.is-todo` 是为了压过上面那两条同特异度的琥珀规则（源序在后才赢得过）。 */
.dp-req-edit input:disabled,
.dp-req-edit select:disabled,
.dp-req-edit .dp-req-price:disabled,
.dp-req-edit .is-todo:disabled { background: var(--surface-2); border-style: solid; border-color: var(--line); color: var(--ink-3); }
/* 两个日期输入在时间条里：固定宽，别把胶囊挤到第二行 */
.dp-req-when-cell .dp-req-due,
.dp-req-when-cell .dp-req-need-in { width: 148px; flex: none; }
.dp-req-apply { width: 100%; padding: 4px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font-size: 12px; font-family: var(--mono); text-align: right; }
.dp-req-apply:focus { outline: none; border-color: var(--blue); background: var(--blue-soft); }
.dp-req-need-in { padding: 4px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font-size: 12px; font-family: var(--mono); }
.dp-req-need-in:focus { outline: none; border-color: var(--blue); background: var(--blue-soft); }
/* 采购明细（事项卡面板中栏那张表）—— 用户口径 2026-09-21：「**采购明细放大**」：
   行更高、字号更大、缩略图更大，列也更全（图片/名称/采购数/单价/供应商/下单时间/预计到货/距到货） */
.dp-buy-card-items .items-table { width: 100%; }
.dp-buy-card-items .items-table th { font-size: 12px; padding: 8px; }
.dp-buy-card-items .items-table td { padding: 10px 8px; font-size: 13px; vertical-align: middle; }
.dp-buy-card-items .pt-thumb { width: 48px; height: 48px; }
/* 采购明细面板的上下几块：单据号那行 / 表尾的 预计到货 · 平均交付 · 风险 · 催供应商 */
.bc-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 2px 0 6px; font-size: 12.5px; color: var(--ink-2); }
.bc-meta b { color: var(--ink); }
/**
 * ★ 2026-09-25（用户口径）：「**后续瓶子的下单，生成的采购单要自动把瓶子的 BOM 展示出来，
 *   有利于仓库入库入仓管理**」——瓶子那一行下面挂一句「含 瓶身×500 · 喷头×500」。
 * 只是**展示**（不加明细行、不过账）：所以要一眼看出它属于上面那一行、又别抢正文的注意力，
 * 走小一号的字 + 一层浅底 + 左侧一道竖线。
 */
.bc-bom {
  display: flex; align-items: baseline; gap: 5px;
  margin-top: 4px; padding: 3px 8px;
  border-left: 2px solid var(--line-hover, var(--line));
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--surface-2); color: var(--ink-2);
  font-size: 11.5px; line-height: 1.5;
}
.bc-bom svg { flex: none; color: var(--ink-3); }
/**
 * ★ 2026-09-22（用户口径）：那句「含 瓶身×500 · 喷头×500」里，**每个零件还带一张小图 + 单位**
 *   （用户报「这里没有显示齐全」：只有名字和数字，仓库认不出零件、也不知道按什么算）。
 * 零件多的时候让它**换行**排，别把那一格撑成一条又长又窄的横条。
 */
.bc-bom-list { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 10px; min-width: 0; }
.bc-bom-part { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
/* 名字可以折行，但「×500 个」这一小截**不许拆开**（拆了会看到孤零零一个「个」占一行） */
.bc-bom-txt { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 0 1px; min-width: 0; }
.bc-bom-qty { white-space: nowrap; }
/* 零件之间的那个「·」交给 CSS，省得拼字符串时漏一个/多一个 */
.bc-bom-part + .bc-bom-part::before { content: '·'; color: var(--ink-3); margin-right: -3px; }
/* 零件缩略图：18px 够认出是瓶身还是喷头，又不把这一句撑高（悬停看大图、双击开大图） */
.bc-bom-pic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex: none;
  border: 1px solid var(--line); border-radius: 4px;
  overflow: hidden; background: var(--surface); cursor: zoom-in;
}
.bc-bom-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bc-bom-pic.is-empty { color: var(--ink-3); border-style: dashed; cursor: default; }
/**
 * ★ 2026-09-22（用户口径）：「瓶子的待入库单，这里要显示**全部的 BOM**，而且**每个都能填
 *   本次到货数和标签打印**」——验收作业台上，瓶子下面**一个零件一行**（不再是挤成一句）。
 *
 * 零件行是"挂在瓶子那一行下面"的：左边缩进 + 引导符，底色压一档，一眼分得清
 * 「这是明细行」还是「这是它的零件」。小图 26px（和待入库列表里那种一个尺寸）。
 */
.job-part-row td { background: var(--surface-2); }
.job-part-row td:first-child { padding-left: 20px; }
.job-part-cell { display: flex; align-items: center; gap: 7px; min-width: 0; }
.job-part-elbow { color: var(--ink-3); flex: none; }
/* 「 · 零件」这枚小标不许被拆到两行（名字太长时整块挪到下一行，别断成「零 / 件」） */
.job-part-name { min-width: 0; }
.job-part-tag { white-space: nowrap; font-size: 11.5px; color: var(--ink-3); }
.job-part-row .pt-thumb { width: 26px; height: 26px; flex: none; }
/* 打印按钮上的字（「再打印（已 2 张）」）不许折行 —— 一折行整列就被撑高，行高跟着跳 */
.items-table [data-job-print], .items-table [data-job-part-print] { white-space: nowrap; }
/* 瓶子那一行里的提示：含 N 个零件，逐行点数 / 逐行打标签 */
.bc-bom-hint { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 11.5px; color: var(--ink-3); }
/**
 * ★ 2026-09-25（用户口径）：「（采购明细的单价那格）**在这里也能直接输入单价**」——
 * 单价那格就地可填（回车 / 离开就存）。没价时**黄底虚线 + 「待补价」占位**
 * （和采购申请卡那几格的"还差哪一格"一个观感），填好了就恢复成普通的格子。
 */
.dp-buy-card-items .bc-price-in {
  width: 96px;
  padding: 4px 6px;
  text-align: right;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink);
  background: var(--yellow-soft);
  border: 1px dashed var(--yellow);
  border-radius: var(--radius-sm);
}
.dp-buy-card-items .bc-price-in:focus {
  outline: none;
  border-style: solid;
  border-color: var(--blue);
  background: var(--blue-soft);
}
/* 已经填了数（不显示占位）→ 看着就是个普通只读格，别整列黄澄澄的 */
.dp-buy-card-items .bc-price-in:not(:placeholder-shown) {
  background: var(--surface);
  border-style: solid;
  border-color: var(--line);
}
/**
 * ★ 2026-09-25（用户口径）：「需要生成一个**采购单图片**，采购人员能够发送给供应商看」
 *   ——「采购单图片」那个弹窗（`purchase-slip.js`）。
 * 图片本身是 canvas 画的 2 倍图（宽 2000px），弹窗里按容器宽缩放显示（不裁、不模糊），
 * 白底一层浅框，看着就像一张"可以存下来的单子"。
 */
.slip-box { display: flex; flex-direction: column; gap: 8px; }
/**
 * ★ 2026-09-25（用户口径）：「图片还有点小，**再扩大**」——
 * 预览窗比通用 `wide`（880px）再宽一档（宽屏 1180、窄屏 94vw），图也就能画得更大。
 */
.modal.modal-slip { max-width: min(1180px, 94vw); }
.modal.modal-slip .modal-body { max-height: 74vh; }
.slip-loading { padding: 40px 0; text-align: center; color: var(--ink-3); font-size: 13px; }
.slip-loading.err { color: var(--red); }
.slip-switch { display: flex; flex-wrap: wrap; gap: 6px; }
.slip-hint { font-size: 12.5px; color: var(--ink-2); }
.slip-hint b { color: var(--ink); }
.slip-img {
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: #fff;
  overflow: auto;
  max-height: 62vh;
  padding: 6px;
}
.slip-img img { display: block; width: 100%; height: auto; }
.slip-foot { display: flex; align-items: center; gap: 8px; width: 100%; }
.slip-foot .grow { flex: 1 1 auto; }
/* ★ 2026-09-21（用户口径「整体排版太难看」）：表尾改成**横排的 标签+值**（能换行），
   不再拉成一整行两列 —— 那样两头的值隔了半个屏幕，看着散 */
.bc-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 26px; margin-top: 10px; border-top: 1px dashed var(--line-2); padding-top: 10px; }
.bc-foot-item { display: inline-flex; align-items: baseline; gap: 8px; font-size: 13px; min-width: 0; }
.bc-foot-item.is-wide { flex: 1 1 100%; }
.bc-foot-k { color: var(--ink-3); font-size: 12px; white-space: nowrap; }
.bc-foot-v { color: var(--ink); display: inline-flex; align-items: baseline; gap: 2px; }
/* 催供应商挪到标题那一行（用户口径：不要孤零零占一行） */
.bc-nudge { flex: none; }
/* 标签：位置固定在名字下面（无论名字一行还是两行） */
.dp-stock-kind { padding: 2px 8px; border-radius: 4px; background: var(--surface-3); color: var(--ink-3); font-size: 11.5px; white-space: nowrap; }
.dp-stock-kind.is-buy { background: #fff3d6; color: #8a6100; }
.dp-stock-kind.is-make { background: #e8f1fd; color: #1f5fa8; }
/* 标签（自己做 / 要买）排在名字下面 */
.dp-stock-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
/**
 * 采购进度（用户口径 2026-09-18：「跟单里的进度，**排成一行**（并**加入进度条**）」）：
 * 三个点 + 中间那条连接线就是进度条（走过的绿），标签跟在点后面，**不换行**；
 * 整块单独占表格的一整行（`.dp-buy-row`），窄栏里也摆得下。
 */
/**
 * 采购进度那一行：**缩进**，而且**跟它上面那行同色** —— 两块读起来是一张卡，
 * 不会跟下一行料混（用户口径 2026-09-18：「上下两个挨在一起…很容易看错」）。
 */
.dp-buy-row td { padding-top: 2px !important; padding-bottom: 9px !important; padding-left: 18px !important; background: var(--surface-2); }
tr.is-make + tr.dp-buy-row td { background: rgba(64, 128, 220, 0.06); }
tr.is-buy.is-short + tr.dp-buy-row td { background: rgba(255, 214, 102, 0.18); }
/**
 * ★ 2026-09-24：链路补上「待下单」那一档（四步）之后，一行放不下单号那句提示了 ——
 *   所以容器允许折行：**链自己裹成一段不折**（`.dp-buy-chain`），提示作为兄弟节点，
 *   放不下就整句折到第二行（原来是 `nowrap` + 提示被省略号吃掉半句）。
 */
.dp-buy-steps { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 11px; white-space: nowrap; }
.dp-buy-chain { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 6px; }
.dp-buy-step { display: inline-flex; align-items: center; gap: 4px; color: var(--ink-3); }
.dp-buy-step.is-on { color: #1c7a3d; font-weight: 500; }
.dp-buy-step.is-now { font-weight: 700; }
.dp-buy-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--surface-3); box-shadow: inset 0 0 0 1px var(--line); flex: none; }
.dp-buy-step.is-on .dp-buy-dot { background: #1c7a3d; box-shadow: none; }
.dp-buy-step.is-now .dp-buy-dot { box-shadow: 0 0 0 3px rgba(28, 122, 61, 0.16); }
.dp-buy-link { width: 22px; height: 3px; border-radius: 2px; background: var(--line); flex: none; }
.dp-buy-link.is-on { background: #1c7a3d; }
.dp-buy-hint { margin-left: auto; min-width: 0; color: var(--ink-3); font-size: 11px; overflow: hidden; text-overflow: ellipsis; }
/* 料行之间一条分隔线；账面够的"要买"行也铺一层浅底（别纯白，免得跟上面的进度块糊在一起） */
.dp-stock-stage tbody tr.is-make td,
.dp-stock-stage tbody tr.is-buy td { border-top: 1px solid var(--line-2); }
.dp-stock-stage tbody tr.is-buy:not(.is-short) td { background: var(--surface-2); }
/**
 * 行色：自己做=蓝、要买且缺=黄（**只用底色**）。
 * ★ 2026-09-18 用户口径：「一张卡的最开头色块贯穿卡片，要不邻近的看着容易看错行」——
 * 原来每行最左边还有一条 3px 色带，相邻几行连起来像一整条，分不清哪行是哪行；**去掉色带**，
 * 靠"底色 + 名字下面那枚标签（自己做 / 要买）"区分就够清楚了。
 */
.dp-stock-stage tr.is-make td { background: rgba(64, 128, 220, 0.06); }
.dp-stock-stage tr.is-buy.is-short td { background: rgba(255, 214, 102, 0.18); }
.dp-stock-stage tr.is-make.is-short td { background: rgba(64, 128, 220, 0.13); }
/**
 * ★ 2026-09-24（用户口径）：「**有下采购申请单的，这里需要与其他还未下采购申请单的颜色区分开**，
 *   并且还没下采购申请单的自动排到上面」。
 *   所以：**还没下的**保持"要动手"的黄底（排在上面）；**已经下过 / 在途**的换中性灰底、
 *   名字压淡一档，跟着它下面那条进度行也一起转灰（原来进度行会继承黄底）。
 *   必须排在上面那两条短底规则**之后**（同为 3 个类，靠顺序取胜）。
 */
.dp-stock-stage tr.is-buy.is-requested td { background: var(--surface-2); }
.dp-stock-stage tr.is-buy.is-requested .dp-stock-name { color: var(--ink-2); }
.dp-stock-stage tr.is-buy.is-requested + tr.dp-buy-row td { background: var(--surface-2); }
.dp-stock-stage tr.is-short td.num:last-child b { font-size: 15px; }
/* 表头下面那句图例 */
.dp-stock-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 2px 0 6px; color: var(--ink-3); font-size: 11.5px; }
.dp-stock-legend .dp-stock-kind { margin-left: 0; }
/**
 * ★ 2026-09-24（用户口径 B）：「拌料车间的工作是给罐装车间的料体做准备，罐装车间的（工作）
 *   是给组装车间的已灌装做准备 —— 这里需要**看着有联动**」。
 *
 * 联动 = 「上一道的产出，就是下一道的投入」。窄栏里横着画链条会被截断，所以画成
 * **一行一道工序**：`①拌料 │ 原料 3 种 → 料体-… │ [原料 · 不看库存]`——
 * 上一行右边那格（产出）和下一行左边那格（投入）**字面相同**，上下对着读就是那条链。
 *
 * ★ 2026-09-24 用户口径（「这里的排版要规定，对齐全」）：**列宽写死**——
 *   车间名 84px / 投入 120px / 箭头 16px 四列定宽，产出占剩下的（超出省略号）。
 *   各行的名字因此都在同一个 x 处截断、账都靠同一个右边缘，读起来才像一张表；
 *   账（要买那摊一行、自己做那摊一行）**整盒占一整行**、右对齐 —— 列也就对齐了。
 */
.dp-chain { margin: 0 0 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.dp-chain-head { display: flex; align-items: baseline; gap: 8px; padding: 6px 10px; background: var(--surface-2); border-bottom: 1px solid var(--line-2); }
.dp-chain-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; padding: 5px 10px; font-size: 11.5px; border-top: 1px solid var(--line-2); }
.dp-chain-row:first-of-type { border-top: 0; }
.dp-chain-step { flex: none; display: inline-flex; align-items: center; gap: 5px; width: 84px; color: var(--ink); font-weight: 650; white-space: nowrap; }
.dp-chain-step i {
  width: 15px; height: 15px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--surface-3); color: var(--ink-2);
  font-size: 9.5px; font-style: normal; font-weight: 700;
}
.dp-chain-in,
.dp-chain-out { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dp-chain-in { flex: none; width: 120px; color: var(--ink-2); }
.dp-chain-out { flex: 1 1 80px; color: var(--ink); font-weight: 500; }
.dp-chain-arrow { flex: none; width: 16px; display: inline-flex; justify-content: center; color: var(--ink-3); }
.dp-chain-chips { flex: 1 0 100%; }
.dp-chain-chips .dp-stock-stage-count { font-size: 10.5px; padding: 1px 7px; }
/**
 * 一段的账：**一组一行**（要买那摊一行 / 自己做那摊一行），整盒右对齐 ——
 * 车间头带（`.dp-stock-stage-chips`）和工艺链（`.dp-chain-chips`）共用这套。
 */
.dp-chip-rows { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.dp-chip-row { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 4px; }
/**
 * 这一行是什么（「产出进度」）—— 摆在行首、把章挤到右边（`margin-right:auto`）；
 * 用户口径 2026-09-24：「还是看不出来是什么意思」，所以数字前先给个名目。
 */
.dp-chip-row-k { margin-right: auto; color: var(--ink-3); font-size: 10.5px; font-style: normal; white-space: nowrap; }
/* 行首那道工序的名字用车间色（和下面三块卡片的色带一一对上） */
.dp-chain-row:nth-of-type(2) .dp-chain-step i { background: var(--violet-soft); color: var(--violet); }
.dp-chain-row:nth-of-type(3) .dp-chain-step i { background: var(--blue-soft); color: var(--blue); }
.dp-chain-row:nth-of-type(4) .dp-chain-step i { background: var(--green-soft); color: var(--green); }
/* 「自己做」那行上的**来路**（口径 A）：这一行是上一道工序做出来的 */
.dp-stock-from {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 1px 7px; border-radius: 999px;
  background: var(--blue-soft); color: var(--blue);
  font-size: 10.5px; font-weight: 600; white-space: nowrap;
}
/* 「订单详情」（跟单卡最上面那块）：左边产品图 + 右边几格字段（用户口径 2026-09-18） */
.dp-oi-body { display: flex; gap: 10px; align-items: flex-start; }
/**
 * ★ 2026-09-21（用户口径）：「**订单详情太大**」——这一块原来占掉跟单卡上半屏：
 * 图 76px、字段间距 8/16、值 12.5px。收一档（图 56、间距 4/12、值 12），
 * 一屏能多看两三行库存；信息一个不少。
 */
.dp-oi-body { gap: 8px; }
.dp-oi-pic { width: 56px; height: 56px; }
.dp-oi-pic.is-none { flex: none; height: 56px; padding: 0; overflow: hidden; }
/* 占位块里的「没挂产品图」文字**不画**（窄方块里会换行把格子撑高一倍）—— 只留图标，文字进 title */
.dp-oi-pic.is-none span { display: none; }
.dp-oi-fields { gap: 4px 12px; }
.dp-oi-v { font-size: 12px; }
.dp-oi-k { font-size: 10.5px; }
/* 订单详情那几格（用户口径 2026-09-18：「文字字体颜色都太相似，下单日期和交货日期错开放也不好看」）：
   固定两列 —— 产品名称独占一行，下单/库存一行，下单日期/交货日期一行（对称）；
   标签小而淡（名目）、值深而实（内容），一眼分得清。 */
.dp-oi-fields { flex: 1; min-width: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; align-content: start; }
/* 标签在上、值在下（窄栏里这样值才有整格宽度 —— 日期不至于被截成「2026-09…」） */
.dp-oi-cell { display: flex; flex-direction: row; align-items: baseline; flex-wrap: wrap; gap: 0 6px; min-width: 0; }
.dp-oi-cell.is-wide { grid-column: 1 / -1; }
.dp-oi-k { flex: none; color: var(--ink-3); font-size: 11px; letter-spacing: 0.2px; white-space: nowrap; }
/* 日期这类短值**不折行**（"2026-09-18" 被从连字符处拆开很难看）；长值（产品名称）才折 */
.dp-oi-v { min-width: 0; font-size: 12.5px; font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dp-oi-cell.is-wide .dp-oi-v { white-space: normal; word-break: break-word; }
/* 「剩 7 天 / 逾期 2 天」自己一行（日期那行才不会被挤） */
.dp-oi-left { display: block; font-size: 11.5px; }
@media (max-width: 560px) {
  .dp-oi-fields { grid-template-columns: minmax(0, 1fr); }
  .dp-oi-cell.is-wide { grid-column: auto; }
  /**
   * 手机上（面板只有 ~320px）把工艺链的「投入」那格收掉 —— 它就是**上一行的「产出」**，
   * 上下对着读本来就有；留着它反而把名字和章挤成一串省略号（「料…」「已…」）。
   */
  .dp-chain-in { display: none; }
}
/* 备注 / 特殊要求那一块：**淡淡的黄色色块**（用户口径 2026-09-18：备注转移到右栏、用淡淡的黄色） */
.dp-track-side .dp-note-block,
.dp-note-block { background: #fffbe6; border: 1px solid #f5e6a8; border-radius: var(--radius-sm); padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; }
.dp-note-block .dp-items-head { border-bottom: 0; padding-bottom: 0; }
.dp-note-block .dp-note { background: rgba(255, 255, 255, 0.7); border-radius: 5px; padding: 4px 8px; }
/* 页脚里那一行交期（就在动作条上面）：交货日期 + 剩余天数 */
/**
 * 页脚的交期那一行（2026-09-19 用户口径：「交货日期 剩余时间比较重要，不需要再展示产品信息」）——
 * 只留「交货日期 + 剩余天数」，日期字号加大、整行给个浅底，成为页脚里最抓眼的一条；
 * 产品名去掉了（面板标题上就写着是哪一单）。
 */
.dp-due-line { display: flex; align-items: baseline; gap: 8px; padding: 7px 10px; border-radius: var(--radius-sm); background: var(--surface-2); font-size: 12.5px; }
.dp-due-k { flex: none; color: var(--ink-3); font-size: 11.5px; }
.dp-due-date { font-size: 15px; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
/**
 * 「剩 N 天」：一枚**带底色的药丸**（用户口径 2026-09-19：「剩 x 天再改下提示方式，不够明显」）——
 * 剩得多=蓝、剩 1~2 天=黄、逾期 / 今天到期=红；前面挂个钟表图标，扫一眼知道急不急。
 */
.dp-due-left { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; white-space: nowrap; background: var(--surface-3); color: var(--ink-2); }
.dp-due-line .dp-due-left.due-red { background: var(--red-soft); color: var(--red); }
.dp-due-line .dp-due-left.due-warn { background: var(--yellow-soft); color: var(--yellow); }
.dp-due-line .dp-due-left.muted { background: var(--blue-soft); color: var(--blue); }
.dp-due-line.is-empty { background: transparent; color: var(--ink-3); font-size: 11.5px; padding: 4px 0; }
.dp-due-line .due-red { color: var(--red); font-weight: 600; }
.dp-due-line .due-warn { color: var(--due-warn); font-weight: 600; }
/* 备注块：**固定显示全**（用户口径 2026-09-19）—— 它是固定区的一员，不许被压扁/裁掉；
   标题（备注 / 特殊要求）不许折行，让右边那句说明自己去换行 */
.dp-track-side .dp-note-block { flex: none; }
.dp-note-block .dp-items-head { flex-wrap: wrap; row-gap: 4px; }
.dp-note-block .dp-items-head .eyebrow { white-space: nowrap; }
.due-red { color: var(--red); font-weight: 600; }
.due-warn { color: var(--due-warn); font-weight: 600; }
/* 发货单明细表里那枚产品图（36px 缩略图，点开看大图） */
.dp-ship-pic { width: 36px; height: 36px; border-radius: 5px; object-fit: contain; background: var(--surface-3); display: inline-block; vertical-align: middle; }
.dp-ship-pic.is-none { display: inline-flex; align-items: center; justify-content: center; color: var(--ink-3); }
/* 发货凭证**按类**那三行（发货清单 / 车牌照 / 其他）：一行一类，右边是上传按钮，下面这一类自己的图 */
.dp-proof-rows { display: flex; flex-direction: column; gap: 10px; }
.dp-proof-row { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border: 1px solid var(--line-2); border-radius: var(--radius-sm); background: var(--surface-2); }
.dp-proof-row-head { display: flex; align-items: center; gap: 8px; }
.dp-proof-row-head b { font-size: 12.5px; }
/* 发货单面板里的「发货凭证」那块：一排缩略图 + 上传按钮 */
.dp-ship-proof { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.dp-ship-proof-acts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* 「销售单」那个小前缀（面板右上角的药丸上） */
.dp-order-kind { color: var(--ink-3); opacity: 0.8; margin-right: 4px; font-weight: 500; }
/* 确定发货弹窗：商品明细那张表里的数量框 */
.so-form { display: flex; flex-direction: column; gap: 10px; }
.so-form .so-qty { width: 100px; text-align: right; }
/* 右上角那格：编号短了（KAI-012 / SD-001 / TD-039），放得下，超长还有省略号兜底。
   字号再压 0.5px、「项目卡」这类前缀也小一号 —— 让 KAI-012 这种号在窄方块里也**不被截**。 */
.task-card.sm .tc-top .tc-no { flex: 0 1 auto; min-width: 0; font-size: 9.5px; }
.task-card.sm .tc-top .tc-no-kind { margin-right: 3px; font-size: 9px; }
.task-card.sm .tc-ring { flex: 0 0 18px; width: 18px; height: 18px; font-size: 9.5px; }
.task-card.sm .tc-title {
  flex: none;
  min-width: 0;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.3;
  height: 3.9em; /* 固定三行：一行也不许缩，缩了下面几行就错位 */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/**
 * ★ 2026-09-21（用户口径）：「**待跟单里的卡片，高度缩小**」——
 * 跟单工作台那一列（）的卡比别处高：标题固定三行 + 内边距大。
 * 只收**这一块**：标题压到两行、内边距和行距各收一档（别的板子不动）。
 */
/**
 * ★ 2026-09-21（用户口径，第二轮）：「订单交付部-跟单员的工作台内的事项卡，**高度还要再压缩**」——
 * 再收一档：内边距 8/9/7 → **6/8/5**、行距 3 → **2**；标题 2.6em → **2.35em**、字号 11 → 10.5；
 * 状态行/时间行/进度条各收一点（进度条 3px → 2px）。
 */
/* ★ 卡高的真正来源是 `.task-card.sm` 的 `min-height: 152px`（小方块设计）+ space-between 撑开 ——
   跟单这一列放开它，卡片就按内容高度走（用户口径：「高度还要再压缩」）。 */
.quad-grid.track-grid .task-card.sm { min-height: 0; justify-content: flex-start; }
.quad-grid.track-grid .task-card { padding: 6px 8px 5px; gap: 2px; }
.quad-grid.track-grid .task-card.sm .tc-title { height: 2.35em; -webkit-line-clamp: 2; font-size: 10.5px; line-height: 1.18; }
.quad-grid.track-grid .task-card.sm .tc-meta { font-size: 10.5px; }
.quad-grid.track-grid .task-card.sm .tc-when { font-size: 10px; }
.quad-grid.track-grid .task-card.sm .tc-bar { height: 2px; }
.quad-grid.track-grid .task-card.sm .tc-ring { width: 20px; height: 20px; font-size: 10px; }
/* 第二行：状态 + 剩 N 天 / 逾期 N 天（环里是同一个数，文字把它说全） */
.task-card.sm .tc-meta {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 15px;
  font-size: 10px;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
}
.task-card.sm .tag { flex: none; padding: 0 5px; font-size: 10px; line-height: 14px; border-radius: 4px; }
.task-card.sm .tc-meta .badge { flex: none; padding: 0 6px; font-size: 10px; line-height: 14px; }
/**
 * ★ 2026-09-21（用户口径）：「**剩余时间与色块时间重叠了**」——
 * 卡片底色现在是一条"左浅右深"的进度渐变，而「剩 N 天」就贴在右端（颜色最重的地方），
 * 字和底色糊在一起。给它一枚**实底药丸**（卡片同色 + 一点点内边距），
 * 无论底下渐变的深浅，这行字都清清楚楚。
 */
.task-card.sm .tc-left { flex: none; font-size: 10px; font-weight: 650; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; background: var(--surface); border-radius: 999px; padding: 0 6px; box-shadow: 0 0 0 2px var(--surface); }
.task-card.sm .tc-left.red { color: var(--red); }
.task-card.sm .tc-left.yellow { color: var(--due-warn); }
.task-card.sm .tc-left.done { color: var(--green); }
/* 第三行：开始 · 截止 */
.task-card.sm .tc-when {
  height: 14px;
  font-size: 10px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.task-card.sm .tc-when.red { color: var(--red); font-weight: 600; }
.task-card.sm .tc-when.yellow { color: var(--due-warn); }
/* 小方块**没有动作行**、也没有「详情」两个字了（用户口径 2026-09-17 / 2026-09-18：
   开始做 / 登记入库 这些操作去面板里做，整张卡就是入口；没读过的讨论角标缩成第二行右端的小圆点）。
   这条 .tc-log 规则留着是给**大卡**那套用的（小卡不再有 .tc-log 节点）。 */
.task-card.sm .tc-log { flex: none; font-size: 10.5px; white-space: nowrap; }
/* 贴底那条进度：通栏 3px，按**剩余比例**涂色（满格 = 时间还早），逾期变红 */
.task-card.sm .tc-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  border-radius: 0;
}
.task-card.sm .tc-bar > i { border-radius: 0; }
/* 能点的子项目卡编号：点了跳到那个项目并选中 */
/* 时间仪表：圈上按**剩余比例**涂色，圈里写着还剩几天（今天到期 0、逾期 !）。
   中间用 radial-gradient 掏空（不用伪元素），字才压得住。 */
.tc-ring {
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--blue);
  background:
    radial-gradient(closest-side, var(--card-bg, var(--surface)) 66%, transparent 68%),
    conic-gradient(var(--blue) calc(var(--pct, 0) * 1%), var(--surface-3) 0);
}
.tc-ring.yellow { color: var(--due-warn); background: radial-gradient(closest-side, var(--card-bg, var(--surface)) 66%, transparent 68%), conic-gradient(#e0a63c calc(var(--pct, 0) * 1%), var(--surface-3) 0); }
.tc-ring.red { color: var(--red); background: radial-gradient(closest-side, var(--card-bg, var(--surface)) 66%, transparent 68%), conic-gradient(var(--red) calc(var(--pct, 0) * 1%), var(--surface-3) 0); }
.tc-ring.none { color: var(--ink-3); background: radial-gradient(closest-side, var(--card-bg, var(--surface)) 66%, transparent 68%), conic-gradient(var(--surface-3) 100%, var(--surface-3) 0); }
.tc-ring.done { color: var(--green); background: radial-gradient(closest-side, var(--card-bg, var(--surface)) 66%, transparent 68%), conic-gradient(var(--green) 100%, var(--surface-3) 0); }

/* 时间窗：开始 · 截止 · 剩 N 天；下面一根按「剩余」画的进度条 + 百分比 */
.tc-time { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.tc-time.yellow { color: var(--due-warn); }
.tc-time.red { color: var(--red); font-weight: 600; }
.tc-progress { display: flex; align-items: center; gap: 6px; }
.tc-bar { flex: 1 1 auto; display: block; height: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.tc-bar > i { display: block; height: 100%; border-radius: 999px; background: var(--blue); transition: width 0.2s ease; }
.tc-bar.yellow > i { background: #e0a63c; }
.tc-bar.red > i { background: var(--red); }
.tc-bar.done > i { background: var(--green); }
.tc-pct { flex: none; font-size: 10.5px; font-weight: 650; color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tc-progress.red .tc-pct { color: var(--red); }
.tc-progress.yellow .tc-pct { color: var(--due-warn); }


.tc-jump { color: var(--blue); text-decoration: none; border-bottom: 1px dashed transparent; }
.tc-jump:hover { border-bottom-color: var(--blue); }

/* 四象限里的一件事：一行扫完（圈 = 开始做/办完，右边两枚小链接 = 改 / 留痕） */

/* 筛选条 + 几个入口**同一行**（用户口径 2026-09-19：底部那行备注删掉，给事项卡留空间） */
.quad-toolbar { display: flex; align-items: center; gap: 10px; padding: 0 32px 6px; flex: none; }
.quad-toolbar .quad-filter { margin: 0; }
.quad-toolbar .quad-links { margin-left: auto; display: flex; gap: 12px; flex: none; }
/**
 * ★ 2026-09-19（用户口径）：「工作台内千问办公入口再优化一下，太丑了」——
 * 合成一条药丸：品牌标 + 引导语 + 状态，整条可点、悬停描边转蓝。全用令牌。
 */
.ai-ask-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 32px 8px;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.16s, box-shadow 0.16s, background 0.16s;
}
.ai-ask-bar:hover,
.ai-ask-bar:focus-visible { border-color: var(--blue); background: var(--blue-soft); box-shadow: var(--shadow); outline: none; }
.ai-ask-brand { display: inline-flex; align-items: center; gap: 6px; flex: none; color: var(--blue); font-size: 13px; }
.ai-ask-brand b { font-weight: 650; }
.ai-ask-hint { flex: 1; min-width: 0; color: var(--ink-3); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-ask-status { flex: none; padding: 2px 9px; border-radius: 999px; background: var(--surface-2); color: var(--ink-3); font-size: 11.5px; white-space: nowrap; }

/* ============================== 项目板块 CSS 开始 ==============================
   项目页（server/web/pages-project.js）的全套样式，到此段末尾的结束标记为止：
   左栏（类型 / 收起拖宽）+ 工具栏（搜索 / 筛选 / 分组 / 排序）+ 项目卡列 + 流程矩阵。

   **这一段只准用 var(--x)**：写死十六进制在深空 / 暖阳 / 薄荷下会破相
   （深空主题里一行悬停曾经变成近白色、字全糊 —— 别再来一次）。
   唯一放行的字面量是「画在实心色块上的白字」#fff。web.test.js 有测试盯着这一段。
   ============================================================================ */

/* ------------------------------------------------------------ 项目：左栏（可拖动调宽 / 可收起） */

.proj-layout { display: flex; align-items: flex-start; }
.proj-side {
  flex: 0 0 auto;
  width: var(--side-w, 244px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 10px 8px 8px;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 14px;
  min-height: min(74vh, 640px);
  overflow: hidden;
}
.proj-side-head { display: flex; align-items: center; gap: 6px; padding: 2px 8px 10px; }
.ps-title { flex: 1; min-width: 0; font-size: 15px; font-weight: 650; letter-spacing: -0.01em; }
.pj-collapse {
  flex: none;
  border: none;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  padding: 2px 7px;
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.4;
}
.pj-collapse:hover { background: var(--surface-2); color: var(--ink); }
.proj-side-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; }
.proj-side-foot { border-top: 1px solid var(--line-2); margin-top: 8px; padding-top: 8px; }
.pj-create {
  width: 100%;
  text-align: left;
  border: none;
  background: transparent;
  color: var(--blue);
  font-size: 13px;
  font-weight: 600;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.pj-create:hover { background: var(--blue-soft); }
.pj-hint { font-size: 11px; color: var(--ink-3); padding: 10px 10px 4px; line-height: 1.5; }

/* 左栏的三种行：项目（.pj）、顶部三行（.pj-row）、分类/子组（.pj-cat） */
.pj,
.pj-row,
.pj-cat {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 7px 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font-size: 13px;
  line-height: 1.35;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.pj:hover,
.pj-row:hover,
.pj-cat:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.pj.active,
.pj-row.active { background: var(--blue-soft); color: var(--blue); font-weight: 600; }
.pj-icon { flex: none; width: 16px; text-align: center; font-size: 13px; }
.pj-no { flex: none; font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); }
.pj-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pj-star { flex: none; color: var(--star-gold); font-size: 11px; }
.pj-count { margin-left: auto; flex: none; font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
/* 左栏类型行上的编辑入口：平时不显眼，悬停整行 / 悬停它自己才亮起来 —— 不跟项目名抢视线。
   （它在 <a> 里面，所以样式上做成 <span>；点击由 JS 拦掉默认行为，不会跳转。） */
.pj-edit {
  flex: none;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 6px;
  font-size: 11px;
  color: var(--ink-3);
  opacity: 0;
  transition: opacity 0.12s ease, background 0.12s ease;
}
.pj:hover .pj-edit,
.pj-row:hover .pj-edit { opacity: 1; }
.pj-edit:hover { background: var(--surface-3); color: var(--ink); }
.pj-row.active .pj-edit { opacity: 0.75; }
.pj.active .pj-count,
.pj-row.active .pj-count { color: var(--blue); }
.pj-cat { font-weight: 500; }
.pj-cat-top { font-weight: 650; color: var(--ink); }
.pj-caret { flex: none; width: 10px; color: var(--ink-3); font-size: 10px; }
.pj-cat-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pj-sep { height: 1px; background: var(--line-2); margin: 8px 6px; }

/* 收起后的窄边 */
.proj-side.is-collapsed {
  width: var(--side-w, 46px);
  min-height: 0;
  align-items: center;
  gap: 6px;
  padding: 10px 6px;
}
.pj-rail {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: none;
  background: transparent;
  color: var(--ink-2);
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
}
.pj-rail:hover { background: var(--surface-2); color: var(--ink); }
.pj-rail-label { writing-mode: vertical-rl; font-size: 11px; color: var(--ink-3); letter-spacing: 0.18em; margin-top: 4px; }

/* 拖动调宽的把手：只有悬停/拖动时才显出一条蓝线，平时不打扰 */
.proj-resizer {
  flex: 0 0 10px;
  align-self: stretch;
  position: relative;
  cursor: col-resize;
  border-radius: 6px;
}
.proj-resizer::after {
  content: '';
  position: absolute;
  top: 8px;
  bottom: 8px;
  left: 4px;
  width: 2px;
  border-radius: 2px;
  background: transparent;
  transition: background 0.12s ease;
}
.proj-resizer:hover::after,
body.resizing .proj-resizer::after { background: var(--blue); }
body.resizing { cursor: col-resize; user-select: none; }

.proj-main { flex: 1 1 auto; min-width: 0; }

/* ------------------------------------------------------------ 项目：右栏与工具条 */

.proj-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.tabs-inline { margin-bottom: 0; }

/* 看板顶部一行小字：说清"这块板是谁的、一行代表什么"（类型名从卡片挪到了这里） */
.flow-hint { font-size: 12px; color: var(--ink-3); margin-bottom: 10px; }
/* 工具栏按钮（筛选 / 分组）：跟输入框一个高度，选中态用冷色 */
.flow-bar .fb-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
}
.flow-bar .fb-btn:hover { border-color: var(--ink-3); color: var(--ink); }
.flow-bar .fb-btn.on { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); }
/* 工具栏里的「＋ 发起项目」（在「分组」右边）：它是**动作**，不是筛选/分组那种视图控件，
   所以文字加重、加号用主色；但**不做深色药丸** —— 页面右上角那颗「发起项目」才是主 CTA，
   一屏放两颗一样重的主按钮，眼睛不知道该点哪颗。 */
.flow-bar .fb-add { color: var(--ink); font-weight: 600; }
.flow-bar .fb-add .zm-i { color: var(--blue); }
.flow-bar .fb-add:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); }
.flow-bar .fb-badge {
  min-width: 16px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}

/* 筛选 / 分组浮层：照参考图那张"面板"——标题 + 说明 + 一行一条条件 */
/* 拖宽/调宽时跟着鼠标的那条引导线（全站通用） */
.drag-guide {
  position: fixed;
  top: 0;
  width: 1px;
  background: var(--blue);
  box-shadow: 0 0 0 1px rgba(11, 107, 242, 0.25);
  pointer-events: none;
  z-index: 9999;
}

.fp-panel {
  position: absolute;
  z-index: 30;
  margin-top: 6px;
  width: min(860px, calc(100vw - 60px));
  padding: 14px 18px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.fp-panel[hidden] { display: none; }
.fp-head { display: flex; align-items: center; gap: 6px; }
.fp-head .grow { flex: 1; }
.fp-title { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 650; }
.fp-help { display: inline-flex; color: var(--ink-3); cursor: help; }
.fp-text {
  border: 0;
  background: transparent;
  padding: 2px 8px;
  border-radius: 7px;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
}
.fp-text:hover { background: var(--surface-2); }
.fp-lead {
  margin: 10px 0 4px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-2);
  color: var(--ink-2);
  font-size: 13px;
}
.fp-rows { padding: 2px 0; }
.fp-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
}
.fp-k { flex: none; width: 26px; color: var(--ink-2); font-size: 13px; white-space: nowrap; }
.fp-drag {
  flex: none;
  display: inline-flex;
  color: var(--ink-3);
  cursor: grab;
}
.fp-drag:active { cursor: grabbing; }
/* 字段 / 运算符：带前置图标的方框（和参考图一样） */
.fp-cell { display: inline-flex; align-items: center; position: relative; }
.fp-cell.fp-field { flex: 0 1 230px; min-width: 160px; }
.fp-cell.fp-op { flex: 0 0 132px; }
.fp-cell.fp-cell-value { flex: 1 1 auto; min-width: 200px; }
.fp-cell .fp-ico {
  position: absolute;
  left: 10px;
  display: inline-flex;
  color: var(--ink-3);
  pointer-events: none;
}
.fp-cell select,
.fp-cell input {
  width: 100%;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}
.fp-cell.fp-field select,
.fp-cell.fp-op select { padding: 0 26px 0 30px; }
.fp-cell select:hover,
.fp-cell input:hover { border-color: var(--ink-3); }
.fp-cell input { padding: 0 12px; }
/* 值：彩色标签（可多个，每个带 ×）+ 一个"再加一个"的下拉 */
.fp-chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  min-height: 34px;
  width: 100%;
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
}
.fp-chips:hover { border-color: var(--ink-3); }
.fp-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 3px 6px 3px 9px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
}
.fp-chip-t { overflow: hidden; text-overflow: ellipsis; }
.fp-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  opacity: 0.65;
}
.fp-chip-x:hover { opacity: 1; background: rgba(0, 0, 0, 0.1); }
.fp-chip.tone-green { background: var(--green-soft); color: var(--green); }
.fp-chip.tone-amber { background: var(--yellow-soft); color: var(--yellow); }
.fp-chip.tone-blue { background: var(--blue-soft); color: var(--blue); }
.fp-chip.tone-violet { background: var(--violet-soft); color: var(--violet); }
.fp-chip.tone-red { background: var(--red-soft); color: var(--red); }
.fp-chips-add {
  flex: 1 1 60px;
  min-width: 46px;
  height: 26px !important;
  border: 0 !important;
  border-radius: 7px !important;
  background: transparent !important;
  color: var(--ink-3);
  font-size: 12.5px;
  cursor: pointer;
}
.fp-chips-add:hover { background: var(--surface-2) !important; color: var(--ink); }
/* 顺序 / 逆序：灰底轨 + 当前那段白底蓝字（参考图那套分段控件） */
.fp-seg {
  display: inline-flex;
  flex: none;
  gap: 2px;
  padding: 2px;
  border-radius: 9px;
  background: var(--surface-2);
}
.fp-seg-btn {
  border: 0;
  border-radius: 7px;
  padding: 5px 11px;
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: 12.5px;
  white-space: nowrap;
}
.fp-seg-btn:hover { color: var(--ink); }
.fp-seg-btn.on { background: var(--surface); color: var(--blue); font-weight: 650; box-shadow: var(--shadow); }
.fp-del {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-3);
}
.fp-del:hover { color: var(--red); background: var(--red-soft); }
.fp-empty { padding: 12px 2px; color: var(--ink-3); font-size: 12.5px; }
.fp-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--line-2);
}
.fp-foot .grow { flex: 1; }
.fp-add {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
}
.fp-add:hover { border-color: var(--ink-3); background: var(--surface-2); }

/* 工具栏按钮选中态：照参考图是"浅底 + 深字"，不是高饱和蓝块 */
.flow-bar .fb-btn.on {
  border-color: var(--line);
  background: var(--surface-3);
  color: var(--ink);
  font-weight: 600;
}
.flow-bar .fb-btn.on .zm-i { color: var(--blue); }

/* 分组后的组头行：横跨整行，一行写清"哪一组、几条" */
.ft-group-row > td {
  padding: 4px 6px !important;
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.ftg-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 650;
}
.ftg-toggle:hover { background: var(--surface-3); }
.ftg-caret { display: inline-flex; transform: rotate(90deg); transition: transform var(--spring-snappy); }
.ftg-caret.closed { transform: rotate(0deg); }
.ftg-count {
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 16px;
}

/* 看板顶部的筛选条：一条就够，左搜索右排序，中间是条件 */
.flow-bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 10px;
  margin-bottom: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.flow-bar .fb-search {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-width: 210px;
  flex: 0 1 280px;
}
.flow-bar .fb-search-ico {
  position: absolute;
  left: 9px;
  display: inline-flex;
  color: var(--ink-3);
  pointer-events: none;
}
.flow-bar .fb-search input {
  width: 100%;
  padding: 6px 10px 6px 30px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
}
.flow-bar .fb-search input:focus { border-color: var(--blue); box-shadow: var(--glow); outline: none; }
.flow-bar .fb-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.flow-bar .fb-field { display: inline-flex; align-items: center; gap: 6px; }
.flow-bar .fb-k { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }
.flow-bar .fb-field select {
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.flow-bar .fb-field select:hover { border-color: var(--ink-3); }
.flow-bar .fb-star {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--ink-2);
  cursor: pointer;
}
.flow-bar .fb-star input { margin: 0; cursor: pointer; }
.flow-bar .fb-count { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* 一行说明（原来那一大段话收成一行，完整解释挂在 title 上） */
.flow-hint { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
.flow-nohit {
  margin: 10px 2px 0;
  padding: 14px;
  text-align: center;
  color: var(--ink-3);
  font-size: 13px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}
.flow-nohit[hidden] { display: none; }

/* 排序下拉：摆在提示条最上面一行，右边是说明文字 */
.flow-sort { display: inline-flex; align-items: center; gap: 6px; margin: 0 10px 6px 0; }
.flow-sort .fs-label { font-size: 12px; color: var(--ink-3); }
.flow-sort select {
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.flow-sort select:hover { border-color: var(--ink-3); }
.flow-hint b { color: var(--ink-2); font-weight: 600; }

/* ---------------------------------------------------------------------------
   项目页：填满视口 + 一个外框，看板在框里滚动
   —— 横向滚动条因此落在**框的底部**，而不是紧贴在看板内容下面。
   页面靠 ZM.page('project', { fill: true }) 打开 #app.page-fill。
   --------------------------------------------------------------------------- */
#app.page-fill { height: 100vh; overflow: hidden; }
#app.page-fill .main { min-height: 0; }
/* 填满视口的页面（项目页）不受 1180px 的版心限制 —— 用户要求"自动填充整个界面，右边不要空着" */
#app.page-fill .content {
  flex: 1;
  min-height: 0;
  max-width: none;
  display: flex;
  flex-direction: column;
  padding-bottom: 22px;
}
/* #content-slot 和 #content 是外壳里的两层容器 —— 必须把高度一路传下去，
   否则 .proj-layout 的 flex:1 落不到实处，看板就不会"填满框"，滚动条也就落不到框底。 */
#app.page-fill .content > #content-slot { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#app.page-fill #content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#app.page-fill .proj-layout { flex: 1; min-height: 0; align-items: stretch; }
#app.page-fill .proj-main { min-height: 0; display: flex; flex-direction: column; }
#app.page-fill .proj-side { max-height: 100%; }
#app.page-fill .proj-side-body { overflow: auto; }

/* 右侧项目区的外框 */
.proj-panel {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 12px 14px 4px;
}
.proj-panel > .proj-toolbar { flex: none; margin-bottom: 10px; }
.proj-panel > .flow-hint { flex: none; }
/* 卡片钉在左边，看板自己滚（两个方向）—— 行数可以无限多，滚动条都在框里 */
.proj-panel > .flow-pin { flex: 1; min-height: 0; }
.proj-panel .flow-board { flex: 1; min-height: 0; overflow: auto; }
/* 项目类型那页同样在框里滚动 */
.proj-panel > /* 「编辑流程」弹窗里的类型卡片：比列表页松一点，弹窗里只有一张 */
.type-dialog .type-card { margin: 0; }
.type-dialog .type-list { padding: 0; }

.type-list { flex: 1; min-height: 0; overflow: auto; }

/* 项目名称卡片：需求方点名要的"流程开头前"那张 */
.flow-board {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 14px;
  /* 同高：所有面板（名称卡片 + 每个环节列）都拉伸到最高的一块 —— 默认就是 stretch，
     这里写出来是为了别被后来的人改回 flex-start（那样又会高矮不齐）。 */
  align-items: stretch;
}

/* 流程环节列：列里每一行是一个项目（单行通告式）
   列本身是纵向 flex：列头固定，列身撑满 —— 这样所有列都和名称卡片一样高。 */
.flow-col {
  /* 可以**变宽**去填满框（环节少的时候右边不留空），但不会窄于 296px
     （环节多的时候宁可横向滚动，也不把列挤扁）。 */
  flex: 1 1 296px;
  min-width: 296px;
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 10px;
  min-height: 200px;
}
.flow-col-body { flex: 1; min-height: 0; }
.flow-col.current { box-shadow: inset 0 0 0 2px var(--blue-soft); }
.flow-col-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px 10px;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.12em;
  color: var(--ink-3);
}
.flow-col-head .fc-name { text-transform: none; letter-spacing: 0; font-size: 12.5px; color: var(--ink-2); }
.flow-col.current .fc-name { color: var(--blue); }
.fc-count { margin-left: auto; font-weight: 600; letter-spacing: 0; font-size: 11.5px; }

/* 一行 = 一个项目：图标 + 编号 + 名称（放不下省略号）+ 负责人 · 用时 */
.flow-row {
  display: flex;
  align-items: center;
  gap: 7px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  margin-bottom: 7px;
  color: var(--ink);
  text-decoration: none;
  font-size: 13px;
  line-height: 1.3;
}
.flow-row:hover { border-color: var(--line-hover); box-shadow: var(--shadow); text-decoration: none; }
.flow-row.active { border-color: var(--blue); background: var(--blue-soft); }
.fr-pic { flex: none; font-size: 13px; }
.fr-no { flex: none; font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); }
.fr-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 550;
}
.flow-row.active .fr-name { color: var(--blue); }
.fr-meta { flex: none; font-size: 11px; color: var(--ink-3); white-space: nowrap; }
/* 末尾那个"+ 改流程"按钮不是面板，别跟着拉伸 */
.flow-add { flex: 0 0 auto; align-self: flex-start; padding: 6px 2px; }
/* 空环节：占满整个列身并居中，看上去像"这里空着一个位置"，而不是孤零零一个小方块 */
.flow-col-body > .empty {
  height: 100%;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ------------------------------------------------------------ 项目类型（流程自定义） */

.type-list { display: flex; flex-direction: column; gap: 12px; }
.type-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; }
.type-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 12px; }
.type-head > div:first-child { flex: 1; min-width: 0; }
.type-name { font-size: 15px; font-weight: 650; letter-spacing: -0.01em; }
/* 环节卡片：一格一个环节（编号 + 名字 + 默认负责人），按住整张卡可以拖动换位置。
   动作（← → 删除）平时收起来，鼠标停在卡片上才出来 —— 不抢名字的位置。 */
.stage-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 8px;
}
.stage-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px 10px 10px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: grab;
  transition: box-shadow 0.12s, border-color 0.12s;
}
.stage-card:hover { border-color: var(--line-2); box-shadow: var(--shadow); }
.stage-card.dragging { opacity: 0.55; cursor: grabbing; box-shadow: var(--shadow-lg); }
body.stage-dragging { cursor: grabbing; user-select: none; }
.stage-card .sc-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
.stage-card .sc-no {
  flex: none;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  line-height: 15px;
  text-align: center;
}
.stage-card .sc-name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  cursor: text;
}
.stage-card .sc-name:hover { text-decoration: underline dotted; text-underline-offset: 3px; }
.stage-card .sc-name { cursor: pointer; }
.stage-card .sc-acts {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  align-items: center;
  gap: 4px;
  padding-left: 14px;
  background: linear-gradient(90deg, rgba(241, 242, 244, 0), var(--surface-2) 14px);
  opacity: 0;
  pointer-events: none;
}
.stage-card:hover .sc-acts { opacity: 1; pointer-events: auto; }
.stage-card .sc-btn {
  height: 20px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
}
.stage-card .sc-btn:hover:not([disabled]) { border-color: var(--ink-3); color: var(--ink); }
.stage-card .sc-btn[disabled] { opacity: 0.35; cursor: default; }
.stage-card .sc-btn.danger:hover { border-color: var(--red); color: var(--red); }
.stage-card .sc-who { display: flex; align-items: center; gap: 6px; min-width: 0; }
.stage-card .sc-who-k { flex: none; font-size: 11px; color: var(--ink-3); }
.stage-card .who-pick { position: relative; flex: 1 1 auto; min-width: 0; }
.stage-card .who-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-width: 0;
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}
.stage-card .who-btn:hover { border-color: var(--ink-3); }
.stage-card .who-btn[aria-expanded='true'] { border-color: var(--blue); box-shadow: 0 0 0 2px var(--blue-soft); }
.stage-card .who-now { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stage-card .who-caret { flex: none; color: var(--ink-3); font-size: 11px; }

/* 选人面板：fixed 挂 body（类型列表是滚动容器，挂在卡片里会被裁掉） */
.who-menu {
  position: fixed;
  z-index: 240;
  width: 264px;
  max-height: 320px;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.who-menu .who-search { padding: 8px 8px 6px; border-bottom: 1px solid var(--line-2); }
.who-menu .who-search input {
  width: 100%;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  font-size: 12.5px;
  color: var(--ink);
}
.who-menu .who-search input:focus { outline: none; border-color: var(--blue); background: var(--surface); }
.who-menu .who-body { overflow: auto; padding: 4px; }
.who-menu .who-list { display: flex; flex-direction: column; gap: 1px; }
.who-menu .who-opt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  padding: 5px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.who-menu .who-opt:hover { background: var(--surface-2); color: var(--ink); }
.who-menu .who-opt.on { background: var(--blue-soft); color: var(--blue); }
.who-menu .who-opt-name { font-size: 12.5px; font-weight: 550; }
.who-menu .who-opt-sub { font-size: 11px; color: var(--ink-3); }
.who-menu .who-empty { padding: 14px 8px; text-align: center; color: var(--ink-3); font-size: 12px; }
/* 「＋ 加环节」占最后一格，虚线框 */
.stage-add {
  min-height: 62px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--blue);
  font-size: 12.5px;
  cursor: pointer;
}
.stage-add:hover { border-color: var(--blue); background: var(--blue-soft); }
.type-add {
  align-self: flex-start;
  border: 1px dashed var(--line);
  background: var(--surface);
  color: var(--ink-2);
  border-radius: var(--radius);
  padding: 9px 14px;
  cursor: pointer;
}

@media (max-width: 1080px) {
  .quad-grid { grid-template-columns: 1fr; }
  .proj-resizer { display: none; }
  .ft thead th.ft-card-resizer { display: none; }
  .ft .ft-card-gap { display: none; }
  .proj-side:not(.is-collapsed) { width: min(var(--side-w, 244px), 42vw); min-height: 0; position: static; }
}

/* ------------------------------------------------------------ 项目：项目卡列 + 流程矩阵
 *
 * 项目卡是矩阵的**第一列**（不是钉在滚动容器外面的面板）：卡片和它右边那行的环节格子
 * 同属一个 <tr>，所以「第 N 张卡 = 第 N 行的项目」由结构保证，横着滚也不会错位。 */
.flow-pin { display: flex; gap: 12px; align-items: stretch; }
.flow-pin > .flow-table-wrap { flex: 1 1 auto; min-width: 0; }

/* 卡片上「当前环节」那行小字（进度条下方） */
.fl-stage { font-size: 12px; color: var(--ink-2); margin-top: 6px; }
.fl-stage b { color: var(--brand); font-weight: 650; }

/* 看板一行：行本身可点（<a class="flow-row">），「推进」按钮是它的**兄弟节点** ——
   物理上不在 <a> 里，所以点按钮绝不会顺带触发行的跳转。 */
.flow-item { display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
.flow-item > .flow-row { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }

/* ---------------------------------------------------------------------------
   流程矩阵：**一行 = 一个子项目的整个流程**，从左到右一步步推进。
   行 = 子项目，列 = 环节；✓ 已过 / ● 当前 / ○ 还没到；每格写的是这一步**实际谁办**。
   项目往前走不靠按钮，而是靠「环节负责人办完他那条待办」；操作格里只有项目负责人的「回退」。
   （原来做成"按环节分列的看板"，方向错了 —— 看板把人拆散，矩阵才看得出单个项目的进度。）
   --------------------------------------------------------------------------- */
.flow-table-wrap { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: auto; }
/* table-layout: fixed —— 列宽**按声明来，不再按容器挤**。
   原来 auto 布局下，窗口一窄就把每一列都压扁：项目卡列被压到 200 以内，
   名字/编号/交期全被截成「笨小孩-成品-桑濑制药克优…」「交付 0…」。
   现在宁可横向滚（左列是 sticky 的，滚动时项目卡一直在），也不把字截掉。 */
.ft { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.ft thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  text-align: center;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.08em;
  color: var(--ink-3);
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.ft thead th.ft-card-col { text-align: left; vertical-align: bottom; }
/* 项目卡列：宽度只认 --card-w（页面 / 表格上设的那一个变量），表头和每一行的格子都跟着它走。
   max-width 一起写，是为了表格自动布局下不会把富余宽度摊到这一列上 —— 拖多宽就多宽。 */
.ft .ft-card-col {
  width: var(--card-w, 252px);
  min-width: var(--card-w, 252px);
  max-width: var(--card-w, 252px);
  vertical-align: top;
}
/* 表头里那条**调宽把手**（和左栏 .proj-resizer 同一套：细线，悬停才变蓝）。
   它和下面每行的空占位格（.ft-card-gap）共用同一列，所以把手正好落在项目卡列右边缘上。 */
.ft thead th.ft-card-resizer { width: 10px; padding: 0; cursor: col-resize; position: sticky; top: 0; }
.ft thead th.ft-card-resizer::after {
  content: '';
  display: block;
  width: 2px;
  height: 20px;
  margin: 0 auto;
  border-radius: 2px;
  background: var(--line);
  transition: background 0.12s ease;
}
.ft thead th.ft-card-resizer:hover::after,
body.resizing .ft thead th.ft-card-resizer::after { background: var(--blue); }
.ft .ft-card-gap { width: 10px; padding: 0; }
/* 环节列 168px：内侧 150px 正好放得下「徽标 + 四象限」并排（最长那个词是「不重要不紧急」）。
   132px 那档塞不下，只能把字压到 10.5px。 */
.ft .ft-stage-col { width: 168px; padding: 0 4px; }

/* 表头 = 和下面流程卡同一套：一张小卡（序号块 + 环节名），圆角/描边/底色跟 .ft-step 对齐。
   表头钉在顶部（横向滚环节时也在），底下一道细线把表头和行分开。 */
.ft thead th { padding: 6px 4px 8px; border-bottom: 1px solid var(--line); }
.ft thead .th-in {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* 「项目卡」这一格：和环节表头是同一张卡，只是内容换成 图标 + 名字 + 项目数 */
.ft thead .th-in.th-in-card {
  justify-content: flex-start;
  gap: 7px;
  color: var(--ink-2);
}
.ft thead .th-ico { flex: none; display: inline-flex; color: var(--ink-3); }
.ft thead .th-count {
  flex: none;
  margin-left: auto;
  min-width: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.ft thead .th-no {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 6px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}
.ft thead .th-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* 表头钉住：往下滚行的时候列名一直在 */
.ft thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); }
.ft tbody td { padding: 5px 8px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.ft tbody tr:last-child td { border-bottom: 0; }
.ft-row:hover { background: var(--row-hover); }
.ft-row.active { background: var(--blue-soft); }

/* ---- 项目卡（矩阵第一列，一行一张）----
   版式：56px 缩略图（悬停放大）· 名字 + 「编号 / 当前环节」一行 ·
   按环节数分段的进度 · 2×2 的四格信息（业务员/跟单员/开单/交付）。 */
.ft-card { display: block; color: var(--ink); text-decoration: none; }
.ft-card:hover { text-decoration: none; }

.pc-top { display: flex; align-items: flex-start; gap: 10px; }
.pc-pic {
  flex: none;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.pc-pic.is-img { cursor: zoom-in; }
.pc-pic img { width: 100%; height: 100%; object-fit: contain; }
.pc-emoji { font-size: 28px; line-height: 1; }
/* 加过星标的项目：名字前面一颗金星 */
.pc-star { color: var(--star-gold); font-size: 12px; }
.pc-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* 标题**固定两行**：一行还是两行，下面的编号行/进度/信息/时间都在同一条线上 */
.pc-name {
  font-size: 13px;
  font-weight: 650;
  line-height: 1.32;
  height: 2.64em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pc-sub { display: flex; align-items: center; gap: 6px; min-width: 0; height: 20px; }
.pc-no {
  flex: none;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-2);
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-2);
}
.pc-stage { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--ink-3); }

/* 进度：一个环节一段 —— 段数跟着流程走，不是一根写死的条 */
.pc-track { display: flex; gap: 3px; margin-top: 9px; }
.pc-seg { flex: 1 1 0; min-width: 6px; height: 5px; border-radius: 999px; background: var(--surface-3); }
.pc-seg.done { background: var(--blue); }
/* 当前这一步：浅蓝 + 蓝描边 —— 还没办完，不能看着像实心已过 */
.pc-seg.cur { background: var(--blue-soft); box-shadow: inset 0 0 0 1.5px var(--blue); }
.pc-prog {
  display: block;
  margin-top: 5px;
  font-size: 10.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* 时间行：开单 → 交付 + 还剩几天（和工作台卡片同一口径：黄=快到期，红=逾期） */
.pc-time {
  display: flex;
  align-items: baseline;
  gap: 8px;
  height: 16px;
  margin-top: 7px;
  font-size: 11px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.pc-time .pc-time-when { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-time .pc-time-left { flex: none; margin-left: auto; font-weight: 650; color: var(--ink-2); }
.pc-time.yellow { color: var(--due-warn); }
.pc-time.yellow .pc-time-left { color: var(--due-warn); }
.pc-time.red { color: var(--red); }
.pc-time.red .pc-time-left { color: var(--red); }
.pc-time.none { color: var(--ink-3); }

/* 信息三格：业务员 / 跟单员 / 客户（客户那格宽一点，客户名一般更长） */
.pc-meta { display: grid; grid-template-columns: 1fr 1fr 1.5fr; gap: 5px 10px; margin-top: 9px; min-height: 30px; }
.pc-cell { display: flex; flex-direction: column; min-width: 0; }
.pc-cell .k { font-size: 10px; color: var(--ink-3); }
.pc-cell .v { font-size: 11.5px; font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 关联订单号独占一行（用户 2026-09-17：「项目卡里面需要加入对应订单号」）：
   单号像 DD-260918002 / XS-DEMO-WORK-86636 都挺长，挤在 1fr 那格会被截成「XS-DE…」，
   等于没写。整张卡都是"看订单情况"的点击区，所以这行做成蓝色、悬停划一条线示意能点。 */
.pc-cell-order { grid-column: 1 / -1; }
.pc-order { color: var(--blue); }
.ft-card:hover .pc-order { text-decoration: underline; }

/* 「订单情况」弹窗（点项目卡开）：一排事实 + 一张只读明细表 */
.po-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 9px 14px;
  margin-bottom: 12px;
}
.po-fact { display: flex; flex-direction: column; min-width: 0; }
.po-fact .k { font-size: 10.5px; color: var(--ink-3); margin-bottom: 2px; }
.po-fact .v { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.po-fact .v.ok { color: var(--green); }
.po-fact .v.warn { color: var(--red); }
.po-lines th { font-size: 11.5px; }
.po-lines td { font-size: 12.5px; }
.po-lines tr.is-mine td { background: var(--blue-soft); }
.po-lines .po-mine {
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  font-size: 10px;
  font-weight: 650;
}
.po-projects { margin-top: 10px; font-size: 12px; color: var(--ink-3); }
.po-proj { margin-right: 8px; }
.po-proj.is-mine { color: var(--blue); font-weight: 650; }
/* 明细那格的商品图（用户 2026-09-18：「需要加入图片」）：
   缩略图 34px 和平台详情面板的明细表一个尺寸（.dp-items-table .pt-thumb），
   名字/规格在旁边竖排，图悬停放大（bindPicZoom 绑在弹窗 body 上）。 */
.po-item { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.po-item-text { display: flex; flex-direction: column; min-width: 0; }
.po-lines .pt-thumb { width: 34px; height: 34px; flex: 0 0 auto; }
.po-lines td { vertical-align: middle; }

/* 悬停放大：表格是滚动容器，绝对定位会被裁，所以这张大图挂 body + fixed */
.pic-zoom {
  position: fixed;
  z-index: 260;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
/* 悬停预览：**按比例看整张**（原来 220x220 + cover 会把宽的包装稿裁得只剩中间一块）。
   同样不放大：图多宽就显示多宽，最多 260px。 */
/* 选商品 / 选子件列表里的缩略图（2026-09-18：用户口径「选择商品时需要加入图片，
   而且还能鼠标放上去扩大看」）。复用 .pt-thumb 那套，只是小一号 —— 列表项本来就窄。 */
.prp-item .prp-thumb { width: 26px; height: 26px; border-radius: 6px; flex: 0 0 auto; }
.prp-item .prp-thumb img { width: 100%; height: 100%; object-fit: contain; }
.prp-item .prp-thumb.is-empty { color: var(--ink-3); border-style: dashed; }

/* ---------------------------------------------------------------- 选择用料（多选弹窗，2026-09-19）
   用户口径：商品BOM / 料体配方的「添加一行用料」换成「选择用料（子件）」，
   点它开一个能**一次勾多个**的弹窗（排版照用户给的图 2：筛子 + 表格 + 分页 + 已选计数），
   但配色字号全用工作台自己的令牌，不照搬那套蓝。 */
.mpk-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.mpk-f { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; flex: none; }
/* 「分类」那格别再被挤成"全"（下拉至少要放得下两个字 + 箭头） */
.mpk-f select { min-width: 104px; }
.mpk-f .pk { color: var(--ink-3); font-size: 12px; }
.mpk-f-search {
  flex: 0 1 300px;
  min-width: 220px;
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.mpk-f-search input { flex: 1; min-width: 0; border: 0; background: transparent; font-size: 12.5px; }
/* 「筛选」那颗：本地的即时筛（不查服务端），只在搜索框有字时可用 —— 别做成一进弹窗就能点的死按钮。
   用户口径 2026-09-19：「打了字看不出来在筛」→ 给它一个明确的落点（回车 / 点它）。 */
.mpk-f-search [data-mpk-search-go] { flex: none; }
.mpk-f-search [data-mpk-search-go]:disabled { opacity: 0.5; cursor: default; }
.mpk-kinds { display: inline-flex; gap: 6px; }

/* ★ AI 推荐「正在检索」的可见反馈（用户口径 2026-09-19：
   「点 ai 推荐后，界面并看不出来正在检索，以为是卡了或者 ai 推荐失效」）——
   推理模型一次要 2–5s，只把按钮文案换成"AI 推荐中…"太轻（在工具条里，眼睛不在那儿）。
   所以：① 按钮加一颗转圈；② 工具条下面压一条会动的进度条 + 一行明确的话。 */
.mpk-ai-btn { position: relative; }
.mpk-ai-btn.is-busy { padding-left: 24px; }
.mpk-ai-btn.is-busy::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 50%;
  width: 11px;
  height: 11px;
  margin-top: -5.5px;
  border: 2px solid var(--violet-soft);
  border-top-color: var(--violet);
  border-radius: 50%;
  animation: mpk-spin 0.7s linear infinite;
}
@keyframes mpk-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .mpk-ai-btn.is-busy::before { animation-duration: 2.4s; }
  .mpk-ai-run::after { animation: none; }
}
.mpk-ai-run { margin: 0 0 8px; }
.mpk-ai-run .t {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  color: var(--violet);
  font-weight: 600;
}
.mpk-ai-run .s { display: block; margin-top: 2px; font-size: 11.5px; color: var(--ink-3); }
/* 一条**不确定宽度**的进度条：模型要跑几秒，进度给不了真的，但"在动"必须看得出来 */
.mpk-ai-run .bar {
  position: relative;
  height: 3px;
  margin-top: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--violet-soft);
}
.mpk-ai-run .bar::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 34%;
  border-radius: 3px;
  background: var(--violet);
  animation: mpk-run 1.15s ease-in-out infinite;
}
@keyframes mpk-run {
  0% { left: -34%; }
  100% { left: 100%; }
}
.mpk-wrap { max-height: 52vh; overflow: auto; border: 1px solid var(--line); border-radius: var(--radius); }
.mpk-table { width: 100%; }
.mpk-table thead th { position: sticky; top: 0; background: var(--surface-2); z-index: 1; }
.mpk-table tbody tr { cursor: pointer; }
.mpk-table tbody tr:hover { background: var(--surface-2); }
.mpk-table tbody tr.on { background: var(--blue-soft); }
.mpk-thumb { width: 28px; height: 28px; border-radius: 6px; }
.mpk-thumb img { width: 100%; height: 100%; object-fit: contain; }
.mpk-foot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
/* 「共 N 条」：原来挤在工具条最右，现在跟在结果区顶上（工具条不再重画，它跟着列表走） */
.mpk-count { text-align: right; margin-bottom: 6px; }

/* ---------------------------------------------------------------- BOM 表单版式（2026-09-19 重做）
   用户口径：「整体的 ui 重新设计，太太太太丑了」。原来三格竖着堆（版本 / 备注 / 母件），
   中间一大片空白，动作按钮挤在表脚下一条长提示里。现在改成**两张卡**：
     ① 基本信息：版本 + 母件**并排**、备注整行；
     ② 用料：动作按钮挪到**卡片头右侧**，表格本身收紧。
   全部用工作台自己的令牌（`var(--…)`），换主题不破相。 */
.bom-form { display: flex; flex-direction: column; gap: 14px; }
/* ⚠️ 类名别叫 .bom-card —— 那个名字**已经被 BOM 卡片列表占了**（下面的 .bom-card 是列表卡片，
   带 display:flex），撞上去这张表单就被排成一行（踩过）。 */
.bom-sec {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 12px 14px 13px;
}
.bom-sec-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.bom-sec-head .eyebrow { margin: 0; }
.bom-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.bom-f { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.bom-f-full { grid-column: 1 / -1; }
.bom-f .bk { font-size: 12px; color: var(--ink-3); }
.bom-f input {
  width: 100%;
  height: 32px;
  padding: 0 10px;
  font-size: 12.5px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
}
.bom-f input:focus { border-color: var(--ink-3); outline: none; }
.bom-form .hint { margin-top: 9px; font-size: 11.5px; }
/* 配方行的三个小动作（↑ ↓ ×） */
.fs-row-acts { white-space: nowrap; text-align: right; }
.fs-row-acts .fs-del { margin-left: 2px; }
.fs-row-acts .fs-del[disabled] { opacity: 0.28; cursor: default; }

/* 选子件弹窗顶部的**母件横幅**（用户口径 2026-09-19：「上面要有母件图片 名字，
   要不进去选子件没参考不知道选什么了」）——浅底一条，图 + 编号 + 名字，右边一句提示。 */
.mpk-parent {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.mpk-parent-pic { width: 40px; height: 40px; border-radius: 8px; flex: 0 0 auto; }
.mpk-parent-pic img { width: 100%; height: 100%; object-fit: contain; }
.mpk-parent-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mpk-parent-text .bk { font-size: 11px; color: var(--ink-3); }
.mpk-parent-text b { font-size: 13px; }
.mpk-parent-text .mono { font-size: 11.5px; }
.mpk-parent-tip { margin-left: auto; flex: none; }
/* 窄屏（手机 / 抽屉布局）：横幅右侧那句提示没有存在价值，却会把母件名字挤成一字一行
   —— 直接藏掉；母件的图 + 名字 + 编号才是这一栏的意义（2026-09-19 实测 <430px）。 */
@media (max-width: 560px) {
  .mpk-parent-tip { display: none; }
}

/* 「同款」那一行（和母件名字对得上的盒子 / 标签 / 料体）——浅底 + 左边一道蓝，
   排序上也被排到最前（见 prodRelatedTo）。 */
.mpk-table tbody tr.is-related { background: var(--blue-soft); }
.mpk-table tbody tr.is-related td:first-child { box-shadow: inset 3px 0 0 var(--blue); }
.mpk-table tbody tr.is-related.on { background: var(--blue-soft); }
.tag.mpk-rel { margin-right: 6px; flex: none; }

/* ★ 2026-09-19：「AI 推荐」（用户口径「用 ai 模型给我推荐相关**瓶子、料体**之类的子件」）。
   标记用**紫**（`--violet`），和「同款」那枚蓝的（`.tag.info` / `.mpk-rel`）**明确区分开** ——
   同一行可以两枚并存：紫的是模型推的，蓝的是"名字对得上"。 */
.tag.mpk-ai { background: var(--violet-soft); color: var(--violet); margin-right: 6px; flex: none; }
/* ★ 「用过」：**学自历史 BOM**（这个母件以前真用过它）—— 绿标，和「AI 推荐」的紫、
   「同款」的蓝都分开。用户口径：「还要不断学习后续建立的商品bom」。
   同一行可以三枚并存：绿(用过) + 紫(模型推的) + 蓝(名字对得上)。 */
.tag.mpk-used { background: var(--green-soft); color: var(--green); margin-right: 6px; flex: none; }
/* AI 那几行排最前 + 紫底 + 左边一道紫（`.is-ai.is-related` 也走紫：AI 优先） */
.mpk-table tbody tr.is-ai,
.mpk-table tbody tr.is-ai.is-related { background: var(--violet-soft); }
.mpk-table tbody tr.is-ai td:first-child { box-shadow: inset 3px 0 0 var(--violet); }
.mpk-table tbody tr.is-ai.on { background: var(--violet-soft); }
/* AI 给的那句理由（小字，挂在名称下面） */
.mpk-why { margin-top: 2px; font-size: 11.5px; line-height: 1.45; color: var(--violet); }
/* 面板上那行"这次到底是不是 AI 排的"（硬规矩：不许假装是 AI 答的） */
.mpk-ai-note { margin: 0 0 8px; font-size: 12px; color: var(--ink-3); }
.mpk-ai-note.is-ok { color: var(--violet); }
/* （`.mpk-ai-btn` 的主体样式在上面、和「正在检索」那组放一起） */
.mpk-ai-btn { flex: none; }

/* 子件那格的小缩略图（用户口径 2026-09-19：「这里显示**图片 名称**就行」）
   ⚠️ 选择器要带上父级：`.pt-thumb`（40px）在文件后面才定义，同权重会把它压回去 ——
   2026-09-19 用户截图里"图框撑大、款号被压住"就是这个顺序坑。 */
.bom-pick-thumb { width: 24px; height: 24px; border-radius: 5px; flex: 0 0 auto; }
.bom-pick .bom-pick-thumb,
.bom-items .bom-pick-thumb { width: 24px; height: 24px; border-radius: 5px; flex: 0 0 auto; }
.bom-pick-thumb img { width: 100%; height: 100%; object-fit: cover; }
.bom-pick-thumb.is-empty { border-style: dashed; }
/* 详情弹窗：图居中一点（别贴着左边框），也放大到 32px —— 详情里空间够，24px 认不出瓶子 */
.bom-items td .bom-pick-thumb { margin: 0 auto; }
.bom-items .bom-pick-thumb { width: 32px; height: 32px; border-radius: 6px; }

/* 一行用料都还没选时的"空白 + 提示"（用户口径：没点「选择用料（子件）」之前这里该是空的） */
.bom-empty {
  padding: 18px 14px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  text-align: center;
  font-size: 12.5px;
}
.bom-empty b { color: var(--ink); }

/* 用料表：头更轻、行更紧（只作用在 BOM 编辑器里，别影响别的 items-table） */
.bom-editor .items-table thead th {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
  padding-bottom: 5px;
}
.bom-editor .items-table tbody td { padding: 5px 6px; }
.bom-editor .items-table input { height: 30px; }
@media (max-width: 640px) {
  .bom-grid { grid-template-columns: 1fr; }
}

/* 用料行那一格的三个小动作：↑ ↓ ✕（2026-09-18 加了调顺序） */
.bom-row-acts { white-space: nowrap; text-align: right; }
.bom-row-acts .ghost { padding: 1px 5px; line-height: 1.4; font-size: 12px; }
.bom-row-acts .ghost[disabled] { opacity: 0.28; cursor: default; }

.pic-zoom img {
  display: block;
  width: auto;
  height: auto;
  max-width: 260px;
  max-height: 220px;
  object-fit: contain;
  border-radius: var(--radius-sm);
}

/* 双击缩略图打开的大图（弹窗里）；图片本身可以右键「拷贝图像」 */
.pic-viewer { display: flex; justify-content: center; padding: 4px 0 8px; }
.pic-viewer img {
  display: block;
  max-width: min(76vw, 680px);
  max-height: 66vh;
  object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.ft-row.active .ft-card-col { box-shadow: inset 3px 0 0 var(--blue); }
.ft-card.active .pc-name { color: var(--blue); }
.ft-cell { text-align: center; }
/* 每一步自己也是一张"卡"（不是光秃秃一个点）——
   因为每个环节以后要挂人（审核员 / 设计员），卡片里得写得出是谁。
   高度压到 46px：一行卡的信息量没变，但一屏能多放几个项目。 */
.ft .ft-cell { padding: 4px 5px; }
/* 环节卡：**所有状态共用一套版式**（白底 / 同样圆角内边距 / 同样高度），
   是"待办中"还是"已完成"只看边色 + 圆点 + 徽标，不再整张卡换个样子。
   一张卡上**只有一个颜色来源 = 状态**：左边那条杠、徽标、描边跟着 status 一起变；
   项目停在哪一步（.is-cur）不另塞一种颜色进去 —— 只是把状态色"加重一档"（见下面 .is-cur）。

   版式 = **三行**（2026-09-15 从四行收回来的）：
     ① 勾选 + 状态点 + 负责人 + 「催」   ← 谁办
     ② 状态徽标 + 四象限                 ← 并排两件小控件
     ③ 催办 + 回退                       ← 各占一半
   有登记数据时中间插一行摘要（.fs-data）。四行那版每格多占 ~60px，一屏少看两个项目。 */
.ft-step {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-height: 84px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 7px 8px;
  text-align: left;
}
.ft-step .fs-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ft-step .fs-pick { display: inline-flex; align-items: center; gap: 5px; flex: none; cursor: pointer; }
.ft-step .fs-pick input { width: 13px; height: 13px; margin: 0; cursor: pointer; }
.ft-step .fs-who {
  flex: 1 1 auto;
  min-width: 0;
  padding: 1px 5px;
  border: 1px dashed transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.ft-step .fs-who:hover { border-color: var(--line); background: var(--surface); }
.ft-step .fs-hot {
  flex: none;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-size: 10.5px;
  font-weight: 650;
}
/* 第二行：状态徽标 + 四象限并排（徽标按文字宽、四象限吃掉剩下的）。
   状态文字不带符号 —— ◐/●/✓/○ 那个符号在左边的圆点上已经有了，重复一遍只是白占宽度。 */
.ft-step .fs-mid { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ft-step .fs-badge {
  flex: 0 1 auto;
  padding: 4px 7px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-3);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  text-align: center;
}
/* ---- 状态配色：灰(还没派) / 琥珀(待办中·等对方) / 蓝实心(进行中·正在做) / 绿(已完成) ----
   用户口径：几个状态的区别要**一眼看得出来**。所以不只是换个字，
   整张卡都跟着变：左边一条状态色条 + 状态条本身的底色 + 卡片的淡底。 */
.ft-step .fs-badge.todo {
  border-color: var(--step-line-todo);
  background: var(--yellow-soft);
  color: var(--yellow);
  font-weight: 700;
}
.ft-step .fs-badge.done { border-color: var(--step-line-done); background: var(--green-soft); color: var(--green); font-weight: 700; }
/* 这一步登记了什么（审核结论 / 采购单号 / 到货日…）。
   看板上**只展示**：真正的动作在负责人自己的工作台里做，格子上不放按钮。 */
.ft-step .fs-data {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 10.5px;
  font-weight: 500;
  color: var(--ink-3);
}
.ft-step .fs-badge.running {
  border-color: var(--blue);
  background: var(--blue);
  color: #fff;
  font-weight: 700;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.ft-step .fs-badge.none { border-style: dashed; color: var(--ink-3); background: transparent; }

/* 卡片左边一条状态色条（4px），整行扫过去就是这个颜色 */
.ft-step { position: relative; overflow: hidden; }
.ft-step::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--line);
}
.ft-step.st-none::before { background: var(--surface-3); }
.ft-step.st-todo::before { background: var(--yellow); }
.ft-step.st-running::before { background: var(--blue); }
.ft-step.st-done::before { background: var(--green); }
/* 卡片底色也轻轻带一点：进行中偏蓝、待办中偏暖、未开始发灰、已完成偏绿。
   描边这一层**只由状态决定**（形状 = 浅色描边；"当前"那一步加重成实边，见 .is-cur），
   「催」和"已走过"都不许再来抢 border-color —— 三个规则抢一个属性，
   结果就是"进行中 + 催"变成粉边、和蓝杠蓝徽标打架（2026-09-15 用户指出的色块问题）。 */
.ft-step.st-running { border-color: var(--step-line-running); background: var(--blue-soft); }
.ft-step.st-todo { border-color: var(--step-line-todo); background: var(--surface); }
.ft-step.st-done { border-color: var(--step-line-done); background: var(--surface); }
.ft-step.st-none { border-style: dashed; background: var(--surface-2); }
.ft-step.st-none .fs-who,
.ft-step.st-none .fs-quad { opacity: 0.75; }
/* 圆点/负责人文字也跟着状态上色 */
.ft-step.st-todo .ft-dot.todo { background: var(--yellow); color: #fff; }
.ft-step.st-todo .fs-who { color: var(--ink); font-weight: 600; }
.ft-step.st-running .ft-dot.running {
  background: linear-gradient(90deg, var(--blue) 0 50%, transparent 50% 100%);
  border: 1.5px solid var(--blue);
}
.ft-step.st-running .fs-who { color: var(--blue); font-weight: 700; }
.ft-step.st-done .fs-who { color: var(--ink-3); }
.ft-step.st-done .ft-dot.done { color: var(--green); font-weight: 700; }
/* 第三行：催办 / 回退 各占一半（原来这俩还和四象限挤一行）。 */
.ft-step .fs-bottom {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.ft-step .fs-quad {
  flex: 1 1 auto;
  min-width: 0;
  padding: 4px 5px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.ft-step .fs-quad:hover { border-color: var(--ink-3); }
.ft-step .fs-nudge {
  flex: 1 1 0;
  padding: 4px 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 11px;
  text-align: center;
  cursor: pointer;
}
.ft-step .fs-nudge:hover { border-color: var(--red); color: var(--red); background: var(--red-soft); }
.ft-step .fs-back {
  flex: 1 1 0;
  padding: 4px 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 11px;
  text-align: center;
  cursor: pointer;
}
.ft-step .fs-back:hover { border-color: var(--ink-3); color: var(--ink); background: var(--surface-2); }
/* 被催过的这一步：提醒只挂在右上角那枚红「催」上，**不再改描边** ——
   描边已经用来表示状态了，再抢一次，同一张卡上就出现两种颜色（粉边 + 蓝杠）。 */

/* 批量选择条：勾了步骤才出现 */
.flow-picks {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 6px 0 8px;
  padding: 6px 10px;
  border: 1px solid var(--blue);
  border-radius: var(--radius);
  background: var(--blue-soft);
  font-size: 12px;
  color: var(--blue);
}
.flow-picks[hidden] { display: none; }
.flow-picks .fp-count b { font-variant-numeric: tabular-nums; }
/* 已走过：只把圆点点成绿的 ✓，描边交给 .st-done（同一个绿，不再各写一条） */
.ft-step.is-done .ft-dot { color: var(--green); font-weight: 700; }
/* 当前环节 = 项目停在这一步。
   原来是一圈固定蓝 + 一条 inset 蓝杠：于是"待办中"的当前步成了琥珀配蓝边、
   左边还并排两条杠（状态杠 + 蓝杠），看着就像画错了。现在改成**把状态色加重一档**：
     · 描边从浅色描边（--step-line-*）升成状态色实边（--yellow / --blue / --green）
     · 左边那条状态杠从 4px 加粗到 6px
   颜色还是这一步自己的颜色，位置靠"加重"看得出来，不再多塞一种色。 */
.ft-step.is-cur { border-color: var(--ink-3); }
.ft-step.is-cur.st-todo { border-color: var(--yellow); }
.ft-step.is-cur.st-running { border-color: var(--blue); }
.ft-step.is-cur.st-done { border-color: var(--green); }
.ft-step.is-cur.st-none { border-color: var(--ink-3); }
.ft-step.is-cur::before { width: 6px; }
/* 还没到：最轻的一档 —— 灰点灰字 */
/* 还没到的：白底一样，只是圆点灰、徽标写「未开始」 */
.ft-dot {
  display: inline-block;
  width: 15px;
  height: 15px;
  line-height: 15px;
  border-radius: 50%;
  font-size: 9.5px;
  color: var(--ink-3);
}
/* 圆点按**状态**上色：待办中 = 实心蓝 ●、已完成 = 绿 ✓、还没派 = 空心灰 ○ */
.ft-step .ft-dot.todo { background: var(--blue); color: #fff; }
/* 进行中：半填充的圆（左半边实心），一眼分得出"开工了"和"只是有待办" */
.ft-step .ft-dot.running {
  background: linear-gradient(90deg, var(--blue) 0 50%, transparent 50% 100%);
  border: 1.5px solid var(--blue);
  color: transparent;
  box-sizing: border-box;
}
.ft-step .ft-dot.done { background: transparent; color: var(--green); font-weight: 700; }
.ft-step .ft-dot.none { background: transparent; }
.ft-step .who {
  font-size: 11px;
  line-height: 1.25;
  color: var(--ink-2);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ft-step.is-done .who { color: var(--ink-3); }
.ft-step.is-cur .who { color: var(--blue); font-weight: 650; }
/* 还没到的环节，人名本来就"还没轮到他"，压暗一档 */
.ft-step:not(.is-done):not(.is-cur) .who { color: var(--ink-3); }

/* 项目卡右键菜单（编辑 / 回退 / 删除）—— 行上不再挂按钮，动作都收在这里 */
.card-menu {
  /* 右键菜单：半透明 + 模糊，像贴在内容上的一层玻璃 */
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  position: fixed;
  z-index: 240;
  min-width: 168px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
/* 右键菜单里的分隔线 */
.cm-sep { height: 1px; margin: 4px 2px; background: var(--line-2); }

.card-menu .cm-title {
  padding: 4px 8px 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line-2);
  color: var(--ink-3);
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-menu .cm-item {
  display: block;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.card-menu .cm-item:hover { background: var(--surface-2); color: var(--ink); }
.card-menu .cm-item.danger:hover { background: var(--red-soft); color: var(--red); }

/* 卡片列钉在左边：横向滚环节时行标签不会跑掉。
   没有这一条的话，往右滚看后面的环节，卡片就跟着滚走了 ——
   而卡片现在已经顶替了被删掉的「子项目」列，是这一行的身份。 */
.ft .ft-card-col {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--surface);
}
.ft thead th.ft-card-col { z-index: 3; background: var(--surface); }
.ft tbody tr:hover .ft-card-col { background: var(--row-hover); }
.ft-row.active .ft-card-col { background: var(--blue-soft); }

/* 窄屏（手机也在这条链路里）：环节列收一档、字号收半档 ——
   但**结构不变**（四行还是四行），不然一窄就退回"三样控件挤一行"那种看不清的版式。
   注意：这条必须放在上面那些基础规则**之后** —— 同一份选择器、同样的优先级，
   写在前面（1080 那条老规则的位置）会被后面的 176px 直接盖掉，等于没写。 */
@media (max-width: 1080px) {
  .ft .ft-stage-col { width: 146px; }
  .ft-step { min-height: 76px; padding: 6px 7px; gap: 4px; }
  .ft-step .fs-who { font-size: 12px; }
  .ft-step .fs-badge { font-size: 11px; padding: 3px 6px; }
  .ft-step .fs-quad,
  .ft-step .fs-nudge,
  .ft-step .fs-back { font-size: 10.5px; }
}

/* ============================== 项目板块 CSS 结束 ============================== */

/* ============================================================================
   风格（主题）：选择框在顶栏，选完打在 <html data-theme="…"> 上。
   每套只是换一组变量 + 一张底色 —— 版式不动，所以换风格不会把页面换乱。
   ============================================================================ */

/* 清爽（默认）：就是上面 :root 那套，只把卡片阴影和圆角收得更"现代"一点 */
:root {
  --bg-image: none;
  --card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 8px 24px -18px rgba(16, 24, 40, 0.18);
}

/* 暖阳：暖色渐变底 + 奶油卡片 + 橙色主色（参考那种"早上好"的画风） */
/* 琥珀：暖色版本，但骨架和默认一样（网格 + 玻璃 + 冷光替换成暖光） */
[data-theme='warm'] {
  --bg: #faf6f1;
  --grid-color: rgba(120, 72, 30, 0.05);
  /* 毛玻璃：暖色光场（琥珀 + 玫瑰金），和这套的橙主色同一个家 */
  --aurora:
    radial-gradient(900px 640px at -4% 62%, rgba(234, 115, 23, 0.3), transparent 60%),
    radial-gradient(820px 600px at 106% 60%, rgba(196, 66, 12, 0.24), transparent 58%),
    radial-gradient(1000px 700px at 40% 104%, rgba(161, 98, 7, 0.26), transparent 60%);
  --glass-bg: rgba(255, 253, 250, 0.6);
  --glass-bg-soft: rgba(255, 253, 250, 0.76);
  --glass-bg-strong: rgba(255, 253, 250, 0.88);
  --glass-line: rgba(255, 255, 255, 0.66);
  --glass-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.46), rgba(255, 255, 255, 0) 46%);
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8), 0 12px 36px -16px rgba(74, 44, 18, 0.26);
  --surface: #fffdfa;
  --surface-2: #f6efe7;
  --surface-3: #ece1d4;
  --ink: #211a15;
  --ink-2: #5c4c40;
  --ink-3: #85705f;
  --line: #ece1d4;
  --line-2: #f5ede4;
  --brand: #211a15;
  --brand-hover: #3a2c22;
  --accent: #ea7317;
  --blue: #c2410c;
  --blue-soft: #fdeee2;
  --blue-deep: #9a3412;
  --violet: #a16207;
  --violet-soft: #fdf3df;
  --red: #c0392f;
  --red-soft: #fdeceb;
  --yellow: #b45309;
  --yellow-soft: #fdf3e3;
  --green: #15803d;
  --green-soft: #e9f7ee;
  /* 项目页那组状态色薄层：暖阳版（描边走暖调，别混进冷蓝） */
  --row-hover: #f7f0e8;
  --line-hover: #e6d7c6;
  --star-gold: #d97706;
  --due-warn: #b45309;
  --step-line-todo: #f0ddc2;
  --step-line-running: #f0cdb6;
  --step-line-done: #cfe6d5;
  --radius: 12px;
  --radius-sm: 8px;
  --radius-lg: 16px;
}

/* 薄荷：冷绿底 + 青蓝主色，干净利落 */
/* 薄荷：冷绿版（同样的骨架，只是主色换成青绿） */
[data-theme='mint'] {
  --bg: #f3f8f8;
  --grid-color: rgba(6, 46, 42, 0.05);
  /* 毛玻璃：冷绿光场（青绿 + 靛蓝 + 天青），和这套的青绿主色同一个家 */
  --aurora:
    radial-gradient(900px 640px at -4% 62%, rgba(13, 148, 136, 0.3), transparent 60%),
    radial-gradient(820px 600px at 106% 60%, rgba(79, 70, 229, 0.24), transparent 58%),
    radial-gradient(1000px 700px at 40% 104%, rgba(14, 165, 233, 0.26), transparent 60%);
  --glass-bg: rgba(255, 255, 255, 0.58);
  --glass-bg-soft: rgba(255, 255, 255, 0.74);
  --glass-bg-strong: rgba(255, 255, 255, 0.86);
  --glass-line: rgba(255, 255, 255, 0.72);
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85), 0 12px 36px -16px rgba(8, 44, 42, 0.24);
  --surface: #ffffff;
  --surface-2: #eef5f4;
  --surface-3: #e0ecea;
  --ink: #0c1a18;
  --ink-2: #40514f;
  --ink-3: #647876;
  --line: #dfeae8;
  --line-2: #ecf3f2;
  --brand: #0c1a18;
  --brand-hover: #17302c;
  --accent: #0d9488;
  --blue: #0d9488;
  --blue-soft: #e0f5f2;
  --blue-deep: #0f766e;
  --violet: #4f46e5;
  --violet-soft: #ebebfd;
  /* 项目页那组状态色薄层：薄荷版（只换和青绿主色打架的两处，其余沿用默认） */
  --row-hover: #f4faf9;
  --line-hover: #cdd9d7;
  --step-line-running: #a9ded8;
  --radius: 12px;
  --radius-sm: 8px;
  --radius-lg: 16px;
}

/* 深空：真正的暗色版（近黑 + 电光蓝 + 点阵网格），夜里看着最"科技" */
[data-theme='dark'] {
  --card-inner: none;
  --bg: #0b0e14;
  --grid-color: rgba(255, 255, 255, 0.05);
  --glass: rgba(17, 21, 29, 0.72);
  /* 毛玻璃：深空的光场要更亮一点才透得出来（暗底上低透明度的色几乎看不见），
     玻璃本身换成深灰而不是白色 —— 白玻璃在暗色主题里会糊成一片灰雾。 */
  --aurora:
    radial-gradient(900px 640px at -4% 62%, rgba(11, 107, 242, 0.36), transparent 60%),
    radial-gradient(820px 600px at 106% 60%, rgba(124, 92, 255, 0.3), transparent 58%),
    radial-gradient(1000px 700px at 40% 104%, rgba(0, 176, 148, 0.26), transparent 60%);
  --glass-bg: rgba(23, 28, 38, 0.62);
  --glass-bg-soft: rgba(17, 21, 29, 0.8);
  --glass-bg-strong: rgba(26, 32, 43, 0.9);
  --glass-line: rgba(255, 255, 255, 0.11);
  --glass-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0) 46%);
  /* 暗色下高光要收着（白边一重就发灰），主要靠落影拉开层次 */
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 14px 44px -18px rgba(0, 0, 0, 0.62);
  --surface: #11151d;
  --surface-2: #171c26;
  --surface-3: #202734;
  --ink: #e8ecf3;
  --ink-2: #b3bccb;
  --ink-3: #7b8697;
  --line: #232a37;
  --line-2: #1a202a;
  --brand: #e8ecf3;
  --brand-ink: #0b0e14; /* 深空里品牌色是浅的，主按钮上的字得反过来用近黑 */
  --brand-hover: #ffffff;
  --blue: #4d9bff;
  --blue-soft: #14243c;
  --blue-soft-hover: #1b3050;
  --blue-deep: #9cc7ff;
  --violet: #8b8bf5;
  --violet-soft: #1d1f3a;
  --accent: #ff8a3d;
  --red: #ff6b60;
  --red-soft: #351c1a;
  --yellow: #e3b341;
  --yellow-soft: #2e2411;
  --green: #4ade80;
  --green-soft: #0f2a1c;
  /* 项目页那组状态色薄层：深空版。
     亮色那套浅描边（#bcd6fb 之类）落在近黑底上会刺眼，所以一律压到"比卡片底亮一点"。
     行悬停也不能再用近白 —— 那是这次要修的毛病：悬停整行变白条、字全糊了。 */
  --row-hover: #151a23;
  --line-hover: #39414f;
  --star-gold: #e3b341;
  --due-warn: #e3b341;
  --step-line-todo: #3a2f16;
  --step-line-running: #1d3355;
  --step-line-done: #1b3a2a;
  /* 待办卡那三档底色（见 :root 里同一组令牌）：近黑底上不加这层暗色的话，
     卡片会是近白色，而字是浅色 —— 标题直接看不见（对比度 1.12）。 */
  --card-red-bg: #191418;
  --card-red-line: #4a2b2c;
  --card-yellow-bg: #1a1912;
  --card-yellow-line: #4d4022;
  --card-overdue-bg: #201416;
  --card-overdue-line: #7a3733;
  --glow: 0 0 0 3px rgba(77, 155, 255, 0.22);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  --shadow-md: 0 6px 18px -8px rgba(0, 0, 0, 0.8);
  --shadow-lg: 0 24px 60px -18px rgba(0, 0, 0, 0.9);
  --card-shadow: var(--shadow);
}

/* 卡片统一吃 --card-shadow（主题里给了更"浮"的那一档） */
.panel,
.task-card,
.ft-step,
.quad,
.ph-card,
.modal {
  box-shadow: var(--card-shadow, var(--shadow));
}

/* 顶栏那个「风格」选择框 */
.theme-pick { display: inline-flex; align-items: center; gap: 6px; flex: none; white-space: nowrap; }
.theme-pick .theme-label { font-size: 12px; color: var(--ink-3); white-space: nowrap; }
.theme-pick select {
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.theme-pick select:hover { border-color: var(--ink-3); color: var(--ink); }


/* ------------------------------------------------------------------ 细节
   科技感不在花哨，在这几处"打磨过"的地方：细滚动条、冷色选区、等宽数字。 */
::selection { background: rgba(11, 107, 242, 0.18); }
* { scrollbar-width: thin; scrollbar-color: var(--surface-3) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--surface-3);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-3); background-clip: content-box; }
/* 系统滚动条两端的上下箭头：又丑又占地方，全站关掉（列表里那种"↑"就是它） */
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
::-webkit-scrollbar-corner { background: transparent; }

/* ============================================================================
   一件事的全貌 —— **三栏对话框**（server/web/comment.js）
   左：项目流程 ｜ 中：当前环节的字段 ｜ 右：评论 / 动态 / 文件
   版式和仓库其它地方一致：卡片化、极轻阴影、眉标给区块命名。
   颜色一律走 var(--x)：换主题（warm / mint / dark）不能破相。
   ============================================================================ */

/* 卡片上的「有几句没看」角标（项目卡和「我的事」卡片共用） */
.pc-talk {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  margin-left: 6px;
  vertical-align: 1px;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
}

/* 三栏要地方，比其它弹窗宽得多。宽高都**定死**，右下角拖拽才有东西可改 */
.modal.full {
  position: relative;
  width: min(1240px, 95vw);
  max-width: min(1240px, 95vw);
  height: min(720px, 86vh);
  /**
   * ★ 2026-09-18（用户反馈「工作窗口又出问题」「不能整体被上下滑动」）：
   * `.mask` 上下各有 56px 内边距，弹窗只要比"遮罩内容区"高，遮罩（`overflow:auto`）
   * 就多出一截可滚高度 —— 滚轮一滚**整扇窗跟着上下滑**，标题栏滑到 macOS 交通灯底下
   * （那 56px 被原生外壳吃掉点击，滑过去就抓不回来了）。这里按遮罩内容区封顶。
   */
  max-height: 100%;
  display: flex;
  flex-direction: column;
}
.modal.full .modal-body { flex: 1; min-height: 0; max-height: none; padding: 0; overflow: hidden; }
.cmt-host { height: 100%; }

/* 右下角的缩放把手（只有 full 弹窗挂它） */
.modal-resize {
  position: absolute;
  right: 1px;
  bottom: 1px;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
  opacity: 0.45;
  background: linear-gradient(135deg, transparent 0 44%, var(--ink-3) 44% 52%, transparent 52% 100%);
}
.modal-resize:hover { opacity: 1; }
/* 拖标题栏挪位置 */
.modal-drag, .modal-drag h2 { cursor: move; }

.dp-layout {
  display: grid;
  /**
   * ★ 2026-09-19（用户口径：「跟单流程的板块默认占比最大，最右侧讨论框也可以拖着」）：
   *   176px            左：项目卡（固定）
   *   minmax(0, 1fr)   中：跟单流程 + 发货安排（内部还能再拖，见 .dp-track-grip）
   *   7px              分界线（拖它改讨论栏的宽窄）
   *   var(--dp-talk-w, 300px)  右：讨论（**可拖**，拖过之后这个变量由 JS 写）
   *
   * 讨论栏原来**写死 336px**：窗口一窄，「跟单流程」就被挤到比讨论栏还窄 ——
   * 实测 1240 的面板下 跟单流程 392 / 讨论 336，几乎一样宽。现在默认收到 300px，
   * 而且可以拖（见 bindTalkGrip）。左栏（项目卡）仍然固定 —— 它就是个窄导航条。
   */
  grid-template-columns: 176px minmax(0, 1fr) 7px var(--dp-talk-w, 300px);
  height: 100%;
  min-height: 0;
  border-top: 1px solid var(--line-2);
}
.dp-col { min-width: 0; height: 100%; overflow: auto; }
.dp-col-flow { padding: 14px 10px; border-right: 1px solid var(--line-2); }
/**
 * 中栏自己**不再滚**（用户口径 2026-09-18：「工作窗口只能手工缩放 拖拽位置，不能整体被上下滑动」）——
 * 滚动收进里面的 `.dp-fields`（卡片流），页脚（分批发货计划 + 操作栏）钉在下面纹丝不动。
 * 滚动条一并藏掉（第二条反馈）；滚动本身还在，否则底下的卡片就够不着了。
 * Firefox 认 `scrollbar-width`，WebKit（工作台客户端就是 WKWebView）/ Chromium 认伪元素。
 */
.dp-col-main { padding: 16px 20px; overflow: hidden; scrollbar-width: none; }
.dp-col-main::-webkit-scrollbar { width: 0; height: 0; }
.dp-col-talk { display: flex; flex-direction: column; overflow: hidden; border-left: 1px solid var(--line-2); }

.dp-eyebrow {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 0 6px 8px;
}
.dp-eyebrow-gap { padding-top: 14px; }

/* 左栏顶上的「源项目卡」：这条待办挂在哪个项目上（点一下跳过去） */
.dp-pcard {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 9px 10px;
  margin-bottom: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink);
  text-decoration: none;
}
.dp-pcard:hover { border-color: var(--blue); }
.dp-pcard-no { font-size: 10.5px; color: var(--ink-3); }
.dp-pcard-name { font-size: 12.5px; font-weight: 600; line-height: 1.35; word-break: break-word; }
.dp-pcard-foot { margin-top: 2px; }
/* 「你这一步」：待办看的是源项目的流程，得一眼找到自己在哪一步 */
.dp-step-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 10px;
  font-weight: 500;
  vertical-align: 1px;
}

/* ---- 左栏：环节 ---- */
.dp-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.dp-step { display: flex; gap: 9px; padding: 7px 7px; border-radius: var(--radius-sm); }
.dp-step-dot {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10.5px;
  font-weight: 700;
  background: var(--surface-2);
  color: var(--ink-3);
  border: 1px solid var(--line);
}
.dp-step.is-done .dp-step-dot { background: var(--green-soft); color: var(--green); border-color: transparent; }
/**
 * ★ 被驳回那一环（用户口径 2026-09-20：「单子已开，而且还被财务审核驳回，流程这里应该
 *   跟着变动……现在点开看不出来任何状态」）—— 圆点填红 + 环节名变红，一眼看出停在哪被退了。
 */
.dp-step.is-rejected { background: var(--red-soft); }
.dp-step.is-rejected .dp-step-dot { background: var(--red); color: #fff; border-color: transparent; }
.dp-step.is-rejected .dp-step-name { color: var(--red); font-weight: 700; }
.dp-step.is-rejected .dp-step-meta { color: var(--red); }
.dp-step.is-current { background: var(--blue-soft); }
.dp-step.is-current .dp-step-dot { background: var(--blue); color: #fff; border-color: transparent; }
.dp-step-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.dp-step-name { font-size: 12.5px; color: var(--ink); }
.dp-step.is-pending .dp-step-name { color: var(--ink-3); }
.dp-step.is-current .dp-step-name { font-weight: 650; }
/* `unknown`：这一步**走没走过不知道**（卡不在了、只能按备注定位当前环节时的前面几步）——
   渲染成"进度不明"，绝不假装成"未开始"或"已完成" */
.dp-step.is-unknown .dp-step-name { color: var(--ink-3); }
.dp-step.is-unknown .dp-step-dot { border-style: dashed; color: var(--ink-3); }
.dp-step-meta { font-size: 10.5px; color: var(--ink-3); }

/* 源项目卡：卡不在了（号码认出来的）——不是链接，样式也要看得出"点不动" */
.dp-pcard.is-missing { cursor: default; border-style: dashed; }

/* ---- 中栏：字段（上半部分自己滚，页脚固定） ---- */
.dp-form { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.dp-form-head {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line-2);
}
.dp-form-title { font-size: 17px; font-weight: 650; letter-spacing: -0.01em; }
.dp-form-no { margin-left: auto; font-size: 11.5px; color: var(--ink-3); }
/**
 * ★ 2026-09-24（用户报的 bug）：「销售的工作台，产品事项卡怎么里面是空的？」——
 * 销售订单总卡的面板：**六档进度**（待审 → 跟单 → 采购 → 生产 → 发货 → 完结，
 * 和卡面那条进度条同一套档位）+ 关联项目卡。走过的绿、当前那档加粗、没到的灰。
 */
.oc-steps { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 11.5px; }
.oc-step { display: inline-flex; align-items: center; gap: 4px; color: var(--ink-3); white-space: nowrap; }
.oc-step.is-on { color: #1c7a3d; font-weight: 500; }
.oc-step.is-now { font-weight: 700; }
.oc-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--surface-3); box-shadow: inset 0 0 0 1px var(--line); flex: none; }
.oc-step.is-on .oc-dot { background: #1c7a3d; box-shadow: none; }
.oc-step.is-now .oc-dot { box-shadow: 0 0 0 3px rgba(28, 122, 61, 0.16); }
.oc-link { width: 16px; height: 3px; border-radius: 2px; background: var(--line); flex: none; }
.oc-link.is-on { background: #1c7a3d; }
.oc-projects { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; margin-top: 2px; font-size: 11.5px; color: var(--ink-3); }
.oc-proj {
  padding: 1px 8px; border-radius: 999px;
  background: var(--surface-3); color: var(--ink-2);
  font-weight: 600; text-decoration: none; white-space: nowrap;
}
.oc-proj:hover { background: var(--blue-soft); color: var(--blue); }
/**
 * ★ **「被驳回」横幅**（用户口径 2026-09-20：「驳回的状态和原因，要点开卡片的详情图内
 *   展示出来，现在点开看不出来任何状态」）—— 红底 + 左红杠，摆在字段之前，
 *   写明谁驳回的、为什么、下一步干什么。左栏流程条上被驳回那一环同步画红（`.dp-step.is-rejected`）。
 */
.dp-reject {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 10px 0 0;
  padding: 8px 10px;
  background: var(--red-soft);
  border-left: 3px solid var(--red);
  border-radius: var(--radius-sm);
}
/** ★ 2026-09-21（用户口径）：「已驳回上面加上**商品详情**」——独占一整行，摆在驳回原因上面 */
/* ★ 2026-09-21：横幅里的商品详情 —— **分字段一小块**（照「订单详情」那套排法） */
.dp-reject-goods { flex: 0 0 100%; display: grid; grid-template-columns: auto auto auto minmax(0, 1fr); gap: 2px 18px; align-items: baseline; font-size: 12.5px; color: var(--ink); padding-bottom: 2px; }
.dp-reject-goods-cell { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.dp-reject-goods-cell.is-wide { grid-column: 1 / -1; }
.dp-reject-goods-k { flex: none; color: var(--ink-3); font-size: 11.5px; }
.dp-reject-goods b { font-weight: 650; }
.dp-reject-text { font-size: 13px; color: var(--ink); }
.dp-reject-text b { font-weight: 700; }
.dp-reject-hint { margin-left: auto; font-size: 12px; color: var(--ink-2); white-space: nowrap; }
/**
 * 中栏右上角那个**相关订单号**（用户口径 2026-09-18：点开看这张销售单的浮窗详情）——
 * 待办卡上它比 TD-xxx 有用得多，所以做成一个能点的药丸；自己的编号在面板底部还写着。
 */
.dp-form-order {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
  padding: 2px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--blue);
  cursor: pointer;
}
.dp-form-order:hover { background: var(--blue-soft); border-color: var(--blue); }
/**
 * 卡片流这一层：**唯一会滚的地方**（用户口径 2026-09-18 定稿）。
 * 中栏锁死高度 → 头（标题 + 订单号）和脚（发货计划 + 操作栏）都不动，只有中间这一层滑。
 * 滚动条藏掉；`overscroll-behavior: contain` 防止滚到底之后把整个面板/页面带下去
 * （那种"整体被上下滑动"的观感多半就是它）。
 */
.dp-fields {
  /**
   * ★ 2026-09-25（用户口径）：「**排版还是很垃圾**」（截图：卡片下面一大片空、动作条孤悬在最底下）。
   *
   * 原来是 `flex: 1 1 auto` —— 内容短的时候这一层**照样把剩余高度吃满**，于是卡片和底部动作条
   * 之间空出一大块（实测 197px，而且那层是透底的，看着就是个"灰洞"）。
   *
   * 改成 `flex: 0 1 auto`：**基准尺寸 = 内容高度**，内容短就紧贴内容（动作条跟上来），
   * 内容长才由 flex-shrink 压到容器高度里、在这一层内部滚。
   * 所以 9/18 那条口径（中栏不整体滚、页脚钉在下面纹丝不动）**照样成立** ——
   * 只是"内容本来就短"的时候不再硬撑出一块空白。
   */
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 0;
}
.dp-fields::-webkit-scrollbar { width: 0; height: 0; }
/** 固定页脚：分批发货计划（跟单卡才有）+ 操作栏，永远整块贴在面板底部 */
.dp-foot {
  flex: 0 0 auto;
  border-top: 1px solid var(--line-2);
  padding-top: 8px;
  background: var(--surface);
}
.dp-field { display: grid; grid-template-columns: 86px minmax(0, 1fr); gap: 10px; align-items: baseline; }
.dp-k { font-size: 12.5px; color: var(--ink-3); }
.dp-v { font-size: 13.5px; color: var(--ink); word-break: break-word; }
.dp-v.mono { font-family: var(--mono); font-size: 12.5px; }
.dp-field-pic { align-items: start; }
.dp-pic {
  max-width: 170px;
  max-height: 130px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  cursor: zoom-in;
}
/**
 * 操作条：★★★★ 一定要**能换行** —— 加了「已确认 · 交期 …」这种状态字之后按钮会变宽，
 * 不换行就被挤出可视区（用户口径 2026-09-19：「确定交期后，这个工作栏变的信息少了」——
 * 其实是「项目结束」那几颗被挤没了）。 */
.dp-acts { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 12px; border-top: 1px solid var(--line-2); }
/**
 * ★ 2026-09-19（用户口径）：「最下排操作按钮，**操作过和没操作过需要有区别**」——
 *   这一步的动作大多是「存一下字段」（确认交期 / 改交期 / 写备注…），办过之后按钮跟没办一样，
 *   看不出来。办过的标成**绿色 + 勾 + 一句办成了什么**（交期 09-23 / 已记备注）。
 */
.dp-acts .sm.is-acted { color: var(--green); border-color: var(--green); background: var(--green-soft); }
.dp-acts .sm.is-acted:hover { background: var(--green-soft); border-color: var(--green); }
.dp-acts .sm.is-acted .dp-act-note { margin-left: 3px; color: var(--ink-2); font-weight: 400; font-size: 11px; }

/* ---- 中栏：商品明细（采购单/销售单点开要看买/卖了什么，用户口径 2026-09-16）---- */
.dp-items { display: flex; flex-direction: column; gap: 8px; padding-top: 4px; }
.dp-items-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.dp-items-head .eyebrow { margin: 0; }
/**
 * ★ 2026-09-19（用户口径）：「整体 ui 布局再优化，一定要做到**任何步骤 信息分类都一目了然**」。
 *
 * 面板（跟单 / 采购 / 入库 / 发货…每一步的详情）里，每一块都是用 `.dp-items` 包的，
 * 但原来只有跟单卡那几块有卡片底，其它步骤是"一片白到底"；而且区块标题用的是 10.5px 的
 * 灰色眉标 —— 比它下面的小标题还弱，扫一眼分不出"这一块讲什么"。
 * 所以这一层统一成：**卡片 + 一条像样的分区标题**。作用域限在 `.dp-form`（三栏面板）里，
 * 商品 / 单据列表那些页面的 `.dp-items` 不受影响。
 */
.dp-form .dp-items:not(.dp-note-block):not(.dp-sales) {
  padding: 11px 13px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.dp-form .dp-items:not(.dp-note-block):not(.dp-sales) > .dp-items-head {
  align-items: center;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line-2);
}
/* 分区标题：从"灰色小眉标"提到"深色小标题"—— 一眼能停住；比块内的小标题（13.5px）再大一档 */
.dp-form .dp-items-head .eyebrow {
  font-size: 14px;
  font-weight: 650;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--ink);
}
.dp-items-table { border: 1px solid var(--line-2); border-radius: var(--radius-sm); overflow: hidden; }
.dp-items-table th { font-size: 11px; color: var(--ink-3); background: var(--surface-2); }
.dp-items-table td { vertical-align: middle; }
.dp-items-table .pt-thumb { width: 34px; height: 34px; flex: 0 0 auto; }
.dp-item-prod { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.dp-item-text { display: flex; flex-direction: column; min-width: 0; }

/* ---- 右栏：评论 / 动态 / 文件 ---- */
.dp-col-talk .cmt-tabs { margin: 12px 14px 0; }
.dp-talk-body { flex: 1; min-height: 0; overflow: auto; padding: 12px 14px; }

.cmt-tabs button { display: inline-flex; align-items: center; gap: 5px; }
.cmt-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--blue); color: #fff;
  font-size: 10px; font-weight: 700;
}
.cmt-n { color: var(--ink-3); font-size: 11px; }

/* 参与人那行（在输入框上方） */
.cmt-people {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 9px 14px 0;
  border-top: 1px solid var(--line-2);
}
.cmt-people-k { font-size: 11px; color: var(--ink-3); }
.cmt-p {
  padding: 2px 9px; border-radius: 999px;
  background: var(--surface-2); color: var(--ink-2); font-size: 12px;
}
.cmt-addp {
  width: 20px; height: 20px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line); border-radius: 999px;
  background: transparent; color: var(--ink-3); cursor: pointer;
}
.cmt-addp:hover { border-color: var(--blue); color: var(--blue); }

/* 消息 —— **微信那种**：自己的靠右、别人的靠左 */
.cmt-item { display: flex; gap: 10px; padding: 9px 8px; border-radius: var(--radius); }
.cmt-item:hover { background: var(--surface-2); }
.cmt-item.is-new { background: var(--blue-soft); }
/* 自己的：整行反过来（头像跑到右边） */
.cmt-item.is-mine { flex-direction: row-reverse; }
.cmt-ava {
  flex: none; width: 28px; height: 28px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-3); color: var(--ink-2); font-size: 12px; font-weight: 600;
}
.cmt-item.is-mine .cmt-ava { background: var(--blue-soft); color: var(--blue-deep); }
/* 名字 / 气泡都贴着自己那一侧 */
.cmt-main { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; flex: 1; }
.cmt-item.is-mine .cmt-main { align-items: flex-end; }
.cmt-head { display: flex; align-items: baseline; gap: 8px; max-width: 100%; }
/* row-reverse：DOM 里第一个（名字）落在最右，删除跑到最左，靠 grow 撑开 */
.cmt-item.is-mine .cmt-head { flex-direction: row-reverse; }
.cmt-who { font-size: 12.5px; color: var(--ink); }
.cmt-at { font-size: 11px; color: var(--ink-3); }
/* 发言时在哪个环节（「徐壮 · 跟单」）——一条讨论是整个项目共用的，不标就看不出谁在哪一环 */
.cmt-step {
  padding: 0 7px; border-radius: 999px;
  background: var(--surface-3); color: var(--ink-2);
  font-size: 10.5px;
}
/* 从待办点进来时提醒一句：这条讨论其实挂在项目上 */
.cmt-thread-hint {
  margin-bottom: 10px; padding: 7px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink-2);
  font-size: 11.5px; line-height: 1.5;
}
.cmt-thread-hint b { color: var(--ink); }
.cmt-edited { font-size: 10.5px; color: var(--ink-3); }
.cmt-del { opacity: 0; font-size: 11.5px; }
.cmt-item:hover .cmt-del { opacity: 1; }
/* 待发的图：**小缩略图并排**（微信那样）。
   一次可以贴多张，所以必须小 —— 做大图两三张就把输入框顶没了。 */
.cmt-pending { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.cmt-thumb { position: relative; display: inline-block; line-height: 0; }
.cmt-thumb-img {
  display: block;
  width: 64px; height: 64px;
  object-fit: cover;
  /* 悬停只变光标，不弹大图（点了才弹） */
  cursor: zoom-in;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  cursor: zoom-in;
}
.cmt-thumb.is-busy {
  display: inline-flex; align-items: center; justify-content: center;
  width: 64px; height: 64px;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  color: var(--ink-3); font-size: 13px; line-height: 1;
}
.cmt-thumb-x {
  position: absolute; top: 2px; right: 2px;
  width: 17px; height: 17px; padding: 0;
  border: 0; border-radius: 999px;
  background: rgba(0, 0, 0, 0.55); color: #fff;
  cursor: pointer; font-size: 10px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.cmt-thumb-x:hover { background: var(--red); }

/* 气泡：别人的浅底，自己的走主色（白字） */
.cmt-body {
  display: inline-block;
  max-width: 100%;
  margin-top: 4px;
  padding: 7px 11px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink);
  font-size: 13.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}
/* 消息里带的图片（粘贴进来的）：点开看大图，所以是放大镜手型 */
.cmt-img {
  display: block;
  max-width: 220px;
  max-height: 220px;
  margin-top: 4px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  cursor: zoom-in;
}
.cmt-item.is-mine .cmt-img { margin-left: auto; }

.cmt-item.is-mine .cmt-body {
  border-color: transparent;
  background: var(--blue);
  color: #fff;
}
.cmt-mentions { margin-top: 5px; display: flex; gap: 5px; flex-wrap: wrap; }
.cmt-item.is-mine .cmt-mentions { justify-content: flex-end; }
.cmt-at-chip {
  padding: 1px 8px; border-radius: 999px;
  background: var(--violet-soft); color: var(--violet); font-size: 11px;
}

/* 输入框 + 工具条 */
.cmt-compose { padding: 8px 14px 12px; }

/* 「那个框」—— 图和文字共用它。焦点在里面的 textarea 上时整个框亮起来。 */
.cmt-input-box {
  box-sizing: border-box;
  padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.cmt-input-box:focus-within { border-color: var(--blue); box-shadow: var(--glow); }
/* ★ 用**两层类**压掉全局的 input/select/textarea 规则（见上面第 1064 行那儿）。
   那条全局规则给每个 textarea 都画了边框/圆角/背景，而 textarea:focus（0,1,1）
   的优先级比 .cmt-input（0,1,0）高 —— 结果输入框一聚焦就自己冒出一圈边框和光晕，
   看着就是"图和文字在两个框里"。这里是 (0,2,0) / (0,2,1)，压得死。 */
.cmt-input-box .cmt-input,
.cmt-input-box .cmt-input:focus {
  display: block; width: 100%; box-sizing: border-box;
  padding: 0; border: 0; border-radius: 0;
  background: transparent; box-shadow: none; outline: none;
  color: var(--ink);
  resize: vertical; min-height: 54px;
  font: inherit; font-size: 13.5px; line-height: 1.6;
}
.cmt-input-box .cmt-input::placeholder { color: var(--ink-3); }
.cmt-compose-bar { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.cmt-ico {
  width: 28px; height: 28px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink-3); cursor: pointer;
}
.cmt-ico:hover { border-color: var(--blue); color: var(--blue); }
.cmt-quiet {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--ink-2); cursor: pointer; user-select: none;
}
.cmt-quiet input { margin: 0; cursor: pointer; }

/* 底部那一块（参与人 + 输入框）。人员表**浮在它上方**，不挤动它俩 */
.dp-foot { position: relative; }

/* 表情面板（在输入框和工具条之间，正常占位） */
.cmt-emoji {
  display: flex; flex-wrap: wrap; gap: 2px; margin-top: 8px;
  padding: 8px; border-radius: var(--radius); background: var(--surface-2);
  max-height: 132px; overflow: auto;
}
.cmt-emoji[hidden] { display: none; }
.cmt-emoji-btn {
  width: 30px; height: 30px; padding: 0; font-size: 17px; line-height: 1;
  border: 0; border-radius: var(--radius-sm); background: transparent; cursor: pointer;
}
.cmt-emoji-btn:hover { background: var(--surface); }

/* 人员表：**从参与人那一行往上展开**（bottom:100% 贴着 dp-foot 的顶边），
   所以参与人 / ＋ 一动不动，输入框和工具条也不会被挤下去。 */
.cmt-picker {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 100%;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 10px;
  margin-bottom: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  max-height: 260px;
  overflow: auto;
}
.cmt-picker[hidden] { display: none; }
/* 分组小标题：本项目 / 全部人员 */
.cmt-pick-sec {
  width: 100%;
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--ink-3);
  padding: 4px 2px 1px;
}
.cmt-pick-sec:first-child { padding-top: 0; }
.cmt-pick {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 4px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.cmt-pick:hover { border-color: var(--blue); color: var(--blue); }
.cmt-pick-role { font-size: 10px; color: var(--ink-3); }
/* 项目上手填的名字，花名册里查不到 → 画成不可点的灰片，不假装能点 */
.cmt-pick.is-off { opacity: 0.45; cursor: default; border-style: dashed; }
.cmt-at { font-size: 15px; font-weight: 700; }

/* 动态 */
.cmt-flow { display: flex; flex-direction: column; }
.cmt-flow-item { display: flex; gap: 10px; padding: 7px 2px; }
.cmt-flow-dot { flex: none; width: 7px; height: 7px; margin-top: 6px; border-radius: 999px; background: var(--surface-3); }
.cmt-flow-t { font-size: 13px; color: var(--ink-2); }
.cmt-flow-at { font-size: 11px; color: var(--ink-3); margin-top: 2px; }

/* 文件 */
.cmt-files { display: flex; flex-direction: column; gap: 6px; }
.cmt-file {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); text-decoration: none;
}
.cmt-file:hover { border-color: var(--blue); }
.cmt-file-ico { color: var(--ink-3); }
.cmt-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.cmt-file-meta { font-size: 11px; color: var(--ink-3); }

/* 窄屏（手机）：三栏竖着堆，先去流程再字段最后聊天；「删除」别藏在 hover 里 */
@media (max-width: 900px) {
  /* 竖着堆了就没有"左右"可拖 —— 分界线一并藏掉（不然会多出一条 7px 的怪缝） */
  .dp-layout { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .dp-talk-grip { display: none; }
  .dp-col-flow, .dp-col-main, .dp-col-talk { max-height: none; border: 0; border-bottom: 1px solid var(--line-2); }
  .dp-col-talk { border-bottom: 0; }
  .dp-talk-body { max-height: 46vh; }
}
@media (max-width: 430px) {
  .cmt-file-meta { display: none; }
  .cmt-del { opacity: 1; }
  .dp-field { grid-template-columns: 74px minmax(0, 1fr); }
  .cmt-people { gap: 4px; }
}

/* ============================================================================
   消息中心（server/web/pages-msg.js）
   左：会话列表（未读 / 最近 / 更早 三段）｜右：这个会话
   颜色一律走 var(--x)，换主题不破相。
   ============================================================================ */

/* fill 页面里 #content 是 flex 列，这里撑满剩下的高度 */
.msg-layout {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: var(--side-w, 426px) minmax(0, 1fr);
  border-top: 1px solid var(--line-2);
}

/* ---- 左栏：会话列表 ---- */
.msg-side {
  display: grid;
  /* 分组列 | 拖宽把手 | 会话列表 —— 宽度由 --rail-w 控制（拖出来的，记在本机） */
  grid-template-columns: var(--rail-w, 146px) 6px minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  border-right: 1px solid var(--line-2);
}
/* 拖宽把手：和项目页那条一个样子 */
.msg-resizer {
  grid-column: 2;
  grid-row: 1 / span 2;
  cursor: col-resize;
  background: transparent;
}
.msg-resizer:hover { background: var(--blue-soft); }
/* 分组那一列（钉钉那种） */
.msg-rail { grid-row: 1 / span 2; border-right: 1px solid var(--line-2); overflow: auto; padding: 12px 6px; }
.msg-rail-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: var(--radius-sm);
  color: var(--ink-2); text-decoration: none; font-size: 13px;
}
.msg-rail-item:hover { background: var(--surface-2); }
.msg-rail-item.active { background: var(--blue-soft); color: var(--blue-deep); font-weight: 600; }
.msg-rail-ico { display: inline-flex; color: var(--ink-3); }
.msg-rail-item.active .msg-rail-ico { color: var(--blue); }
.msg-rail-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-rail-count { font-size: 11px; color: var(--ink-3); }
.msg-rail-item.active .msg-rail-count { color: var(--blue-deep); }
.msg-search {
  grid-column: 3;
  margin: 12px 12px 8px; padding: 7px 11px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink); font: inherit; font-size: 12.5px;
}
.msg-search:focus { outline: none; border-color: var(--blue); box-shadow: var(--glow); }
.msg-list { grid-column: 3; min-height: 0; overflow: auto; padding: 0 8px 12px; }

/* 会话列表 */
.msg-items { display: flex; flex-direction: column; gap: 1px; }

.msg-item {
  display: flex; gap: 10px; padding: 9px 8px;
  border-radius: var(--radius); color: var(--ink); text-decoration: none;
}
.msg-item:hover { background: var(--surface-2); }
.msg-item.active { background: var(--blue-soft); }
/* 会话图标块（照钉钉）：带底色的圆角块 + 右下角一个类型小字 */
.msg-ava {
  position: relative;
  flex: none; width: 38px; height: 38px; border-radius: 11px;
  display: inline-flex; align-items: center; justify-content: center;
}
.msg-ava-badge {
  position: absolute; right: -3px; bottom: -3px;
  min-width: 15px; height: 15px; padding: 0 3px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 5px; border: 1.5px solid var(--surface);
  background: var(--surface-3); color: var(--ink-2);
  font-size: 9.5px; font-style: normal; font-weight: 600; line-height: 1;
}
/* 配色从主题的语义色里挑，不自己造颜色 */
/* 未读数**标在图标块上**（照钉钉），不是行内再写一遍 */
.msg-ava-unread {
  position: absolute; top: -6px; right: -6px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; border: 1.5px solid var(--surface);
  background: var(--red); color: #fff;
  font-size: 10px; font-style: normal; font-weight: 700; line-height: 1;
}
.msg-ava.tone-blue { background: var(--blue-soft); color: var(--blue); }
.msg-ava.tone-violet { background: var(--violet-soft); color: var(--violet); }
.msg-ava.tone-green { background: var(--green-soft); color: var(--green); }
.msg-ava.tone-yellow { background: var(--yellow-soft); color: var(--yellow); }
.msg-ava.tone-red { background: var(--red-soft); color: var(--red); }
.msg-item-main { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 3px; }
.msg-item-top { display: flex; align-items: baseline; gap: 6px; }
/* ★ 别写成 flex:1 —— 后面那个 .grow 占位也是 flex:1，两个一比一，
   等于把可用宽度对半分，长项目名会被砍成「测试一...」。
   写成 0 1 auto：先按内容宽度占满，实在放不下才截断。 */
.msg-item-title {
  /* 名字**尽量占满**：类型标签去掉之后，这里能用上整行剩下的宽度。 */
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px; font-weight: 600;
}
/* 项目编号：给个固定位置（第二行开头），扫一眼就能对上是哪个项目 */
.msg-no { flex: none; font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); }
.msg-item-time { flex: none; font-size: 10.5px; color: var(--ink-3); }
.msg-item-sub { display: flex; align-items: baseline; gap: 5px; min-width: 0; font-size: 11.5px; color: var(--ink-3); }
.msg-item-who { flex: none; display: inline-flex; align-items: baseline; gap: 4px; color: var(--ink-2); }
.msg-step { padding: 0 5px; border-radius: 4px; background: var(--surface-3); color: var(--ink-3); font-size: 10px; }
.msg-colon { flex: none; color: var(--ink-3); }
.msg-item-body { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-mute { flex: none; font-size: 11px; }

/* ---- 右栏：一个会话 ---- */
.msg-main { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.msg-head {
  display: flex; align-items: center; gap: 9px;
  padding: 12px 16px; border-bottom: 1px solid var(--line-2);
}
.msg-head-ico { display: inline-flex; color: var(--ink-3); }
.msg-head-title { font-size: 14.5px; font-weight: 650; letter-spacing: -0.01em; }
.msg-body { flex: 1; min-height: 0; overflow: auto; padding: 12px 16px; }
.msg-main .empty { padding: 40px 20px; text-align: center; }

/* ---- 新会话弹窗（选人 / 起群名） ---- */
.nc-tabs { margin-bottom: 10px; }
.nc-hint { margin-bottom: 10px; }
.nc-title, .nc-search {
  width: 100%; box-sizing: border-box; margin-bottom: 10px;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); font: inherit; font-size: 13px;
}
.nc-title:focus, .nc-search:focus { outline: none; border-color: var(--blue); box-shadow: var(--glow); }
.nc-people { display: flex; flex-wrap: wrap; gap: 6px; max-height: 320px; overflow: auto; }
.nc-person {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px 4px 5px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink-2); font: inherit; font-size: 12.5px; cursor: pointer;
}
.nc-person:hover { border-color: var(--blue); }
.nc-person.on { border-color: var(--blue); background: var(--blue-soft); color: var(--blue-deep); }
.nc-ava {
  width: 20px; height: 20px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-3); color: var(--ink-2); font-size: 10.5px; font-weight: 600;
}
.nc-person.on .nc-ava { background: var(--blue); color: #fff; }
.nc-tick { font-size: 11px; }

/* 窄屏：竖着堆（列表在上，会话在下） */
@media (max-width: 900px) {
  .msg-layout { grid-template-columns: minmax(0, 1fr); }
  .msg-side { border-right: 0; border-bottom: 1px solid var(--line-2); max-height: 42vh; }
}

/* 分组列的标题（照钉钉那个「≡ 分组」） */
.msg-rail-head {
  display: flex; align-items: center; gap: 7px;
  padding: 2px 9px 10px; color: var(--ink-3);
}
.msg-rail-head-ico { display: inline-flex; }
.msg-rail-head-label { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }

/* 列表最底下那一行：折叠 / 展开置顶会话 */
.msg-fold {
  display: flex; align-items: center; gap: 6px; width: 100%;
  margin-top: 8px; padding: 8px; border: 0; border-top: 1px solid var(--line-2);
  background: transparent; color: var(--ink-3); font: inherit; font-size: 12px; cursor: pointer;
}
.msg-fold:hover { color: var(--ink); }
.msg-fold-caret { display: inline-flex; transition: transform var(--spring-snappy); }
.msg-fold-caret.open { transform: rotate(90deg); }

/* 右边没有会话时的空态（别只甩一句"没有数据"） */
.msg-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; height: 100%; text-align: center; padding: 40px 20px; }
.msg-welcome-ico {
  width: 64px; height: 64px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--ink-3);
}
.msg-welcome-t { font-size: 14px; color: var(--ink-2); }
.msg-welcome-s { font-size: 12px; line-height: 1.7; color: var(--ink-3); }

/* ---- 横版项目卡（消息页右栏顶上那块）------------------------------------
   三行都是横着的：身份 / 人 / 进度。窄屏自己折行，不撑高。 */
.mdc {
  display: flex;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-2);
  background: var(--surface-2);
}
.mdc-pic {
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.mdc-pic img { width: 100%; height: 100%; object-fit: cover; }
/* 有图的时候手型变放大镜 —— 悬停会弹大图（和项目页同一个设定） */
.mdc-pic.is-img { cursor: zoom-in; }
.mdc-pic-ico { color: var(--ink-3); display: inline-flex; }
.mdc-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }

.mdc-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mdc-name {
  font-size: 14.5px; font-weight: 650; letter-spacing: -0.01em;
  max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mdc-no { font-size: 11.5px; color: var(--ink-3); }

.mdc-meta { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--ink-2); }
.mdc-cell { display: inline-flex; align-items: baseline; gap: 5px; }
.mdc-cell i { font-style: normal; font-size: 10.5px; color: var(--ink-3); }

/* 进度：一条细进度条 + 环节名（走过的实心、当前的高亮、没到的灰） */
.mdc-flow { display: flex; align-items: center; gap: 10px; }
.mdc-bar {
  flex: none; width: 96px; height: 5px; border-radius: 999px;
  background: var(--surface-3); overflow: hidden;
}
.mdc-bar i { display: block; height: 100%; background: var(--blue); border-radius: 999px; }
.mdc-steps { flex: 1; min-width: 0; display: flex; gap: 6px; flex-wrap: wrap; font-size: 11.5px; }
/* 每一步的状态**配色和项目流的环节卡一致**：已完成绿 / 进行中蓝 / 待办中琥珀 / 未开始灰。
   圆点字符（✓ ◐ ● ○）已经带了信息，颜色是加强 —— 只靠颜色区分对色弱不友好。 */
.mdc-step {
  padding: 0 7px; border-radius: 999px; line-height: 18px;
  background: var(--surface-3); color: var(--ink-3);
}
.mdc-step.is-done { background: var(--green-soft); color: var(--green); }
.mdc-step.is-running { background: var(--blue-soft); color: var(--blue); font-weight: 600; }
.mdc-step.is-todo { background: var(--yellow-soft); color: var(--yellow); }
.mdc-step.is-none { background: var(--surface-3); color: var(--ink-3); }
/* 名字后面的状态小字 */
.mdc-step-st { margin-left: 4px; font-size: 10px; font-style: normal; opacity: 0.78; }
.mdc-pct { flex: none; font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }

/* 窄屏：项目卡竖着堆，进度条整行 */
@media (max-width: 700px) {
  .mdc-name { max-width: 100%; }
  .mdc-flow { flex-wrap: wrap; }
}

/* ============================================================================
   商品档案（server/web/pages-product.js）
   工具栏 + 表格 + 批量条 + 导入预览。
   颜色一律走 var(--x)（换主题不破相）；类名按模块分域：prod-（页面壳）/ pt-（商品表）。
   ============================================================================ */

/* ---------------------------------------------------------------- 商品页
   用户口径：下面的商品信息要"适配工作台尺寸" ——
   整页撑满视口（#app.page-fill），筛选条钉在顶上，表格区吃掉剩下的高度自己滚：
   纵向滚行、横向滚列（操作那一列永远够得着，不会被切掉）。 */
.prod-page {
  display: flex;
  flex-direction: column;
  gap: 5px;
  height: 100%;
  min-height: 0;
  padding: 0 32px 10px; /* page-bleed 把左右边距去掉了，这里自己补回来 */
  box-sizing: border-box;
}
.prod-page .prod-bar { flex: none; }
.prod-page .prod-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
}
.prod-page .prod-scroll > .empty { margin: auto; }

/* ---------------------------------------------------------------- 价格 / 成本页
   用户口径 2026-09-19：「价格页面的 UI 设计需要重新改」。原来顶部是一条挤在工具栏里的
   灰字（几个数字一样大、看不出该管哪个），下面两张表裸着铺、中间那句长说明占半屏。
   现在：① 概览做成三张 `.stat` 卡（大数字 + 眉标，缺价那张按状态给色，**点一下切视图**）；
        ② 两张表各套一个 `.price-sec` 区块（眉标 + 计数 + 右侧"只看…"）；
        ③ 长说明默认收起。全部用工作台令牌，换主题不破相。 */
.price-stats { margin: 2px 0 4px; }
.price-stats .price-stat {
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  min-height: 104px;
  transition: border-color 0.15s, box-shadow 0.15s;
  /* 窄屏下卡片会变窄，说明那行字（"最近采购 0 · 按 BOM 滚 32 · 手工 217"）比卡片宽 ——
     不许它把卡片撑破（撑破就横向溢出），让它自己换行。 */
  overflow-wrap: anywhere;
  min-width: 0;
}
/* 窄屏：三张卡挤两列会把内容压到看不出层次，干脆一列一张 */
@media (max-width: 560px) {
  .price-stats { grid-template-columns: 1fr; }
  .price-stats .price-stat { min-height: 0; }
}
.price-stats .price-stat:hover { border-color: var(--line-hover); box-shadow: var(--shadow-md); }
/* 当前正在看的那张：描边用主色 + 一道左竖条（和"选中的那张表"呼应） */
.price-stats .price-stat.is-on {
  border-color: var(--blue);
  box-shadow: inset 3px 0 0 var(--blue);
}
.price-stats .price-stat .v .unit { font-size: 15px; font-weight: 500; color: var(--ink-3); letter-spacing: 0; }

.price-hint { flex: none; }
.price-hint .link-btn { font-size: 12px; }

/* 选了一批要批量填价时那条：给主按钮，让它比旁边的筛选条显眼 */
.prod-page .price-picks { background: var(--blue-soft); border-radius: var(--radius); padding: 6px 10px; }

/* 一张表一个区块：白底 + 描边 + 圆角，区块头是"眉标 + 计数"。
   两张表并排铺时留出间距（`is-alone` = 只看这一张，就不要下边距）。 */
.price-sec {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 12px 14px 10px;
  margin-bottom: 12px;
}
.price-sec.is-alone { margin-bottom: 0; }
.price-sec-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 8px;
}
.price-sec-head .cnt { font-size: 15px; font-weight: 650; letter-spacing: -0.01em; }
.price-sec-head .cnt.warn { color: var(--red); }
/* 区块里的表格去掉自己的外框（区块已经有框了，别套两层） */
.price-sec .items-table { border: 0; }

.prod-page .pt-wrap { flex: 1 1 auto; min-height: 0; }
/* 名称那一列吃掉富余宽度，编号/分类/属性/时间/状态/操作 都按内容定宽 */
.prod-page .pt { min-width: 100%; }
.prod-page .pt .pt-name { width: auto; }

/* 窄屏：名称列**必须给个实宽**。
   `.pt` 是 `table-layout: fixed`，其它 10 列声明宽合计 912px；窄屏容器只有 ~364px，
   而这一列是"吃掉富余宽度"的那一列 → 富余是负的，会被算成 **0px**：商品名一个字都看不见
   （实测 430px 下 nameColW=0，桌面 1440px 下是正常的 252px）。
   必须 `!important`：那一列的宽度是 `prodThHtml` 写在 **th 的内联 style** 上的（`width:auto`），
   内联优先级高于媒体查询。窄屏上拖列宽本来也点不到（没有 hover），所以压过它没有副作用。 */
@media (max-width: 700px) {
  .prod-page .pt .pt-name { width: 200px !important; }
}
.prod-page /* 表头右边那个"拖宽"手柄：平时看不见，鼠标到这一列（或正在拖）才显出来 */
/* 拖宽手柄：贴在表头格子右边（表头是 sticky，本身就是定位元素，手柄 absolute 就贴它）。
   ⚠️ 这里千万别再写 .pt thead th { position: relative } —— 那会把吸顶顶掉，表头跟着滚走。 */
.pt .pt-resize {
  position: absolute;
  /* 必须在格子里（right:0）—— 原来 right:-2px 伸出格子，会被右边那一列的
     sticky 表头盖住（两个 th 的层叠上下文一样，后面的在上），结果只剩 2px 能点。 */
  right: 0;
  top: 0;
  bottom: 0;
  width: 12px;
  cursor: col-resize;
  z-index: 4;
  touch-action: none;
  user-select: none;
}
.pt .pt-resize::after {
  content: '';
  display: block;
  width: 2px;
  height: 62%;
  margin: 19% 2px 0 auto; /* 贴着格子右边缘站 */
  
  border-radius: 2px;
  background: transparent;
  transition: background 0.12s ease, width 0.12s ease;
}
.pt th:hover .pt-resize::after { background: var(--line); }
.pt .pt-resize:hover::after { width: 3px; }
.pt .pt-resize:hover::after,
body.resizing .pt .pt-resize::after { background: var(--blue); }

/* 列设置浮层：勾显示 + 拖顺序 */
.pc-panel {
  position: absolute;
  right: 10px;
  z-index: 30;
  width: 300px;
  padding: 10px 12px 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.pc-panel[hidden] { display: none; }
.pc-head { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 650; }
.pc-head .grow { flex: 1; }
.pc-lead { margin: 6px 0 8px; color: var(--ink-3); font-size: 11.5px; line-height: 1.5; }
.pc-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 2px;
  border-top: 1px solid var(--line-2);
}
.pc-row:first-of-type { border-top: 0; }
.pc-drag { display: inline-flex; color: var(--ink-3); cursor: grab; }
.pc-drag.is-off { opacity: 0.3; cursor: default; }
.pc-check { display: inline-flex; align-items: center; gap: 6px; flex: 1; font-size: 12.5px; color: var(--ink); cursor: pointer; }
.pc-check input { margin: 0; cursor: pointer; }
.pc-check input:disabled { cursor: default; }
.pc-w { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pc-reset { border: 0; background: transparent; padding: 0 4px; font-size: 11.5px; color: var(--blue); cursor: pointer; }
.pc-reset:empty { display: none; }

.pt .pt-acts { width: 132px; }

/* 成本价那一列：有价就是数字，缺价显式写出来（浅红，一眼扫得到）。
   2026-09-16 起这两样都是**按钮**（点开看这笔成本是怎么算出来的、缺哪一支料）——
   所以先把通用按钮那套框 / 底 / 内距抹掉，只留这块小地方自己的样子。 */
.pt .pt-cost { font-variant-numeric: tabular-nums; }
.pt .pt-cost .pt-cost-missing,
.pt .pt-cost .pt-cost-num {
  display: inline-block;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: background 0.12s ease, box-shadow 0.12s ease, color 0.12s ease;
}
.pt .pt-cost .pt-cost-missing {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-size: 11.5px;
}
/* 悬停只加一圈红边、不铺底色块 —— 一眼知道能点，换主题也不破相 */
.pt .pt-cost .pt-cost-missing:hover { box-shadow: inset 0 0 0 1px var(--red); }
.pt .pt-cost .pt-cost-num { padding: 1px 4px; border-radius: 6px; }
.pt .pt-cost .pt-cost-num:hover { background: var(--surface-2); color: var(--brand); }
/* 它现在是真按钮，本来就进 Tab 顺序 —— 键盘走到时要看得见 */
.pt .pt-cost .pt-cost-missing:focus-visible,
.pt .pt-cost .pt-cost-num:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* 成本核算结果弹窗（点「缺价」/ 成本价弹出来的那个）：结论 → 缺什么（派工）→ 逐层 */
.prod-costx { display: flex; flex-direction: column; gap: 8px; }
.prod-costx .costx-sum {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--surface-2);
}
.prod-costx .costx-k { color: var(--ink-3); font-size: 12px; }
.prod-costx .costx-sum b { font-size: 16px; }
.prod-costx .costx-missing th,
.prod-costx .costx-missing td { font-size: 12.5px; }
.prod-costx .eyebrow { margin-top: 6px; }
.prod-costx p { margin: 0; }
/* 说人话的那一段（没配 BOM / 手工价）：不要用 .empty，那个是"大块居中留白"的空态 */
.prod-costx .costx-note {
  padding: 10px 12px;
  border-left: 3px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 12.5px;
  line-height: 1.7;
}

/* 分页条：钉在表格下面（不跟着表格滚），一行放完 —— 用户口径 2026-09-15：
   商品多了不能一次全渲染，要能设每页几条 + 翻页 */
.prod-page .pt-pager {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 2px 2px 0;
  font-size: 12px;
  color: var(--ink-3);
}
.prod-page .pt-pager .pp-range b,
.prod-page .pt-pager .pp-page b { color: var(--ink-2); }
.prod-page .pt-pager .pp-range,
.prod-page .pt-pager .pp-page { font-variant-numeric: tabular-nums; }
.prod-page .pt-pager .pp-size { display: inline-flex; align-items: center; gap: 5px; flex: none; white-space: nowrap; }
.prod-page .pt-pager .pp-range,
.prod-page .pt-pager .pp-page { white-space: nowrap; }
.prod-page .pt-pager .pp-size select {
  padding: 4px 7px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.prod-page .pt-pager .pp-size select:hover { border-color: var(--ink-3); }
.prod-page .pt-pager button[disabled] { opacity: 0.45; cursor: not-allowed; }

.prod-bar {
  display: flex;
  align-items: center;
  gap: 6px 8px;
  flex-wrap: wrap;
  padding: 6px 10px; /* 工具条矮一点：一块 2 行的条从 ~95px 收到 ~70px */  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--card-shadow, var(--shadow));
}
/* 商品档案那条：**固定两行**（用户口径 2026-09-15）
     第一行 找东西：搜索 + 分类 / 属性 / 状态
     第二行 看结果 + 动手：批量操作 · 共 N 个 · 排序 · 导入导出 · 新增
   两行各自是一条 flex、内部还能再换行 —— 不会再出现"按钮被甩到左下角"那种乱换行。
   注意：这个两行布局只挂在 .is-two-rows 上 —— 采购 / 销售 / 供应商那些页也复用 .prod-bar，
   一刀切改成纵向会把它们的工具条拆散。 */
.prod-bar.is-two-rows {
  flex-direction: column;
  align-items: stretch;
  gap: 4px; /* 两行之间只留一条缝，别把整块撑高 */
}
.prod-bar .pb-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* 工具条里的控件统一 28px（按钮 26px）：一行就是一行，不靠 padding 撑高度 */
.prod-bar .sm { height: 26px; padding: 0 10px; }
/* 「列设置」是个功能入口，不是普通按钮 —— 用冷色描边 + 图标，别让人找不着 */
.prod-bar .sm.prod-col-btn {
  gap: 5px;
  border-color: var(--blue);
  color: var(--blue);
  background: var(--blue-soft);
  font-weight: 600;
}
.prod-bar .sm.prod-col-btn:hover { background: var(--surface); }
.prod-bar .pb-count,
.prod-bar .prod-count { font-size: 12px; line-height: 26px; }
.prod-bar .pb-row .link-btn { padding: 2px 6px; }
/* BOM 页那排「全部 / 成品 BOM / 瓶子 BOM」筛选（2026-09-19）：贴在搜索旁边，
   密度跟着工具条那 28px 的控件走 —— 用现成的分段控件（.tabs）皮肤，只把尺寸收一档。 */
.prod-bar .bom-kind-tabs { flex: none; margin: 0; }
.prod-bar .bom-kind-tabs button { padding: 4px 10px; font-size: 12.5px; }
.prod-bar .pb-row-filter .prod-search { flex: 0 1 320px; min-width: 200px; }
.prod-search { position: relative; display: inline-flex; align-items: center; flex: 0 1 260px; min-width: 180px; }
.prod-search-ico { position: absolute; left: 9px; display: inline-flex; color: var(--ink-3); pointer-events: none; }
.prod-search input {
  width: 100%;
  height: 28px;
  padding: 0 10px 0 28px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
}
.prod-search input:focus { outline: none; border-color: var(--blue); box-shadow: var(--glow); }
.prod-field { display: inline-flex; align-items: center; gap: 5px; }
.prod-field .pk { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }
.prod-field select {
  height: 28px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.prod-field select:hover { border-color: var(--ink-3); }
.prod-count { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
.prod-count b { color: var(--ink-2); }
/**
 * ★ 2026-09-22（用户口径）：「张丽-入库员；周娜-配货；刘红-入仓员」——
 * 作业页工具条上那句「**这活归谁**」（待入库 / 拣货出库 / 待入仓）。
 * 走一枚很轻的药丸：和旁边的「共 N 张」一个量级，不抢正文，但一眼看得见人。
 */
.wh-staff {
  flex: none;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 12px;
  white-space: nowrap;
}
.wh-staff b { color: var(--ink); font-weight: 600; }
/* 仓库列表里那一格（入库 张丽 · 配货 周娜 · 入仓 刘红） */
.wh-staff-cell { font-size: 12.5px; color: var(--ink-2); }

/* 勾选之后才出现的批量条（和流程卡那条一个样子） */
.prod-picks {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 8px 0;
  padding: 7px 11px;
  border: 1px solid var(--blue);
  border-radius: var(--radius);
  background: var(--blue-soft);
  font-size: 12px;
  color: var(--blue);
}
.prod-picks[hidden] { display: none; }
.prod-picks .pp-count b { font-variant-numeric: tabular-nums; }

/* ---- 商品表 ---- */
.pt-wrap { overflow: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.pt { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0; font-size: 12.5px; }
.pt thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-3);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-align: left;
  white-space: nowrap;
}
.pt tbody td { padding: 5px 10px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.pt tbody tr:last-child td { border-bottom: 0; }
.pt tbody tr:hover { background: var(--row-hover, var(--surface-2)); }
.pt-row.is-off { opacity: 0.62; }

.pt .pt-pick { width: 40px; }
.pt .pt-no { width: 54px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pt .pt-pic { width: 62px; }
.pt .pt-code { width: 130px; }
.pt .pt-cat { width: 110px; color: var(--ink-2); }
.pt .pt-kind { width: 92px; }
/* ★ 2026-09-22：「单位」列（用户口径：「商品档案里，再加入一个单位字段」）—— 窄窄一格就够 */
.pt .pt-unit { width: 70px; color: var(--ink-2); }
/**
 * ★ 2026-09-22（用户口径）：「**商品档案里，加入仓库数量**」——
 * 这一列是一个数，**右对齐 + 等宽数字**（一列数对齐才扫得动）；表头也跟着右对齐。
 */
.pt .pt-stock { width: 110px; }
.pt th.pt-stock, .pt td.pt-stock { text-align: right; }
.pt td.pt-stock { font-variant-numeric: tabular-nums; color: var(--ink); }
/**
 * ★ 2026-09-22（用户口径）：「**点击仓库数，会显示对应仓位及数量**」——
 * 这个数**可点**，观感和「成本价」那一格同一套：平时就是个数字（透明无边框），
 * 悬停才浮出一层浅底 + 变成主色 —— 看得出能点，又不把一列数字搅花。
 */
.pt .pt-stock-num {
  display: inline-block;
  padding: 1px 4px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.pt .pt-stock-num:hover { background: var(--surface-2); color: var(--brand); }
.pt .pt-stock-num:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.pt .pt-time { width: 140px; color: var(--ink-3); font-size: 11.5px; white-space: nowrap; }
.pt .pt-status { width: 84px; }
.pt .pt-acts { width: 56px; text-align: center; }
/* 行上的「操作」：一个 ⠿ 图标（点开才有编辑 / 启用禁用 / 删除）—— 不再摆三个文字链接占地方 */
.pt-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 26px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
}
.pt-more:hover { border-color: var(--line); background: var(--surface-2); color: var(--ink); }

/* 工具栏那一行里的三个批量操作（勾选之后才可点） */
.prod-bulk { display: inline-flex; align-items: center; gap: 6px; }
.prod-bulk .pb-count { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.prod-bulk .pb-count b { color: var(--blue); }
.prod-bulk button[disabled] { opacity: 0.45; cursor: not-allowed; }

.pt-thumb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--ink-3);
}
.pt-thumb.is-img { cursor: zoom-in; }
.pt-thumb img { width: 100%; height: 100%; object-fit: contain; } /* 同卡片：标签照片要整张看全 */

.pt-name { font-weight: 600; color: var(--ink); }
.pt-name-text {
  display: block;
  /* 用户口径：商品名称**允许自动换行**（长名字看全，别截断） */
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.35;
}
.pt-note { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 名称下面那行：规格 · 单位 · 条码（有什么画什么，都没有就整行不画） */
.pt-meta { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------------------------------------------------------------- 选子件（能搜的面板）
   原来是个原生下拉，几百个商品里翻不动；现在是一个"搜索 + 快捷筛 + 键盘"的面板。 */
.bom-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 30px;
  padding: 0 8px 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}
.bom-pick:hover { border-color: var(--ink-3); }
.bom-pick.is-empty { color: var(--ink-3); }
.bom-pick .bp-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bom-pick .bp-caret { display: inline-flex; color: var(--ink-3); transform: rotate(90deg); }
/**
 * 「选择客户」那一格：空态**本身就是搜索框**（用户口径 2026-09-20：「输入框直接用这个，
 * 不需要再出一个新的搜索框」）—— 输入框去掉边框/底色，直接嵌在这个格子里，
 * 看着还是原来那个框（左边放大镜、右边「可搜客户名 / 编号」）。
 */
.bom-pick .bp-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 12.5px;
}
.bom-pick .bp-input::placeholder { color: var(--ink-3); }
.bom-pick:focus-within { border-color: var(--blue); box-shadow: var(--glow); }
/* 选完客户之后右边那颗「换一个」 */
.bom-pick .bp-swap {
  flex: none;
  padding: 2px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}
.bom-pick .bp-swap:hover { color: var(--blue); }
.bom-edit-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.bom-edit-foot .muted { font-size: 11.5px; }

/* BOM 表单里的「母件」：和子件一样的可搜按钮 */
.bom-parent { margin: 2px 0 12px; }
.bom-parent-label { font-size: 11.5px; color: var(--ink-3); margin-bottom: 5px; }
.bom-parent-label .req { color: var(--red); margin-left: 2px; }
.bom-parent .bom-pick { max-width: 420px; }

.prp-panel {
  position: fixed;
  /* 必须**高于弹窗**（.mask 是 z-index:100）—— 这个选择器是在 BOM 弹窗里用的，
     60 的时候面板被弹窗盖住，看起来就是"点了没反应"（2026-09-15 用户报的）。
     和 who-menu / date-pop / card-menu 一个高度（240）。 */
  z-index: 240;
  width: 430px;
  max-width: calc(100vw - 24px);
  padding: 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prp-head { position: relative; display: flex; align-items: center; }
.prp-head .zm-i { position: absolute; left: 9px; color: var(--ink-3); pointer-events: none; }
.prp-head input {
  width: 100%;
  height: 30px;
  padding: 0 10px 0 30px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
}
.prp-head input:focus { outline: none; border-color: var(--blue); box-shadow: var(--glow); }
.prp-kinds { display: flex; gap: 4px; flex-wrap: wrap; }
/**
 * ★ 2026-09-25（用户口径）：「可以在搜索框内加入一个 ai 推荐选项」——
 * 搜索框下面那一排 AI 推荐（只有 AI 给出结果时才画）：一枚药丸 + 一句"推的是哪几家"。
 * 药丸走紫色，和列表里那枚「AI 推荐」标一个色系（一眼知道这排是 AI 那档）。
 */
.prp-kinds.prp-ai { align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2); margin-bottom: 6px; }
.prp-ai .prp-kind { background: var(--surface); }
.prp-ai .prp-kind.on { border-color: var(--violet); background: var(--violet-soft); color: var(--violet); }
.prp-ai .muted { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.prp-kind {
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}
.prp-kind:hover { border-color: var(--ink-3); color: var(--ink); }
.prp-kind.on { border-color: var(--blue); background: var(--blue-soft); color: var(--blue); font-weight: 600; }
/**
 * ★ 2026-09-19：面板**整块钉住高度**（头 + 可滚的列表 + 尾），列表再长也不会把面板顶出屏幕；
 * 页面一滚就自动收起（见 JS 里的 scroll 监听）—— 不然它会脱离按钮"卡"在那儿。
 */
.prp-panel { display: flex; flex-direction: column; max-height: min(78vh, calc(100vh - 20px)); }
.prp-body { flex: 1 1 auto; max-height: 44vh; overflow: auto; }
.prp-more { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 6px 10px; border-top: 1px dashed var(--line-2); }
.prp-list { display: flex; flex-direction: column; }
.prp-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 5px 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}
.prp-item:hover, .prp-item.on { background: var(--blue-soft); }
.prp-item .prp-code { flex: none; color: var(--ink-3); font-size: 11.5px; }
.prp-item .prp-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prp-item .prp-spec, .prp-item .prp-unit { flex: none; color: var(--ink-3); font-size: 11px; }
/* 客户标签（料体是给哪个客户打的样）：紫底小药丸，在选择器里一眼能把同名的料体分开 */
/* 供应商那枚（配方选料时看这支料从哪家买）：青色，和客户标签的紫色分开 */
.prp-item .prp-sup {
  flex: none; max-width: 118px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 0 6px; border-radius: 999px; background: var(--green-soft); color: var(--green); font-size: 11px;
}
.prp-item .prp-cust {
  flex: none; max-width: 118px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 0 6px; border-radius: 999px; background: var(--violet-soft); color: var(--violet); font-size: 11px;
}
.prp-empty { padding: 14px 8px; color: var(--ink-3); font-size: 12.5px; text-align: center; }

/**
 * ★ 2026-09-24（用户口径）：「每个产品在某个供应商那里下过单……后续开单选择时候，自动排序到最上面」——
 * 选供应商的面板里，**这个品下过单的那几家**排在最前面，行上缀一枚「用过 N 次」
 * （蓝 = 记录/历史，和客户标签的紫、供应商档案的绿分开），说明它凭什么叫排前面。
 */
.prp-item .prp-used {
  flex: none; max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 0 6px; border-radius: 999px; background: var(--blue-soft); color: var(--blue); font-size: 11px;
  font-weight: 600;
}
/**
 * ★ 2026-09-25（用户口径）：「把推荐的排到前面」——AI 推的那几家顶到列表最前，标是**紫色**的
 * （和"用过 N 次"那枚蓝标分得开：一个是模型推的，一个是真下过单的）。悬停写着模型给的理由。
 */
.prp-item .prp-used.is-ai { background: var(--violet-soft); color: var(--violet); }

/**
 * ★ 2026-09-24（用户口径）：「采购下单这里要有搜索功能」——
 * **供应商格子**：原来那格是原生 `<select>`（三百多家翻不动、搜不了），现在是一颗按钮，
 * 点开就是上面那个可搜索的面板（`openPartnerPick`）。样子按原来的下拉框描：
 * 整格宽、左对齐文字、右边一枚朝下的箭头；没选的时候还是黄底虚线（`.is-todo`）。
 */
.sup-cell {
  display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0;
  padding: 4px 6px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
  font: inherit; font-size: 12px; text-align: left; cursor: pointer;
}
.sup-cell:hover { border-color: var(--blue); background: var(--blue-soft); }
.sup-cell:focus { outline: none; border-color: var(--blue); background: var(--blue-soft); }
.sup-cell .sup-cell-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sup-cell .sup-cell-c { flex: none; display: inline-flex; color: var(--ink-3); }
.sup-cell.is-todo { background: var(--yellow-soft); border-style: dashed; border-color: var(--yellow); }
.sup-cell.is-todo .sup-cell-t { color: var(--ink-2); }
/**
 * 「推荐」态（2026-09-25）：格子里的值是**系统替采购填的**——这个品的默认供应商
 * （商品档案 supplier_id，从聚水潭采购记录回填）。和"还没填"（黄底虚线）要一眼分得开：
 * 它有值、是可用状态，所以走**蓝色的浅底 + 实线**，再加一枚「推荐」小标；人一选就撤掉。
 */
.sup-cell.is-rec { background: var(--blue-soft); border-color: var(--blue); }
.sup-cell.is-rec .sup-cell-t { color: var(--ink); font-weight: 600; }
.sup-cell .sup-cell-rec {
  flex: none; padding: 0 5px; border-radius: 999px;
  background: var(--surface); color: var(--blue); border: 1px solid var(--blue);
  font-size: 10px; font-weight: 650; letter-spacing: 0.02em;
}
/**
 * 「AI 推荐」态（2026-09-25）：格子里的值是**模型按已有采购关系推的**
 * （用户口径：「看看能否加入 ai 推荐……而不是用死的 1 对 1 绑定」）。
 * 和上面那档"档案里的默认供应商"要分得开（一个是查出来的、一个是推出来的），
 * 所以走**紫色**那一档 —— 悬停写着模型给的理由，点开随时能换。
 */
.sup-cell.is-ai { background: var(--violet-soft); border-color: var(--violet); }
.sup-cell.is-ai .sup-cell-t { color: var(--ink); font-weight: 600; }
.sup-cell .sup-cell-ai-tag {
  flex: none; padding: 0 5px; border-radius: 999px;
  background: var(--surface); color: var(--violet); border: 1px solid var(--violet);
  font-size: 10px; font-weight: 650; letter-spacing: 0.02em; white-space: nowrap;
}
/**
 * ★ 2026-09-25（用户口径）：「在等待 ai 推荐的时间太久，**看不出来他正在运转**，
 *   优化一下展示方式」—— AI 是推理模型、要十几秒，这期间格子必须**自己说在跑**：
 *   转圈（`.zm-spin`，由 `markAiLoading` 插进来）+ 「AI 推荐中…」+ 中性虚线底。
 *   走中性灰而不是"待填"的黄：黄是"等你去填"，这里要传的是"**正在替你算**"。
 */
.sup-cell.is-ai-loading { background: var(--surface-2); border-style: dashed; border-color: var(--line-hover, var(--line)); }
.sup-cell.is-ai-loading .sup-cell-t { color: var(--ink-3); }
.sup-cell .sup-cell-spin { flex: none; display: inline-flex; color: var(--ink-3); }
/* 转圈时那颗下箭头先藏起来 —— 一格里两个图标太挤，跑完再露出来 */
.sup-cell.is-ai-loading .sup-cell-c { display: none; }
.sup-cell[disabled] {
  background: var(--surface-2); border-style: solid; border-color: var(--line);
  color: var(--ink-3); cursor: default;
}
.sup-cell[disabled] .sup-cell-c { opacity: 0.5; }
/* 格子比表格单元高一点点时别把行撑开（明细表 / 采购申请卡内嵌表都要） */
.items-table td .sup-cell { max-width: 100%; }
/* 「显示更多」那一行（选原料一页 20 个，用户口径 2026-09-18） */
.prp-more { display: flex; justify-content: center; padding: 6px 10px; border-top: 1px dashed var(--line-2); }

.prp-foot { display: flex; align-items: center; gap: 8px; padding-top: 6px; border-top: 1px solid var(--line-2); }
.prp-foot .grow { flex: 1; }
.prp-foot .prp-count { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* 商品属性标签：一个属性一个色（色值全走语义变量） */
.tag.ok { background: var(--green-soft); color: var(--green); }
.tag.info { background: var(--blue-soft); color: var(--blue); }
.tag.warn { background: var(--yellow-soft); color: var(--yellow); }
/**
 * ★ 2026-09-19：`.tag.err` 这档**原来少了这条 CSS** —— 代码里已经在用（`tag err`）却没人定义，
 * 于是那两处渲染出来是**没有底色/颜色的裸标签**。补上（红=要处理），
 * 「该客户累计 · 欠款」也用它，和上面那颗红色的「欠款金额」对得上。
 */
.tag.err { background: var(--red-soft); color: var(--red); }
.tag.mute { background: var(--surface-3); color: var(--ink-3); }

/* ---- 弹窗里的图片行 ---- */
.pic-row { display: flex; align-items: flex-start; gap: 10px; margin-top: 12px; }
.pic-row-label { flex: none; padding-top: 6px; font-size: 12.5px; color: var(--ink-3); }

/* ---- 批量导入 ---- */
.prod-import { display: flex; flex-direction: column; gap: 10px; }
.pi-lead { margin: 0; font-size: 12.5px; color: var(--ink-2); line-height: 1.7; }
.pi-acts { display: flex; align-items: center; gap: 10px; }
.pi-preview { padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2); }
.pi-sum { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pi-chip {
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 11.5px;
}
.pi-chip b { font-variant-numeric: tabular-nums; }
.pi-chip.ok { background: var(--green-soft); color: var(--green); }
.pi-chip.info { background: var(--blue-soft); color: var(--blue); }
.pi-chip.err { background: var(--red-soft); color: var(--red); }
.pi-errs { margin-top: 8px; display: flex; flex-direction: column; gap: 3px; max-height: 200px; overflow: auto; }
.pi-err { font-size: 11.5px; color: var(--red); }

/* ---- BOM（配方）卡片 ---- */
/* 一张 BOM 一张卡：卡片头是母件，下面直接是用料表 —— 一眼看完，不用点开。 */
/* BOM 卡片网格 —— 照用户给的参考（他们老 ERP 的「Bom 设计」页）：
   一行摆几张，每张 = 左边商品图 + 右边名称/编号/分类/版本/用料 + 右上角徽标 + 右下角详情/更多 */
.bom-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 12px;
  align-content: start;
}
.bom-card {
  display: flex;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--card-shadow, var(--shadow));
}
.bom-card:hover { border-color: var(--line-2); }
.bom-thumb { flex: none; width: 64px; height: 64px; }
.bom-thumb .bt-img {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  overflow: hidden;
  color: var(--ink-3);
}
/* ★ 缩略图一律**整张显示**（用户口径 2026-09-19：「图片内显示不全」）：
   料体的图多是**包装标签照片**（横向长条），cover 会把中间一块放大、两头的名字裁掉；
   contain 让整张缩进方块里（底色 .bt-img 的 --surface-2 正好当留白），鼠标停上去还有放大预览。 */
.bom-thumb .bt-img img { width: 100%; height: 100%; object-fit: contain; display: block; }
.bom-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.bom-title { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
/* ★ 卡片名字：**长的折 2~3 行**（用户口径 2026-09-19：「名字长的，在图片内可以做成 2 行或者三行」）。
   原来是单行 + 省略号，「苏少龙-料体-香港断痒王50g」这种全被截成「…断痒」。
   最多 3 行封顶（再长就省略号），并给 2 行的最小高度 —— 名字长短不一时卡片还是一样高。 */
.bom-name {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 2.9em;
  font-weight: 600;
  color: var(--ink);
  white-space: normal;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.bom-badge {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-3);
  font-size: 11px;
  white-space: nowrap;
}
.bom-badge.ok { border-color: transparent; background: var(--green-soft); color: var(--green); }
.bom-meta {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 3px 8px;
  font-size: 12px;
}
.bom-meta .bm-k { color: var(--ink-3); white-space: nowrap; }
.bom-meta .bm-v { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 成本那一格：有价是数字，缺价写成浅红的「缺价」（别让人误以为成本是 0） */
.bom-meta .bm-v.is-missing { color: var(--red); }
.prod-page .pt .pt-cost { font-variant-numeric: tabular-nums; }
.bom-acts { margin-top: auto; display: flex; justify-content: flex-end; gap: 6px; }
/* 详情弹窗 */
.bom-detail { display: flex; flex-direction: column; gap: 12px; }
.bom-detail .bd-head { display: flex; gap: 12px; }
.bom-detail .bd-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.bom-detail .bd-name { font-weight: 600; }
.bom-items { border-top: 1px solid var(--line-2); }
.bom-items th {
  padding: 5px 6px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
  text-align: left;
}
.bom-items td { font-size: 12.5px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.bom-remark { margin-top: 2px; }
/* 成本趋势弹窗：一条零依赖的火花线（等宽字体才对齐） */
.cost-trend { display: flex; flex-direction: column; gap: 8px; }
.cost-trend .ct-spark {
  font-family: var(--mono);
  font-size: 20px;
  letter-spacing: 2px;
  color: var(--blue);
  line-height: 1.1;
  word-break: break-all;
}
/* 逐层成本表 */
.bom-cost { display: flex; flex-direction: column; gap: 8px; }
.bom-cost-sum { font-size: 13px; color: var(--ink-2); }
.bom-cost-sum .is-missing { color: var(--red); font-weight: 600; }
.bom-cost-tree td { font-size: 12.5px; }
.bom-cost-tree .tag { margin-left: 6px; }
/* 「缺价」这个小标记在好几处用（成本列 / 算料 / 逐层表） */
.is-missing { color: var(--red); }
/* 弹窗里那段"用料（子件）" */
.bom-editor { margin-top: 12px; }
.bom-editor .eyebrow { margin-bottom: 6px; }

/* 「要多少料」弹窗：上面一个数量输入，下面就地重算的结果 */
.bom-expand { display: flex; flex-direction: column; gap: 12px; }
.bom-expand-qty { max-width: 200px; }
.bom-expand-sum {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 12.5px;
}
.bom-expand-sum b { color: var(--ink); font-variant-numeric: tabular-nums; }
.bom-expand-table { margin-top: 2px; border-top: 1px solid var(--line-2); }
.bom-expand-table th {
  padding: 5px 6px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
  text-align: left;
}
.bom-expand-table td { font-size: 12.5px; color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* 窄屏：工具栏竖着排，表格留住横向滚动 */
@media (max-width: 700px) {
  .prod-bar { gap: 6px; }
  .prod-search { flex: 1 1 100%; }
  .pt { min-width: 860px; }
  /* BOM 卡片的用料表也留横向滚动：列被压扁比滚动难看得多 */
  .bom-items,
  .bom-expand-table { min-width: 520px; }
}

/* ---- 业务模块的列表页（往来单位 / 仓储 / 采购）共用的一点点 ---- */
/* 名称下面那行小字（备注 / 地址） */
.biz-name { font-weight: 600; color: var(--ink); }
.biz-sub { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 表格单元格里的几个并排动作（看库存 / 操作菜单） */
.biz-acts { display: inline-flex; align-items: center; gap: 6px; justify-content: flex-end; }

/* ---- 客户专属价（一品一客户一价格）弹窗 ---- */
/* 清单可能几十上百条：给一个自己的滚动区，别把弹窗撑过屏幕（页脚要一直看得见） */
.cprice-scroll { max-height: 320px; overflow: auto; }
/* 底下那条"选商品 → 填价 → 保存"：和列表页工具条同一个形状（.prod-bar 本来就是通用工具条） */
.cprice-editor { margin-top: 10px; }
.cprice-editor select { flex: 1 1 260px; min-width: 200px; }
.cprice-editor input[data-cprice-price] { width: 110px; flex: none; }
.cprice-editor input[data-cprice-remark] { flex: 1 1 180px; min-width: 140px; }

/* ---------------------------------------------------------------- 销售单表（2026-09-16）
   用户口径 + 参考图：**一个订单一行**，商品明细（可能好几个商品）摞在中间那一格里。
   和商品/采购那种"一行一个明细"的表不一样，所以这一套样式单独挂 .sl-*，不碰通用表格。
   宽度由列设置算出来写到 <table style="width:…">（table-layout: fixed）——
   不给宽度的话中文商品名会被压成**一个字一行**；给死了又没法让用户调列宽。 */
.sales-lines .pt-name-text { overflow-wrap: normal; word-break: keep-all; }
/**
 * 销售单列表：表格装进**一张白卡片**（用户口径 2026-09-20：「**光秃秃的太丑了**」）——
 * 以前表格直接躺在页面底色上，单子一少下面就一大片空荡荡，看着像没画完。
 * 现在：白底 + 细边 + 一点投影的卡，底部再来一行「已经到底了」收尾。
 *
 * ⚠️ 卡片**不能**加 `overflow: hidden`（圆角好看了，但会毁掉表头）：表头是 `position: sticky`，
 *    多一层滚动容器它就不粘了 —— 圆角改成让表头首末格自己带。
 */
.prod-scroll.sales-lines { padding: 0 0 14px; }
.sl-card {
  /* 至少铺满（和上面的工具条左右对齐），表格更宽时跟着表格走（由 .prod-scroll 横滚） */
  width: fit-content;
  min-width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.sl-card .sl-table thead th:first-child { border-top-left-radius: var(--radius); }
.sl-card .sl-table thead th:last-child { border-top-right-radius: var(--radius); }
/* 列表收尾那一行（单子少的时候，它让"到底了"变得看得见） */
.sl-end {
  padding: 9px 12px;
  border-top: 1px dashed var(--line-2);
  color: var(--ink-3);
  font-size: 11.5px;
  text-align: center;
}
.sl-card > .empty { margin: 28px auto; }
.sl-table { table-layout: fixed; border-collapse: separate; border-spacing: 0; }
.sl-table th { position: sticky; top: 0; z-index: 2; background: var(--surface-2); }
.sl-table th, .sl-table td { padding: 8px 10px; vertical-align: top; border-bottom: 1px solid var(--line); }
.sl-table th.sl-th-lines, .sl-table td.sl-td-lines { vertical-align: top; }
.sl-table td.sl-td-lines { padding: 8px 10px 6px; }
/* ★ 2026-09-20（用户口径）：「被驳回的单子，不要放在草稿箱，还要放在销售单内，**并标红凸显出来**」——
   整行淡红底 + 最左边一条红杠，在「全部」列表里一眼挑得出来（挂 td 上：
   通用 `tbody tr:hover` 设的是 tr 背景，会被 td 自己的背景盖住，行色稳定不闪）。 */
.sl-table tbody tr.is-rejected > td { background: var(--red-soft); }
.sl-table tbody tr.is-rejected > td:first-child { box-shadow: inset 3px 0 0 var(--red); }
.sl-table tbody tr.is-rejected .sl-td-order_status .badge { font-weight: 700; }
/* 订单号那一格窄，别让长编号把列撑开 */
.sl-td-doc_no, .sl-td-seq, .sl-td-related_no { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 「相关单号」那一格：**竖列**排，每条前面一个种类标注（项目卡 / 采购单）——
   用户口径 2026-09-16：一行一个号，前面写清是什么单，别把一堆号并排糊在一起。 */
.sl-rels { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.sl-rel { display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%; }
.sl-rel-kind { flex: none; font-size: 11px; color: var(--ink-3); }
.sl-rels a.tag { text-decoration: none; }
.sl-rel-pill {
  border: 1px solid var(--line); border-radius: 6px; background: var(--surface-2);
  padding: 1px 6px; font-size: 11.5px; font-family: var(--mono); color: var(--ink-2); cursor: pointer;
}
.sl-rel-pill:hover { border-color: var(--blue); color: var(--blue); }

/* 表头每格右边一个拖宽手柄 —— 和商品表（`.pt .pt-resize`）**同一套机制、同一个手感**，
   只是挂 .sl-table 前缀（th 本身是 sticky，已经是定位元素，绝对定位的手柄直接贴它右边）。 */
.sl-table .sl-resize {
  position: absolute; right: 0; top: 0; bottom: 0; width: 12px;
  cursor: col-resize; z-index: 4; touch-action: none; user-select: none;
}
.sl-table .sl-resize::after {
  content: ''; display: block; width: 2px; height: 62%; margin: 19% 2px 0 auto;
  border-radius: 2px; background: transparent;
  transition: background 0.12s ease, width 0.12s ease;
}
.sl-table th:hover .sl-resize::after { background: var(--line); }
.sl-table .sl-resize:hover::after { width: 3px; }
.sl-table .sl-resize:hover::after,
body.resizing .sl-table .sl-resize::after { background: var(--blue); }

/* 「展示设置」按钮 + 浮层：贴住工具栏右下（浮层本体用商品页那套 .pc-* 样式，不另造） */
.sl-col-wrap { position: relative; display: inline-flex; }
.sl-col-wrap .pc-panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; width: 280px; }

/* 商品明细那一格：几个商品摞起来 + 一条"商品数量 / 金额合计"的脚注 */
.sl-lines { display: flex; flex-direction: column; gap: 6px; }
/* 一行商品：**四块各占一个网格区**（用户口径 2026-09-17：「商品这块的排版ui再优化，
   不要显得太拥挤」）——
     [图] 商品名 规格                            × 100 盒
          已发 0 · 未发 100 · 单价 ¥23.00   [已建交付项目 KAI-012]
   名称独占一整行、子行和交付各占下排一半。原来是"名+子行"挤一列、右边再站一个长药丸，
   商品名会被挤成竖排。 */
.sl-line {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  grid-template-areas:
    'pic name qty'
    'pic sub  acts';
  column-gap: 10px;
  row-gap: 3px;
  align-items: center;
  min-width: 0;
  padding: 2px 0;
}
.sl-line + .sl-line { margin-top: 4px; padding-top: 8px; border-top: 1px dashed var(--line-2); }
.sl-line .pt-thumb { grid-area: pic; width: 40px; height: 40px; }
.sl-line-name { grid-area: name; display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; min-width: 0; }
.sl-line-name .pt-name-text { font-size: 12.5px; line-height: 1.35; }
.sl-line-sub {
  grid-area: sub; min-width: 0;
  font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sl-line-qty {
  grid-area: qty; justify-self: end;
  font-size: 12.5px; font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.sl-line-acts { grid-area: acts; justify-self: end; display: inline-flex; justify-content: flex-end; }
.sl-lines-foot {
  display: flex; align-items: center; gap: 6px;
  margin-top: 2px; padding-top: 6px; border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--ink-3);
}

/* ============================================================================
   配方（server/web/pages-formula.js）
   「配方」管**料体**（原料 + 占比），跟「商品BOM」管**成品**是两回事 —— 见
   server/modules/formula/service.js 顶部。颜色一律走 var(--x)，换主题不破相。
   ============================================================================ */
.fs-page { display: flex; flex-direction: column; gap: 10px; }
.fs-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fs-search { position: relative; display: inline-flex; align-items: center; }
.fs-search input[type='search'] { min-width: 280px; padding-left: 28px; }
.fs-search-ico { position: absolute; left: 9px; color: var(--ink-3); display: inline-flex; pointer-events: none; }
.fs-scroll { overflow: auto; }
.fs-table td { font-size: 12.5px; }
.fs-table tr.is-off td { opacity: 0.55; }
.fs-table .fs-base-name { font-weight: 600; }
.fs-table .fs-note { display: block; margin-top: 2px; color: var(--ink-3); font-size: 11.5px; }

/* 配方明细编辑器：占比是主角，数量/金额是算出来的 */
.fs-editor-wrap { margin-top: 12px; }
.fs-editor { display: flex; flex-direction: column; gap: 8px; }
.fs-items td { font-size: 12.5px; }
.fs-items input { width: 100%; }
/* 注意：这条必须**带作用域** —— 项目流程卡上也有一个 `.fs-pick`（勾选框 + 状态点，见 L3023），
   两个模块共用 `fs-` 前缀。写成裸 `.fs-pick` 时 width:100% / space-between 会漏进项目页：
   勾选框独占整行、`.fs-who`（负责人）被挤成 12px → 名字被 overflow:hidden 裁掉，看着像"负责人不见了"
   （2026-09-17 用户报的 bug，headless Chrome 实测 whoW=12px、pickW=140px）。 */
.fs-items .fs-pick {
  display: inline-flex; align-items: center; justify-content: space-between; gap: 6px;
  width: 100%; text-align: left;
}
.fs-items .fs-pick.is-empty { color: var(--ink-3); }
.fs-caret { display: inline-flex; flex: none; color: var(--ink-3); }
.fs-del {
  border: 0; background: transparent; color: var(--ink-3);
  font-size: 15px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.fs-del:hover { color: var(--red); }
.fs-items-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* 汇总条（用户表底下那三格：占比合计 / 总料价格 / 每克单价） */
.fs-sum {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  padding: 10px 12px; border-radius: var(--radius);
  background: var(--surface-2); font-size: 12.5px;
}
.fs-sum-k { color: var(--ink-3); }
.fs-sum b { font-size: 15px; font-variant-numeric: tabular-nums; }

@media (max-width: 700px) {
  .fs-search { flex: 1 1 100%; }
  .fs-search input[type='search'] { min-width: 0; width: 100%; }
  .fs-bar select { width: 100%; }
}

/* 「导入配方」：说明复用了全局的 .pi-lead / .pi-acts / .pi-preview（商品导入那套），
   这里只补预演报告自己的几个类。 */
.fs-import { display: flex; flex-direction: column; gap: 10px; }
.fs-import-res { display: flex; flex-direction: column; gap: 8px; }
.fs-import-sum { font-size: 13px; color: var(--ink-2); }
.fs-import-plan td { font-size: 12.5px; }
/* 每条配方自己的提醒（占比合计不是 100% / 档案里有重名原料 / 表里没料体名） */
.fs-warn { display: block; margin-top: 2px; color: var(--yellow); font-size: 11.5px; line-height: 1.6; }
.fs-import-unknown {
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--yellow-soft); color: var(--ink-2); font-size: 12.5px; line-height: 1.7;
}
.fs-import-errors { margin: 0; padding-left: 18px; color: var(--red); font-size: 12.5px; line-height: 1.75; }

/* 两个板块（打样 / 料体配方）：切换条沿用 .tabs.sub-tabs 那套，这里只收紧一点间距 */
.fs-page .fs-tabs { flex: none; margin: 0 0 2px; }

/* 打样编号：`年月日(6) + 打样顺序(3)` —— **系统生成、只读展示**（用户口径 2026-09-16：「这个为系统生成」） */
.fs-code-row { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.fs-code-label { font-size: 12.5px; color: var(--ink-2); font-weight: 550; }
.fs-code-inputs { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 只读的编号：虚线框 + 浅底（看着就"不是给你打的"），字号比正文大一点，方便照着念 */
.fs-code-auto {
  display: inline-flex; align-items: center; height: 34px; padding: 0 12px;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink);
  font-size: 15px; letter-spacing: 1.5px;
}
/* 料体配方：名称那一列的主次 */
.fs-table .fs-base-name { font-weight: 600; }
.fs-table .tag.warn { margin-left: 6px; }
.fs-table .tag.mute { margin-left: 6px; }

/* 「配比」那一列：一行一支料（用户口径：xxx xx%，一行一行地摆） */
.fs-ratio { display: flex; flex-direction: column; gap: 1px; }
.fs-ratio-line { display: flex; align-items: baseline; gap: 6px; font-size: 12px; line-height: 1.5; }
.fs-ratio-name {
  color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px;
}
.fs-ratio-val { color: var(--ink-3); flex: none; }

/* 「料体配方」的卡片（用户口径 2026-09-17：这里的 BOM 展示也改成卡片式）：
   复用商品BOM 那套 .bom-card 的皮。★ 卡面只摆 编号 / 每克价格 / 来自打样 / 归属 ——
   不摆配比（「不要展示配方」），所以这里没有 .fs-ratio 的样式了。

   字段区**不用商品BOM 那张 2×2 网格**（用户口径 2026-09-17：「这里还是不好看，再优化」——
   网格把编号挤成两行「CSKHJ-260 / 916006」、客户名一长就被切边）：
   改成一组一组横排（`.bm-f`），放不下就**整组**换行 —— 永远不截字、不拆编号。 */
.fs-base-card { cursor: pointer; }
.fs-base-card.is-off { opacity: 0.55; }
.fs-base-card .bom-meta {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px;
  /* 摆得下就摊开（卡比字段宽，左对齐会在右边留一大块空）；摆不下整组换行 */
  justify-content: space-between;
}
.fs-base-card .bm-f { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
.fs-base-card .bm-f .bm-v { min-width: 0; }
/* 编号独占一行：整串 + 复制键，一行摆得下（它是这张卡的"身份证"） */
.fs-base-card .bm-f.is-wide { flex: 1 1 100%; }
.fs-base-card .bom-meta .bm-v { white-space: nowrap; overflow: visible; text-overflow: clip; }
/* 客户名再长也不切：药丸里自己折行（悬停还有全名） */
.fs-base-card .bm-f .tag { white-space: normal; max-width: 100%; overflow-wrap: anywhere; }
/* 编号是这张卡的"身份证"：给它正文的颜色和字重（其它三样是辅助信息，别抢） */
.fs-base-card .fs-code-text { letter-spacing: 0.2px; color: var(--ink); font-weight: 600; }
/* 编号后面那颗复制键 */
.fs-copy {
  margin-left: 3px; padding: 0 3px; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--ink-3); cursor: pointer;
  vertical-align: middle; line-height: 1;
}
.fs-copy:hover { color: var(--blue); }
/* 行内操作按钮：横着排、不换行到第二行 */
.fs-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fs-acts .pt-more { margin-left: auto; }

/* 「从基础配方复制用料」：表单里那条提示 + 挑配方的小弹窗 */
.bom-copy-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  margin-top: 10px; padding: 8px 10px; border: 1px dashed var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.fs-rec { display: flex; flex-direction: column; gap: 8px; }
/* 「常用配方 / 全部配方」两栏（用户口径 2026-09-18：别一上来全摊开还要搜） */
.fs-rec-tabs { display: flex; gap: 6px; }
.fs-rec input[type="search"] {
  width: 100%; max-width: 320px; padding: 5px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
}
.fs-rec-box { max-height: 46vh; overflow: auto; }
.fs-rec-list { display: flex; flex-direction: column; gap: 6px; }
.fs-rec-item {
  display: grid; grid-template-columns: minmax(120px, 1fr) auto auto; gap: 10px; align-items: baseline;
  padding: 7px 10px; text-align: left; cursor: pointer;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm); background: var(--surface);
}
.fs-rec-item:hover { border-color: var(--line); background: var(--surface-2); }
.fs-rec-name { font-weight: 550; }

/* 「改配方」表单底下那条「配方历史 / 恢复」（用户口径 2026-09-17） */
.bom-hist-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  margin-top: 12px; padding: 8px 10px; border: 1px dashed var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
/* 历史弹窗的空态：还没改过配方时明说「暂无历史配方」 */
.fs-hist-empty { display: flex; flex-direction: column; gap: 6px; padding: 18px 4px; }
.fs-hist-empty-t { font-size: 15px; font-weight: 600; color: var(--ink-2); }

/* 料体卡片上「来自打样 260916006」那颗 —— 看着像值，点得动（跳去打样那张单） */
.fs-from-link {
  padding: 0 6px; border: 0; border-radius: 999px; background: var(--blue-soft); color: var(--blue);
  font: inherit; cursor: pointer; transition: background-color 150ms ease, color 150ms ease;
}
.fs-from-link:hover { background: var(--blue); color: #fff; }
.fs-from-link:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }

/* 配方历史弹窗：左栏一版一条（时间 / 几支料 / 谁改的），右栏那一版的明细 */
.fs-hist { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 12px; align-items: start; }
.fs-hist-side { display: flex; flex-direction: column; gap: 6px; max-height: 56vh; overflow: auto; }
.fs-hist-item {
  display: flex; flex-direction: column; gap: 2px; text-align: left; padding: 7px 9px;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm); background: var(--surface); cursor: pointer;
}
.fs-hist-item:hover { border-color: var(--line); background: var(--surface-2); }
.fs-hist-item.on { border-color: transparent; background: var(--blue-soft); }
.fs-hist-time { font-size: 12.5px; color: var(--ink); }
.fs-hist-sum { font-size: 12px; color: var(--ink-2); }
.fs-hist-main { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.fs-hist-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }

/* 整行可点（点进配方详情）—— 给个能点的样子 */
.fs-table tbody tr[data-fs-row],
.fs-table tbody tr[data-fs-brow] { cursor: pointer; }
.fs-table tbody tr[data-fs-row]:hover td,
.fs-table tbody tr[data-fs-brow]:hover td { background: var(--surface-2); }

/* 「打样」板块里两块（需求 / 记录）—— 用户口径 2026-09-17：
   「这个地方的 UI 不太好分清打样需求和打样记录，优化一下」。
   两块各是一张卡片 + **语义色**：需求 = 蓝（待办、要动手），记录 = 绿（做完了、只查）。
   颜色全走 var()，换主题不破相。 */
.fs-block {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.fs-block + .fs-block { margin-top: 18px; }
.fs-block-head {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  box-shadow: inset 3px 0 0 var(--line-2);
  background: var(--surface-2);
}
.fs-block-title { font-size: 13.5px; font-weight: 650; letter-spacing: 0.02em; color: var(--ink); }
.fs-block-count {
  display: inline-flex; align-items: center; height: 19px; padding: 0 8px;
  border-radius: 999px; font-size: 11.5px; font-weight: 650;
  background: var(--surface-3); color: var(--ink-2);
}
.fs-block-note { color: var(--ink-3); font-size: 11.5px; }
.fs-block-body { padding: 2px 10px 10px; }
.fs-block-body > .empty { padding: 14px 2px; }

/* 需求 = 蓝（等着打样）；记录 = 绿（打完了） */
.fs-block.is-request { border-color: var(--blue-soft); }
.fs-block.is-request > .fs-block-head { background: var(--blue-soft); box-shadow: inset 3px 0 0 var(--blue); }
.fs-block.is-request .fs-block-title { color: var(--blue); }
.fs-block.is-request .fs-block-count { background: var(--blue); color: var(--brand-ink); }

.fs-block.is-record { border-color: var(--green-soft); }
.fs-block.is-record > .fs-block-head { background: var(--green-soft); box-shadow: inset 3px 0 0 var(--green); }
.fs-block.is-record .fs-block-title { color: var(--green); }
.fs-block.is-record .fs-block-count { background: var(--green); color: var(--brand-ink); }

/* 「原料档案」那块 = violet（"档案"，跟打样的蓝 / 绿区分开）——
   里面用的是商品档案那套表（`.pt`），一格都不重画，两边看的就是同一份数据 */
.fs-block.is-material { border-color: var(--violet-soft); }
.fs-block.is-material > .fs-block-head { background: var(--violet-soft); box-shadow: inset 3px 0 0 var(--violet); }
.fs-block.is-material .fs-block-title { color: var(--violet); }
.fs-block.is-material .fs-block-count { background: var(--violet); color: var(--brand-ink); }
.fs-mat-table { min-width: 1040px; }
.fs-mat-table tbody tr[data-fs-mat-row] { cursor: pointer; }
.fs-mat-table tbody tr[data-fs-mat-row]:hover td { background: var(--row-hover); }

/* 列设置那条别在卡片头上再顶一层空隙 */
.fs-block-body .col-strip { padding-top: 0; }

/* 参考图片（用户口径 2026-09-17：打样需求可以传多张） */
/* 需求量那一行：瓶数 × 每瓶克数写成 `xx瓶-xxg/瓶`，后面跟折出来的 kg */
.fs-qty-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin: 10px 0 2px; }
.fs-qty-k { color: var(--ink-2); font-size: 12.5px; font-weight: 550; }
.fs-qty-v { font-size: 14px; letter-spacing: 0.5px; }
/* 「需求人」那格：输入框 + 「选人」同一行（wireUserPick 把它们包进 .fs-who-row） */
.fs-who-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
.fs-who-row > input { flex: 1 1 auto; min-width: 0; }
.fs-who-btn { flex: none; white-space: nowrap; }

.fs-refpics { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.fs-refpics-list { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.fs-refpic { position: relative; display: inline-block; }
.fs-refpic .fr-img,
.fs-refpic-mini .fr-img {
  display: inline-grid; place-items: center; width: 56px; height: 56px;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--surface-2); overflow: hidden;
}
.fs-refpic .fr-img img,
.fs-refpic-mini .fr-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fs-refpic-del {
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; padding: 0;
  border-radius: 999px; border: 1px solid var(--line-2); background: var(--surface);
  color: var(--ink-3); font-size: 12px; line-height: 1; cursor: pointer;
}
.fs-refpic-del:hover { color: var(--red); border-color: var(--red); }
.fs-refpic-mini { display: inline-flex; align-items: center; gap: 4px; }
.fs-refpic-mini .fr-img { width: 30px; height: 30px; }
.fs-refpic-big { gap: 8px; margin-top: 4px; }
.fs-refpic-big .fr-img { width: 72px; height: 72px; cursor: zoom-in; }
.fs-detail .fs-detail-need { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 4px; }

/* 「配方权限设置」面板：一个权限点一行，右边是该角色的小药丸勾 */
.fs-perm { display: flex; flex-direction: column; gap: 10px; }
.fs-perm-group { display: flex; flex-direction: column; gap: 4px; }
.fs-perm-row {
  display: grid; grid-template-columns: minmax(220px, 1fr) auto; gap: 8px 14px; align-items: start;
  padding: 5px 0; border-top: 1px solid var(--line-2);
}
.fs-perm-act { display: flex; flex-direction: column; gap: 1px; font-size: 12.5px; color: var(--ink-2); }
.fs-perm-roles { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
.fs-perm-role {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface-2);
  font-size: 12px; color: var(--ink-2); cursor: pointer; white-space: nowrap;
}
.fs-perm-role input { margin: 0; }
.fs-perm-role.is-admin { opacity: 0.6; cursor: default; }
.fs-perm-role:has(input:checked) { border-color: transparent; background: var(--blue-soft); color: var(--blue); }

/* 「配方权限设置（按人）」：一人一张卡，三组权限各一行小药丸 */
.fs-perm-search { display: flex; align-items: center; gap: 10px; }
.fs-perm-search input {
  flex: 1 1 auto; max-width: 260px; padding: 5px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
}
.fs-perm-users { display: flex; flex-direction: column; gap: 10px; max-height: 52vh; overflow: auto; }
.fs-perm-user {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 10px; border: 1px solid var(--line-2); border-radius: var(--radius);
  background: var(--surface);
}
.fs-perm-user-head { display: flex; align-items: baseline; gap: 8px; }
.fs-perm-gline { display: grid; grid-template-columns: 118px minmax(0, 1fr); gap: 6px 10px; align-items: center; }
.fs-perm-gk { color: var(--ink-3); font-size: 11.5px; }
.fs-perm-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.fs-perm-role.is-locked { opacity: 0.75; cursor: default; }
.fs-perm-role.is-on { border-color: transparent; background: var(--blue-soft); color: var(--blue); }
.fs-perm-role.is-locked.is-on { background: var(--surface-3); color: var(--ink-3); }

/* 配方详情弹窗（照用户那张 Excel 的版式） */
.fs-detail .fs-detail-head { align-items: center; }
.fs-detail .fs-detail-code { color: var(--ink-3); font-size: 12.5px; }
.fs-detail .fs-detail-meta { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 14px; }
.fs-detail .fs-detail-table td { font-size: 12.5px; }
.fs-detail .fs-detail-sub { display: block; color: var(--ink-3); font-size: 11px; }
.fs-detail .fs-detail-total td { border-top: 2px solid var(--line); font-weight: 600; }
/* 缺价的提醒（去补采购单价） */
.fs-detail .fs-detail-missing {
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--yellow-soft); color: var(--ink-2); font-size: 12.5px; line-height: 1.7;
}
/* 每克单价那一行要显眼 —— 做商品BOM 按克用料时要乘它 */
.fs-detail .fs-detail-pergram {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  padding: 10px 12px; border-radius: var(--radius); background: var(--surface-2);
}
.fs-detail .fs-pergram { font-size: 16px; }






/* ============================================================================
   AI 问 · 对话工作台（2026-09-17）
   ----------------------------------------------------------------------------
   只**新增类名 `ai-*`**，不改任何已有规则 —— 免得动到别人正在改的东西。
   全部用 :root 里的令牌，换主题不会破相。

   为什么需要这几条：左栏每行三个小动作（置顶/改名/删除）如果一直显示，
   一屏十行全是图标，看着乱；开场的"问候 + 大输入框"也要照 DeepSeek 那种
   居中大圆角的样子，靠行内样式写不出 :hover / :focus-within。
   ============================================================================ */

/* 左栏每一行：三个小动作平时藏起来，悬停或选中才出现 */
.ai-thread-acts { display: flex; gap: 2px; flex: none; opacity: 0; transition: opacity 0.12s ease; }
.msg-rail-item:hover .ai-thread-acts,
.msg-rail-item.active .ai-thread-acts { opacity: 1; }
.ai-thread-acts button { padding: 0 6px; height: 22px; line-height: 1; font-size: 12px; }

/* 左栏的分组标题（置顶 / 今天 / 昨天 / 7天内 / 更早） */
.ai-thread-group { padding: 12px 9px 3px; font-size: 11px; color: var(--ink-3); letter-spacing: 0.04em; }

/* 开场的问候 */
.ai-hero-title { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; }

/* 输入框：一个大圆角条，居中、有限宽（对话模式时贴底，一样是这一条） */
.ai-composer {
  display: flex; align-items: center; gap: 8px;
  width: 100%; max-width: 760px; margin: 0 auto;
  padding: 8px 8px 8px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.ai-composer:focus-within { border-color: var(--ink-3); box-shadow: var(--shadow-md); }
.ai-composer input {
  flex: 1; min-width: 0;
  border: 0; background: transparent; box-shadow: none;
  font-size: 14.5px; padding: 6px 0;
}
.ai-composer input:focus { border: 0; box-shadow: none; }

/* 开场垫在输入框下面那排「试试」：**跟输入框同一套宽度、一起居中**。
   原来它是个通栏 div、里面的 .row 又左对齐 —— 输入框是居中 760px，它却占满整栏
   1148px（1440 宽下），所以看着又宽又偏左，和上面那条输入框对不齐
   （用户 2026-09-17 截图：「试试那些信息没排版好，影响整体效果」）。
   注意：这里只管**输入框下那排**（wrapper 是 data-ai-hints-row）；
   答案卡里的同一排是 data-ai-hints，在消息流里面，不归它管。 */
[data-ai-hints-row] { max-width: 760px; margin-left: auto; margin-right: auto; }
[data-ai-hints-row] .row { justify-content: center; }

/* ---------------------------------------------------------------- 销售开单：合计 + 收款（2026-09-16）
   用户口径：「如果选了三个商品，最下面应该有总数量 总金额 已付xx钱+付款凭证图，
   方便财务审核订单」。合计是**实时**算的，已付/凭证随单一起提交。 */
/* 明细表自己那条「合计 ¥x」在这个弹窗里藏掉 —— 下面已经有更清楚的「总数量 / 总金额」，
   同一个框里出现两个"合计"会让人以为哪个才是准的（组件本身不动，采购那边照旧）。 */
.modal:has(.sl-form) [data-sales-items] .items-total { display: none; }
.sl-order-sum {
  display: flex; align-items: baseline; justify-content: flex-end; gap: 18px;
  margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line);
}
.sl-sum-item .k { margin-right: 6px; font-size: 11.5px; color: var(--ink-3); }
.sl-sum-item b { font-size: 15px; font-weight: 650; }
.sl-pay-block { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line-2); }
.sl-pay-row { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 10px 22px; margin-top: 6px; }
.sl-pay-amt { display: inline-flex; align-items: center; gap: 6px; }
.sl-pay-amt .k, .sl-pay-proof > .k { font-size: 12px; color: var(--ink-2); }
.sl-pay-amt input { width: 130px; }
.sl-pay-proof { display: inline-flex; align-items: flex-start; gap: 8px; flex: 1 1 320px; min-width: 260px; }
.sl-pay-proof > .k { flex: none; padding-top: 6px; }
/**
 * 收款小结（用户口径 2026-09-20：「**这一块信息，需要突出一点**」）——
 * 「这一单共 / 已付 / 还差」三个数原来是混在一句灰字里的，扫一眼根本抓不到；
 * 现在摆成一条带左侧强调条的小结，数字加大、还差用红、付清用绿。
 * `is-plain` = 一分钱都还没填 → 退回原来那句朴素说明（不加框，别到处抢眼）。
 */
.sl-pay-hint {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 10px;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--blue);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.sl-pay-hint.is-plain {
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: none;
}
.sl-pay-hint-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 18px; }
.sl-pay-hint-line .sp { font-size: 12.5px; color: var(--ink-2); }
.sl-pay-hint-line .sp b {
  margin-left: 3px;
  font-size: 15px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.sl-pay-hint-line .is-paid b { color: var(--green); }
.sl-pay-hint-line .is-due, .sl-pay-hint-line .is-due b { color: var(--red); }
.sl-pay-hint-line .is-done {
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--green-soft);
  color: var(--green);
  font-weight: 600;
}
.sl-pay-hint-sub { font-size: 11.5px; color: var(--ink-3); }

/* 单据详情面板里的「收款」块：应付/已付/待付 + 每一笔（含凭证缩略图，悬停可放大） */
.dp-pay { margin-top: 10px; }
.dp-pay-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.dp-pay-head .k { font-size: 11.5px; color: var(--ink-3); }
.dp-pay-head b { font-size: 14px; }
.dp-pay-list { margin-top: 6px; display: flex; flex-direction: column; gap: 6px; }
.dp-pay-item {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2);
  font-size: 12px;
}
.dp-pay-item .dp-pay-proof-pic { flex: none; width: 34px; height: 34px; border-radius: 6px; overflow: hidden; cursor: zoom-in; }
.dp-pay-item .dp-pay-proof-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ★ 2026-09-17：放大查看器里的图**太小了**（用户口径：「放大后的图片太小了」）。
   商品图很多只有 145×145，而原来的规则 `max-width: min(76vw, 680px)` / `max-height: 66vh`
   **只封顶、不放大** —— 那点尺寸的图放进大弹窗里还是小小一张，等于没放大。
   改成给一个**确定的宽度**（按视口）：小图会被拉大（会有点糊，但看得见），
   大图仍不超过 88vw / 78vh；`object-fit: contain` 保证宽高比不变形
   （超高 / 超宽的图会留白，不拉伸）。 */
/* ★ 2026-09-18：**别强制拉宽**。
   商品档案里那批包装稿源图只有 145px 高（宽的也就 525px），强制 width:760px 等于 4 倍插值 ——
   越"放大"越糊（用户反馈"图片很模糊"）。改成 max-width + width:auto：
   **大图缩到框里、小图保持原尺寸（1:1 最清楚）**。 */
.pic-viewer img {
  width: auto;
  max-width: min(88vw, 760px);
  height: auto;
  max-height: 78vh;
  object-fit: contain;
}

/* 选商品面板里的「客户标签」快捷筛（用户口径 2026-09-16：「留一个客户标签快速选择」）。
   复用 .prp-kind 那套药丸，只多一行、加个小标签和一条分隔线，和上面的属性筛分开。 */
.prp-custs { margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--line-2); align-items: center; }
.prp-cust-label { flex: none; margin-right: 2px; font-size: 11px; color: var(--ink-3); }

/* ---------------------------------------------------------------- 收货地址（2026-09-16）
   用户口径：「下单时候需要填写收货地址 / 可以自动识别 / 也可以维护到客户档案里」。
   三处界面共用一套 : 开单弹窗的地址块、客户档案的地址簿弹窗、单据详情面板那一块。 */
.sl-ship-block { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line-2); }
.sl-ship-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.sl-ship-pick { flex: 1 1 320px; min-width: 240px; }
.sl-ship-parse { display: flex; align-items: flex-start; gap: 8px; margin-top: 6px; }
.sl-ship-parse textarea { flex: 1; min-height: 46px; font: inherit; font-size: 12.5px; resize: vertical; }
.sl-ship-parse button { flex: none; margin-top: 2px; }
.sl-ship-fields { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 8px; }
.sf { display: inline-flex; align-items: center; gap: 6px; flex: 1 1 200px; min-width: 160px; }
.sf-wide { flex: 1 1 420px; }
.sf .k { flex: none; font-size: 12px; color: var(--ink-2); }
.sf input { flex: 1; min-width: 0; }
.sf-inline { flex: 1 1 100%; }
.sf-inline select { flex: 1; min-width: 0; }

/* 客户档案里的地址簿弹窗 */
.addr-list { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 14px; }
.addr-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
}
.addr-item.is-default { border-color: var(--blue); background: var(--blue-soft); }
.addr-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.addr-name { font-size: 13px; font-weight: 600; display: inline-flex; align-items: center; gap: 6px; }
.addr-phone { font-size: 12px; color: var(--ink-2); }
.addr-text { font-size: 12.5px; color: var(--ink); }
.addr-acts { flex: none; display: inline-flex; gap: 4px; }
.addr-add { margin-top: 6px; padding-top: 10px; border-top: 1px dashed var(--line-2); }
.addr-add textarea { width: 100%; min-height: 44px; font: inherit; font-size: 12.5px; resize: vertical; }
.addr-form { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 10px; }
.af { display: inline-flex; align-items: center; gap: 6px; flex: 1 1 200px; min-width: 160px; }
.af-wide { flex: 1 1 420px; }
.af .k { flex: none; font-size: 12px; color: var(--ink-2); }
.af input { flex: 1; min-width: 0; }
.af-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); }

/* 单据详情面板里的「收货地址」块 */
.dp-ship-line { display: flex; align-items: baseline; gap: 10px; margin-top: 4px; }
.dp-ship-name { font-size: 13px; font-weight: 600; }
.dp-ship-text { margin-top: 2px; font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }

/* 「客户编辑」弹窗里的收货地址块（用户口径 2026-09-17）：**只读展示 + 一个「＋ 添加地址」**
   —— 档案上那一个文本框表达不了"默认哪条、一共几条"，地址必须是一行行的数据。 */
.pf-addr { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line-2); }
.pf-addr-head { display: flex; align-items: center; gap: 8px; }
.pf-addr-head .eyebrow { margin: 0; }
.pf-addr-line { display: flex; align-items: baseline; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.pf-addr-text { flex: 1 1 auto; min-width: 0; font-size: 12.5px; }
.pf-addr-detail { margin-left: 6px; color: var(--ink-2); }

/* ============================================================================
   可配置列表格（server/web/widgets.js：colMetasOf / applyColPrefs / bindColTables）
   用户口径 2026-09-17：「配方-打样 / 采购-采购申请 / 采购-采购单 / 仓储-仓位查询 /
   仓储-待入库 / 仓储-待入仓 / 仓储-出库，这些地方的列都要允许拖拽缩放，还要有列配置」。
   浮层复用商品页那套 .pc-*；拖宽手柄和 .pt / .sl-table **同一套手感**，只是挂通用类名。
   ⚠️ 这几条必须写在文件末尾：`.items-table th { position: static }` 和它同权重，
      靠"后写的赢"才能让表头成为定位元素（手柄 absolute 才贴得住格子）。
   ============================================================================ */
.col-table { table-layout: fixed; }
.col-table th { position: relative; }
.col-table .col-resize {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 12px;
  cursor: col-resize;
  z-index: 4;
  touch-action: none;
  user-select: none;
}
.col-table .col-resize::after {
  content: '';
  display: block;
  width: 2px;
  height: 62%;
  margin: 19% 2px 0 auto;
  border-radius: 2px;
  background: transparent;
  transition: background 0.12s ease, width 0.12s ease;
}
.col-table th:hover .col-resize::after { background: var(--line); }
.col-table .col-resize:hover::after { width: 3px; }
.col-table .col-resize:hover::after,
body.resizing .col-table .col-resize::after { background: var(--blue); }
/* 藏起来的列：表头和数据一起收（`.col-off` 不参与，用 hidden 属性，语义也对） */
.col-table [hidden] { display: none; }

/* 「列设置」那条：贴表格右上角；浮层挂在它下面（和销售页 .sl-col-wrap 一个做法） */
.col-strip { position: relative; display: flex; justify-content: flex-end; align-items: center; padding: 2px 0 5px; }
.col-strip .col-btn { display: inline-flex; align-items: center; gap: 5px; }
.col-strip .pc-panel { position: absolute; right: 0; top: calc(100% + 4px); z-index: 30; width: 300px; max-height: 62vh; overflow: auto; }

/* 开单里的收货地址选择器（用户口径 2026-09-17：「这个地址选择太小了，重新优化」）。
   一条地址是「收货人 + 电话 + 省市区详细地址」，原生下拉一行塞不下 ——
   改成**整行宽的已选地址块**（收货人/电话一行、地址一行）+ 点开是**一张张地址卡**。 */
.sl-ship-pick { flex: 1 1 100%; min-width: 0; }
.sl-addr-cur {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); font: inherit; text-align: left; cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.sl-addr-cur:hover { border-color: var(--ink-3); box-shadow: var(--shadow); }
.sl-addr-cur-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sl-addr-meta { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; }
.sl-addr-meta .mono { color: var(--ink-2); font-size: 12px; }
.sl-addr-text { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; }
.sl-addr-more {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; color: var(--ink-3);
}
.sl-addr-more svg { display: block; }
.sl-addr-list {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 6px; padding: 6px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2);
  max-height: 260px; overflow: auto;
}
.sl-addr-list[hidden] { display: none; }
.sl-addr-card {
  display: flex; flex-direction: column; gap: 2px; width: 100%;
  /* ⚠️ 全局 `button` 那条有 align-items:center / white-space:nowrap —— 这里必须显式覆盖，
     否则卡片里的文字会缩成一小坨**居中**，地址还不会换行（2026-09-17 实测踩到） */
  align-items: stretch; white-space: normal;
  padding: 7px 10px; border: 1px solid transparent; border-radius: 8px;
  background: var(--surface); color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.sl-addr-card:hover { border-color: var(--line); }
.sl-addr-card.on { border-color: var(--blue); background: var(--blue-soft); }
.sl-addr-card.is-none { background: transparent; }
.sl-addr-card.is-none:hover { background: var(--surface); }
.sl-addr-card.is-none .sl-addr-meta b { font-weight: 500; color: var(--ink-2); }
.sl-addr-card.is-none .sl-addr-text { color: var(--ink-3); }
/* 头部这一块在"手填"状态下的样子：没选中地址簿哪条时，标题写「下面手填（不用地址簿）」 */
.sl-addr-cur.is-manual .sl-addr-meta b { font-weight: 550; color: var(--ink-2); }

/* ---------------------------------------------------------------- 商品标签（待入库验收时打印）
   用户口径 2026-09-17：「打印商品标签…具体样式我回头再完善，留一个编辑入口」。
   所以这里只给一套**能用的最小样式**：标签的宽高由 JS 按档位给 mm（行内 style），
   这一段管字号、层级和条码占位框。要改样子就改这一段；
   要改"印哪些字段"走界面上那个「标签样式」入口（存在本机，不用改代码）。 */
.label-sheet { display: flex; flex-wrap: wrap; gap: 6mm; padding: 4px; align-items: flex-start; }
.label {
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  padding: 1.5mm;
  box-sizing: border-box;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 0.8mm;
  justify-content: center;
  background: var(--surface);
  color: var(--ink);
}
/* 二维码：默认不写死尺寸，交给下面的毫米规则（`qrSvgHtml` 只有给了 px 才写行内尺寸） */
.qr-code { display: block; }
.label-name { font-size: 11px; line-height: 1.25; font-weight: 600; max-height: 2.5em; overflow: hidden; }
.label-code { font-family: var(--mono); font-weight: 700; letter-spacing: 0.06em; word-break: break-all; }
.label-spec { font-size: 10px; color: var(--ink-2); }
.label-small { font-size: 9px; color: var(--ink-3); }
.label-sm .label-code { font-size: 12px; }
.label-md .label-code { font-size: 15px; }
.label-lg .label-code { font-size: 19px; }
/**
 * ★ 2026-09-25（用户口径 + 实物标签照片）：标签上排 = **二维码 + 仓位码白框**。
 *   二维码里放商品编号（`web/qr.js` 自己算的，扫码枪扫得动）；白框里的仓位码
 *   要等入仓人员绑好仓位才有，没有就空着（虚线框 = 手写位）。
 */
.label-top { display: flex; align-items: stretch; gap: 1.5mm; }
/* 二维码按标签尺寸给毫米（照片里大约占标签宽度的 1/3、高度的一半）——
   50×30mm 那张：12mm 的码 + 名称 + 规格 + 时间正好放得下，不会被 `overflow:hidden` 切掉。 */
.label-top > svg.qr-code { flex: none; width: 12mm; height: 12mm; }
.label-sm .label-top > svg.qr-code { width: 10mm; height: 10mm; }
.label-lg .label-top > svg.qr-code { width: 16mm; height: 16mm; }
.label-bin {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 8mm;
  border: 1px dashed var(--line-2);
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 12.5px;
  letter-spacing: 0.04em;
  color: var(--ink);
  padding: 1mm;
}
.label-bin.has-code { border-style: solid; }
/**
 * 采购单号（用户口径 2026-09-25：「入库的标签上标记上对应的采购单号」）——
 * 比名称小一号、等宽、加粗一档：仓库对单时要一眼看到，又不抢商品名的位置。
 */
.label-po { font-size: 10px; font-weight: 700; font-family: var(--mono); letter-spacing: 0.01em; }
.label-time { font-size: 9px; color: var(--ink-3); font-family: var(--mono); }
/**
 * 打印预览的那张「白纸」（用户口径 2026-09-25：「现在还没链打印机，所以点打印，选预览效果」）。
 * 尺寸照 A4 的可打印区给个样子（210mm 宽），标签照真实毫米排上去，虚线就是裁切线。
 */
.label-paper {
  width: 210mm;
  max-width: 100%;
  min-height: 297mm;
  margin: 0 auto;
  padding: 6mm;
  box-sizing: border-box;
  background: #fff;
  color: #111;
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
}
.label-paper .label { background: #fff; color: #111; }
/**
 * ★ 2026-09-25（用户口径：「在这加入一个标签编辑样式的地方」）——
 * 标签样式编辑器：左边勾「印什么」，右边**当场出效果**。
 * 窄屏（手机 / PDA）自动堆成一列，别硬挤两栏。
 */
.label-style-editor { display: grid; grid-template-columns: minmax(220px, 1fr) minmax(240px, 1.2fr); gap: 16px; align-items: start; }
/* 「标签上印什么」那行小标题：上面那个尺寸下拉别贴太紧 */
.label-style-form .eyebrow { margin-top: 6px; }
.label-style-editor .label-style-preview {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px;
  min-height: 40mm;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.label-style-editor .label-style-preview .label-sheet { justify-content: center; }
@media (max-width: 560px) {
  .label-style-editor { grid-template-columns: 1fr; }
}
.label-barcode { display: flex; flex-direction: column; gap: 1mm; }
.label-barcode-box {
  border: 1px dashed var(--line-2);
  border-radius: 3px;
  text-align: center;
  font-size: 9px;
  color: var(--ink-3);
  padding: 3px 0;
}
/* 打印时把虚线框换成实线（虚线在纸上很糊） */
@media print {
  .label { border: 1px solid #ddd; }
  .label-bin { border: 1px solid #ddd; }
  .label-paper { border: 0; box-shadow: none; }
}

/* 待入库那种"一个商品一行"的格子（商品列 / 采购数量列）。
   两列都用 .ib-lines，**行高故意锁死成一样** —— 这样"第 N 行商品"和"第 N 行数量"
   看着就是一行对一行（用户口径 2026-09-17：「不要合并总数，要单独对应」）。 */
.ib-lines { display: flex; flex-direction: column; gap: 2px; }
/**
 * ★ 2026-09-22（用户口径）：「仓储的待入库界面，**显示加上图片**」——
 * 商品那一格每行前面配一张 34px 缩略图（和采购单/明细表那几处一个尺寸），
 * 所以每一行的**最小高度**提到 34px；
 * 这一条是 `.ib-lines` 共用的（采购数量 / 收货进度 / 供应商那几格也是它），
 * 于是那几列的每一行**跟着一样高**——"第 N 行对第 N 行"照旧成立。
 */
.ib-lines > div { display: flex; align-items: center; gap: 8px; min-height: 34px; line-height: 1.45; }
.ib-lines .pt-thumb { width: 34px; height: 34px; flex: none; }
.ib-lines-num { align-items: flex-end; }
.ib-lines-num > div { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ============================================================================
   设备模拟窗（server/web/pages-device.js）
   用户口径 2026-09-17：侧栏空档放两个可移动的浮动窗口 —— 模拟 PDA 把枪 / 手机版，
   后续开发 PDA 版和手机版用。
   两层：`.side-dev` 是侧栏那两颗入口；`.fw` 是浮动窗（可拖可缩，没有遮罩），
   里面 `.pda` / `.ph` 是两块"设备屏幕"的版式。颜色一律走 var(--x)。
   ============================================================================ */

/* ---------------------------------------------------------------- 侧栏入口 */
.side-dev { padding: 2px 10px 10px; border-top: 1px solid var(--line-2); }
.side-dev-head {
  padding: 9px 10px 4px;
  color: var(--ink-3);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.08em;
}
.side-dev-btn {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.side-dev-btn:hover { background: var(--surface-2); color: var(--ink); }
.side-dev-btn .ico { display: inline-flex; width: 18px; justify-content: center; opacity: 0.85; flex: none; }
.side-dev-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-dev-tag {
  flex: none;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 10px;
  color: var(--ink-3);
}

/* ---------------------------------------------------------------- 浮动窗本体
   ⚠️ z-index 压在弹窗（.mask，150+）和 toast（200）下面 —— 开着设备窗也要能正常开弹窗看详情。 */
#float-root { position: fixed; inset: 0; z-index: 90; pointer-events: none; }
#float-root > * { pointer-events: auto; }
.fw {
  position: fixed;
  display: flex;
  flex-direction: column;
  min-width: 268px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.fw-head {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding: 7px 8px 7px 11px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  cursor: grab;
  user-select: none;
}
.fw-head:active { cursor: grabbing; }
.fw-ico { display: inline-flex; color: var(--ink-2); }
.fw-title { font-size: 12.5px; font-weight: 650; white-space: nowrap; }
.fw-sub { font-size: 11px; color: var(--ink-3); white-space: nowrap; }
.fw-btn {
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  padding: 2px 6px;
  color: var(--ink-3);
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}
.fw-btn:hover { background: var(--surface-3); color: var(--ink); }
.fw-x { display: inline-flex; align-items: center; padding: 3px; }
.fw-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.fw-resize { position: absolute; right: 0; bottom: 0; width: 20px; height: 20px; cursor: nwse-resize; z-index: 2; }
.fw-resize::after {
  content: '';
  position: absolute;
  right: 4px;
  bottom: 4px;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--line);
  border-bottom: 2px solid var(--line);
  border-radius: 0 0 3px 0;
}

/* 手机：深色机身 + 圆角屏 */
.fw.is-phone .fw-body { padding: 12px; background: var(--ink); }
.ph {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-radius: 26px;
  background: var(--bg);
  overflow: hidden;
}
/* PDA：机身更方、底下留一条"扳机/键盘"的位置 */
.fw.is-pda .fw-body { padding: 10px 10px 0; background: var(--ink); }
.pda {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-radius: 12px 12px 0 0;
  background: var(--bg);
  overflow: hidden;
}

/* ---------------------------------------------------------------- PDA 屏（把枪） */
.pda-top {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
  padding: 8px 12px;
  background: var(--ink);
  color: var(--surface);
  font-size: 12px;
}
.pda-wh { font-weight: 650; }
.pda-net { font-size: 11px; opacity: 0.75; }
.pda-seg { display: flex; gap: 6px; flex: none; padding: 8px 10px 6px; }
.pda-seg-btn {
  flex: 1;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 6px 0;
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.pda-seg-btn.active { background: var(--blue-soft); border-color: var(--blue); color: var(--blue-deep); font-weight: 650; }
.pda-seg-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.pda-scan {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: none;
  margin: 0 10px;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-3);
}
.pda-scan input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  outline: none;
}
.pda-msg { flex: none; margin: 8px 10px 0; padding: 6px 9px; border-radius: var(--radius-sm); background: var(--blue-soft); color: var(--blue-deep); font-size: 11.5px; line-height: 1.5; }
.pda-msg.is-err { background: var(--red-soft); color: var(--red); }
.pda-bin-hit { flex: none; margin: 7px 10px 0; font-size: 11.5px; color: var(--green); }
.pda-list { flex: 1; min-height: 0; overflow: auto; padding: 8px 10px 10px; }
.pda-list-head { padding: 2px 2px 6px; font-size: 11px; color: var(--ink-3); }
.pda-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-bottom: 6px;
  padding: 8px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.pda-row:hover { border-color: var(--blue); }
.pda-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pda-row-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pda-row-sub { font-size: 10.5px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pda-row-qty { flex: none; font-size: 14px; font-weight: 650; }
.pda-row-qty i { font-style: normal; font-size: 10.5px; color: var(--ink-3); margin-left: 3px; }
.pda-empty { flex: 1; display: flex; align-items: center; justify-content: center; padding: 20px; text-align: center; color: var(--ink-3); font-size: 12px; line-height: 1.6; }
.pda-pick { flex: 1; min-height: 0; overflow: auto; padding-bottom: 10px; }
.pda-card { margin: 10px; padding: 10px 11px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.pda-card-name { font-size: 14px; font-weight: 650; }
.pda-card-sub { margin-top: 2px; font-size: 10.5px; color: var(--ink-3); }
.pda-kv { display: flex; align-items: baseline; justify-content: space-between; margin-top: 6px; font-size: 12px; color: var(--ink-2); }
.pda-kv b { font-size: 13px; color: var(--ink); }
.pda-qty { display: flex; align-items: center; gap: 8px; margin: 10px; }
.pda-qty-k { font-size: 12px; color: var(--ink-2); }
.pda-qty input {
  flex: 1;
  min-width: 0;
  padding: 8px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.pda-step {
  width: 38px;
  height: 36px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.pda-step:hover { background: var(--surface-2); }
.pda-acts { display: flex; flex-direction: column; gap: 7px; margin: 4px 10px 0; }
.pda-btn {
  width: 100%;
  padding: 11px 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}
.pda-btn.primary { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 650; }
.pda-btn.primary:disabled { opacity: 0.6; cursor: default; }
.pda-foot {
  flex: none;
  margin-top: auto;
  padding: 8px 12px 10px;
  background: var(--ink);
  color: var(--surface);
  font-size: 10.5px;
  text-align: center;
  opacity: 0.9;
}

/* ---------------------------------------------------------------- 手机屏 */
.ph-status { display: flex; align-items: center; gap: 6px; flex: none; padding: 9px 16px 2px; font-size: 11px; color: var(--ink-2); }
.ph-sig { letter-spacing: 1px; }
.ph-head { display: flex; align-items: center; gap: 8px; flex: none; padding: 6px 16px 12px; }
.ph-hi b { display: block; font-size: 16px; }
.ph-hi span { font-size: 11px; color: var(--ink-3); }
.ph-bell {
  width: 30px;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.ph-scroll { flex: 1; min-height: 0; overflow: auto; padding-bottom: 10px; }
.ph-cards { display: flex; gap: 8px; padding: 0 14px; }
.ph-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.ph-card .k { font-size: 11px; color: var(--ink-3); }
.ph-card .v { font-size: 21px; font-weight: 650; line-height: 1.15; }
.ph-card .s { font-size: 10.5px; color: var(--ink-3); }
.ph-sec { padding: 12px 16px 6px; font-size: 10.5px; font-weight: 650; letter-spacing: 0.08em; color: var(--ink-3); }
.ph-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; padding: 0 10px; }
.ph-app {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 8px 2px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.ph-app:hover { background: var(--surface-2); }
.ph-app-ico {
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--surface);
  color: var(--blue);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ph-list { display: flex; flex-direction: column; gap: 6px; padding: 0 12px; }
.ph-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ph-item:hover { border-color: var(--blue); }
.ph-item-ico { flex: none; color: var(--blue); display: inline-flex; }
.ph-item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ph-item-main b { font-size: 12.5px; font-weight: 600; }
.ph-item-main i { font-style: normal; font-size: 11px; color: var(--ink-3); }
.ph-tabbar { flex: none; display: flex; border-top: 1px solid var(--line); background: var(--surface); }
.ph-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 7px 0 3px;
  border: 0;
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: 10px;
  cursor: pointer;
}
.ph-tab.active { color: var(--blue); }
.ph-home { flex: none; height: 16px; display: flex; align-items: center; justify-content: center; background: var(--surface); }
.ph-home::after { content: ''; width: 96px; height: 4px; border-radius: 999px; background: var(--line); }

/* ============================================================================
   扫码报工（server/web/pages-work.js + modules/work）
   用户口径 2026-09-17：车间主管「扫任务卡 → 扫工牌 → 输数量」。
   左栏是任务卡（配货生成的领料单），右栏是三步报工台 + 今天的流水。
   三个输入框都要**大、好点**（车间里手是脏的），焦点一路自动往下走。
   ============================================================================ */
.wk-page .wk-body {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(320px, 1.15fr) minmax(360px, 1fr);
  gap: 12px;
}
.wk-col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.wk-col-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line-2);
  background: var(--surface-2);
  font-size: 12.5px;
  font-weight: 650;
}
.wk-col-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 10px; }

/* 车间切换 */
.wk-seg { display: inline-flex; gap: 4px; padding: 2px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-2); }
.wk-seg-btn {
  border: 0;
  border-radius: 999px;
  background: transparent;
  padding: 4px 12px;
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.wk-seg-btn:hover { color: var(--ink); }
.wk-seg-btn.active { background: var(--surface); color: var(--blue-deep); font-weight: 650; box-shadow: var(--shadow); }

/* 任务卡 */
.wk-cards { display: flex; flex-direction: column; gap: 8px; }
.wk-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  padding: 10px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.wk-card:hover { border-color: var(--blue); }
.wk-card.is-picked { border-color: var(--blue); background: var(--blue-soft); }
.wk-card-top { display: flex; align-items: center; gap: 7px; }
.wk-card-no { font-size: 12.5px; font-weight: 650; }
.wk-card-qty { font-size: 12px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.wk-card-name { font-size: 13.5px; font-weight: 600; }
.wk-card-sub { font-size: 11px; color: var(--ink-3); }

/* 三步报工台 */
.wk-panel { padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.wk-step { display: flex; gap: 10px; }
.wk-step.is-off { opacity: 0.5; }
.wk-step-no {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 11.5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.wk-step-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.wk-step-title { font-size: 12.5px; font-weight: 650; color: var(--ink-2); }
.wk-scan {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-3);
}
.wk-scan:focus-within { border-color: var(--blue); }
.wk-scan input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  outline: none;
}
.wk-hint { font-size: 11.5px; color: var(--ink-3); line-height: 1.5; }
.wk-picked {
  padding: 8px 10px;
  border: 1px solid var(--blue);
  border-radius: var(--radius-sm);
  background: var(--blue-soft);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.wk-picked-no { font-size: 12.5px; font-weight: 650; color: var(--blue-deep); }
.wk-picked-name { font-size: 13px; }
.wk-picked-sub { font-size: 11.5px; color: var(--ink-2); }
.wk-worker { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 13px; }
.wk-worker-name { font-weight: 650; }
.wk-pick { align-self: flex-start; }
.wk-qty { display: flex; align-items: center; gap: 8px; }
.wk-qty input {
  flex: 1;
  min-width: 0;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 17px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.wk-step-btn {
  flex: none;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
}
.wk-step-btn:hover { background: var(--surface-2); }
.wk-submit {
  width: 100%;
  padding: 12px 0;
  border: 1px solid var(--brand);
  border-radius: var(--radius-sm);
  background: var(--brand);
  color: #fff;
  font: inherit;
  font-size: 14.5px;
  font-weight: 650;
  cursor: pointer;
}
.wk-submit:disabled { opacity: 0.45; cursor: not-allowed; }
.wk-msg {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--blue-soft);
  color: var(--blue-deep);
  font-size: 12px;
  line-height: 1.55;
}
.wk-msg.is-warn { background: var(--yellow-soft); color: var(--ink); }
.wk-note { font-size: 11px; color: var(--ink-3); line-height: 1.5; }

/* 今天的流水 */
.wk-day { border-top: 1px solid var(--line-2); padding: 10px 12px 12px; }
.wk-day-head { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); }
.wk-reps { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
.wk-rep { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.wk-rep-time { color: var(--ink-3); font-size: 11.5px; }
.wk-rep-main { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; overflow: hidden; }
.wk-rep-qty { font-weight: 650; }
.wk-rep-qty i { font-style: normal; font-size: 10.5px; color: var(--ink-3); margin-left: 2px; }
.wk-rep-card { flex: none; }

/* 窄屏：一栏到底（车间里也可能拿手机看） */
@media (max-width: 900px) {
  .wk-page .wk-body { grid-template-columns: 1fr; }
  .wk-col { max-height: 46vh; }
  .wk-col-panel { max-height: none; }
}

/* 「建交付项目」按钮 + 已建成的项目药丸（用户口径 2026-09-17：「建立交付项目不够显眼」）。
   原来按钮是 ghost（透明底），混在「已发 0 · 未发 100 · 单价 ¥23」那堆灰字里看不见 ——
   按钮换成 primary-soft（浅蓝底主色字，和「＋ 新建销售单」同档）并加大一点点，
   已建成的项目号也做成实心药丸。 */
.sl-convert-btn { flex: none; }
.sl-proj-pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  background: var(--green-soft); color: var(--green);
  border: 1px solid transparent;
  font-size: 12px; font-weight: 600; text-decoration: none; white-space: nowrap;
}
.sl-proj-pill:hover { border-color: currentColor; }

/* 「新建销售单」弹窗顶上那句「还有 N 张草稿」（用户口径 2026-09-17：「新建销售单里还要有
   查看草稿功能」）—— 点开"新建"才想起上回填了一半，这里一句话就能跳过去接着填。 */
.sl-draft-hint {
  margin: 0 0 10px; padding: 7px 10px;
  border: 1px dashed var(--line); border-radius: var(--radius-sm); background: var(--surface-2);
  font-size: 12.5px; color: var(--ink-2);
}
.sl-draft-hint b { color: var(--ink); }

/* 「草稿箱」是个独立视图，不是状态筛里的一档（用户口径 2026-09-17：「状态里不含草稿」）——
   进去之后工具栏那个位置显示这个静态标记，右边是「← 回到销售单」。 */
.sl-draft-mode {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--yellow-soft, var(--surface-2)); color: var(--ink);
  font-size: 12.5px; font-weight: 600;
}
.sl-draft-mode svg { display: block; opacity: 0.7; }

/* 「项目卡被删除」那一格（用户口径 2026-09-17）：说清"当初建的卡没了"，并**照旧给建按钮** ——
   两个都要画，不然人不知道自己没建过、还是建过被删了。 */
.sl-proj-gone { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.sl-proj-gone-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  background: var(--surface-2); color: var(--ink-3);
  border: 1px dashed var(--line);
  font-size: 11.5px; white-space: nowrap;
}
.sl-proj-gone-tag svg { display: block; opacity: 0.7; }
/* 项目卡已作废（不是"已建交付项目"）—— 用中性灰虚线，别用绿色（绿色是"好好的"） */
.sl-proj-gone-tag.is-cancelled { background: var(--surface-2); color: var(--ink-3); border-style: solid; }

/* 相关单号里的药丸（2026-09-17 统一）：项目卡**还活着**才是绿的；
   作废的、被删掉的一律中性灰 + 一个小标，别拿"好好的"那个绿去骗人。 */
.sl-rel-flag {
  margin-left: 5px; padding: 1px 5px; border-radius: 999px;
  background: var(--surface-3, var(--surface-2)); color: var(--ink-3);
  font-size: 10px; font-weight: 500;
}
.sl-rel-pill.is-dead { background: var(--surface-2); color: var(--ink-3); text-decoration: line-through; }
.sl-rel-pill.is-dead .sl-rel-flag { text-decoration: none; }
.sl-rel-pill.is-gone { background: transparent; color: var(--ink-3); border: 1px dashed var(--line); }
/* 项目卡那一颗现在是 <a>（点开项目页），药丸样式要能压在链接默认样式上 */
a.sl-rel-pill { display: inline-flex; align-items: center; text-decoration: none; }

/* ---- 「来源销售单」块（订单审核 / 跟单的详情中栏）----
   用户口径 2026-09-17：订单审核的卡要能看到关联销售单号 / 商品信息 / 应付实付 / 付款凭证 */
.dp-sales { display: flex; flex-direction: column; gap: 10px; margin: 2px 0 6px; }
.dp-sales .is-hot { color: var(--red); }
.dp-proofs-box { margin-top: 2px; }
.dp-proofs { display: flex; flex-wrap: wrap; gap: 8px; }
.dp-proof {
  width: 92px;
  height: 92px;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  cursor: zoom-in;
}
.dp-proof.is-file {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  font-size: 12px;
  text-align: center;
  word-break: break-all;
  cursor: pointer;
}

/* ---- 财务审核那一屏（`panel: 'finance'`）----
   用户口径 2026-09-17：财务审核的中栏看的是**那张订单的事**（订单详情 / 付款金额 / 付款凭证），
   不是待办自己那串字段 */
.dp-finance { display: flex; flex-direction: column; gap: 10px; }
.dp-finance .dp-items-head { align-items: baseline; }

/* ---- 财务审核：金额三块 + 订单明细表 + 付款凭证 ----
   用户口径 2026-09-18：
   · 「应付 实付 待付 的颜色要相似，容易看错」→ 三块各给底色 + 语义色，远看分得清
   · 「订单明细表的名字要全：序号 图片 产品名称 单价 数量 总价」
   · 「付款凭证这一整块的 ui 太垃圾」→ 改成凭证卡片（缩略图 + 名称 + 谁传的/什么时候） */
.dp-money { display: flex; flex-wrap: wrap; gap: 8px; }
.dp-money-cell {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
}
.dp-money-k { font-size: 11.5px; color: var(--ink-3); }
.dp-money-cell b { font-size: 14px; }
.dp-money-cell.is-paid { background: var(--green-soft); border-color: transparent; }
.dp-money-cell.is-paid .dp-money-k { color: var(--green); }
.dp-money-cell.is-paid b { color: var(--green); }
.dp-money-cell.is-due { background: var(--red-soft); border-color: transparent; }
.dp-money-cell.is-due .dp-money-k { color: var(--red); }
.dp-money-cell.is-due b { color: var(--red); }
.dp-money-cell.is-clear { background: var(--green-soft); border-color: transparent; }
.dp-money-cell.is-clear .dp-money-k, .dp-money-cell.is-clear b { color: var(--green); }

/**
 * 「该客户累计」那一行（用户口径 2026-09-19：「每个客户总订单金额 实付多少钱都要做标签
 * 记录下来，后续开发财务系统用」）。
 *
 * 摆在三个金额格子**下面**、用一行小标签 —— 它讲的是"这个客户整体"，不是"这一单"，
 * 所以要和上面那三格在视觉上分开（否则财务一眼扫过去会把两套数看混，
 * 正是 2026-09-18 那次的教训）。只准用令牌，换主题不能破相。
 */
.dp-cust-total {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
}
.dp-cust-k { font-size: 11.5px; color: var(--ink-3); }
.dp-cust-total .tag { display: inline-flex; align-items: baseline; gap: 4px; }
.dp-cust-total .tag b { font-weight: 650; }

.dp-fin-table td { vertical-align: middle; }
.dp-fin-name { font-size: 13px; line-height: 1.45; word-break: break-word; }
.dp-fin-pic {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  cursor: zoom-in;
  display: inline-block;
}
.dp-fin-pic.is-none {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-3);
  cursor: default;
}

.dp-proofs { display: flex; flex-wrap: wrap; gap: 10px; }
.dp-proof-card {
  margin: 0;
  width: 116px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.dp-proof-thumb {
  display: block;
  width: 100%;
  height: 88px;
  object-fit: cover;
  background: var(--surface-2);
  cursor: zoom-in;
  border: 0;
}
.dp-proof-thumb.is-file {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 6px;
  font-size: 11px;
  color: var(--ink-2);
  text-decoration: none;
  word-break: break-all;
  text-align: center;
}
.dp-proof-cap { padding: 5px 7px; display: flex; flex-direction: column; gap: 1px; }
.dp-proof-name {
  font-size: 11.5px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dp-proof-meta { font-size: 10.5px; color: var(--ink-3); }
.dp-proof-empty {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--ink-2);
}
.dp-proof-empty b { display: block; font-size: 12.5px; color: var(--ink); margin-bottom: 2px; }
.dp-proof-empty-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-3);
  flex: none;
}

/* ============================================================================
   「金额」这一组：订单总金额 / 应付金额 / 实付金额 / 待付金额
   （用户口径 2026-09-17：「这里的ui也是太相似，容易看花眼，重新优化ui」）

   两个手段：① 用两道竖线把这四栏**框成一组**，一眼看得出"这是钱那一段"；
            ② 四栏本身分出主次（见 pages-sales.js 里 salesCellHtml 的注释）。
   只借现成的 sl-th-* / sl-td-* 类，不动 DOM 结构。
   ============================================================================ */
.sl-table th.sl-th-doc_amount,
.sl-table td.sl-td-doc_amount { border-left: 1px solid var(--line-2); }
.sl-table th.sl-th-unpaid,
.sl-table td.sl-td-unpaid { border-right: 1px solid var(--line-2); }
/* 这一组的表头统一压深一点（和右边"发货 / 状态"那些非金额列拉开） */
.sl-table th.sl-th-doc_amount,
.sl-table th.sl-th-payable,
.sl-table th.sl-th-paid,
.sl-table th.sl-th-unpaid { color: var(--ink-2); font-weight: 600; }

.sl-money { font-variant-numeric: tabular-nums; }
/* 这单多大 —— 最重的一栏，读账先看它 */
.sl-money-total { color: var(--ink); font-weight: 700; }
/* 已经收到的钱：绿（和「已结清」那个绿是同一个语义） */
.sl-money.is-paid { color: var(--green); }
/* 还欠的钱：琥珀 + 加粗 —— 整个列表里最该被"看见"的数 */
.sl-money.is-due { color: var(--yellow); font-weight: 700; }
/* 0 一律压暗：一串 ¥0.00 不该跟真数字抢注意力 */
.sl-money.is-zero { color: var(--ink-3); font-weight: 400; }

/* ---- 改分批发货计划（跟单卡上的「改计划」）---- */
.sp-form { display: flex; flex-direction: column; gap: 10px; }
.sp-rows { display: flex; flex-direction: column; gap: 6px; }
.sp-row {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) 96px 120px 72px 40px;
  gap: 6px;
  align-items: center;
}
.sp-no { color: var(--ink-3); font-size: 12px; text-align: right; }
.sp-in {
  padding: 6px 8px;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  min-width: 0;
}
.sp-qty { text-align: right; font-variant-numeric: tabular-nums; }

/* ---- 跟单卡中栏：分批发货计划 + 备产三块进度（2026-09-18）----
   每地址一张出库单之后，那一行要挂得住它的出库单号；下面再摆「库存 / 采购 / 车间」三块，
   跟单一眼能判断"要不要采购、什么时候配货、催哪个车间"。全部只用主题令牌。 */
.dp-tracking { display: flex; flex-direction: column; gap: 14px; }
/**
 * 中栏几块（订单详情 · 库存 · 采购 · 车间）**各自成一张卡片** ——
 * 用户口径 2026-09-18：「库存 采购进度 车间进度要分区明显」。
 * ★ 2026-09-19：卡片样式统一提到 `.dp-form .dp-items`（见「中栏：商品明细」那一段），
 *   这样**别的步骤**（采购 / 入库 / 发货…）的分区也一起受益，不再只有跟单卡有卡片底。
 */
.dp-tracking .dp-items-table { font-size: 12.5px; background: var(--surface); }
.dp-tracking .dp-items-table td { background: var(--surface); }
/* 「备注 / 特殊要求」两行：前面挂一枚「订单 / 跟单」的小标签，看得出是谁写的 */
.dp-note { display: flex; align-items: baseline; gap: 8px; }
.dp-note-who {
  flex: 0 0 auto;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 11px;
}
/* 催单可以催多次：按钮旁边写「已催 N 次」 */
.dp-nudge-count { color: var(--ink-3); font-size: 11.5px; margin-left: 2px; white-space: nowrap; }
.dp-tracking .dp-items-table { font-size: 12.5px; }
/**
 * 「分批发货计划」在跟单卡的**右栏**（跟中栏的卡片流一个待遇：也是分区卡片）——
 * 标题边距 / 行高压到最小，地址多了让表格自己滚（下面 `.dp-ship-table`）。
 */
.dp-ship-block { gap: 8px; }
.dp-ship-block .dp-items-table th,
.dp-ship-block .dp-items-table td { padding-top: 3px; padding-bottom: 3px; }
/* 「生成发货单」那一排：按钮 + 一句"生成后谁发货"的说明 */
.dp-ship-acts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.dp-ship-acts .primary { font-size: 12.5px; padding: 5px 13px; }
.dp-ship-acts .tag { display: inline-flex; align-items: center; gap: 4px; }
/**
 * 地址多的时候**自己滚**（32vh ≈ 960 高的屏上还能看十几行）—— 这样页脚永远不会
 * 高到把上面的卡片流挤没；正常 1~4 个地址是**整块全部看得见**的。
 */
.dp-ship-table { max-height: 32vh; overflow-y: auto; }
/**
 * 窄屏（≤900px）三栏是**竖着堆**的：中栏不锁高度，卡片流也不再自己滚（交给外层），
 * 页脚就当普通内容排在最后。
 */
@media (max-width: 900px) {
  .dp-col-main { overflow: auto; }
  .dp-form { height: auto; }
  .dp-fields { overflow: visible; overscroll-behavior: auto; }
  .dp-foot { border-top: 0; padding-top: 0; }
}
/* 「需要 1040.30 盒」这种数别把单位折到下一行（表格窄的时候特别难看） */
.dp-tracking .dp-items-table td.num { white-space: nowrap; }
.dp-out-chip {
  display: inline-block;
  margin: 0 4px 2px 0;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 11.5px;
  white-space: nowrap;
}
.dp-out-chip.is-done { border-color: var(--green); background: var(--green-soft); color: var(--green); }
/* 「出库单」列里那个小小的「回退」（只对还没发货的单出现） */
.dp-out-back { margin-left: 2px; padding: 1px 5px; font-size: 11px; }
.warn-ink { color: var(--yellow); }
.dp-bar {
  display: inline-block;
  vertical-align: middle;
  width: 96px;
  height: 6px;
  margin-right: 6px;
  border-radius: 999px;
  background: var(--surface-3);
  overflow: hidden;
}
.dp-bar > i { display: block; height: 100%; background: var(--brand); }
/* 车间进度那两行「派单 / 催单」：并排两个小按钮，禁用的（没负责人 / 已走过 / 已完成）明显压暗 */
/* 「需要到货时间」那一格（一键采购表单里，必填） */
.req-need { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 2px; }
.req-need-k { font-size: 12.5px; color: var(--ink); font-weight: 650; white-space: nowrap; }

/* 往来单位列表：备注独立一列（★ 2026-09-21 用户口径「备注单独做一列」）—— 太长就省略，悬停看全 */
.pf-remark { display: inline-block; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
/**
 * 供应商列表的「采购记录」列（2026-09-25）：`用过 N 次` 用绿色小字（这是"真在用"的信号），
 * `没往来` 走 `.muted`（淡灰，别抢注意力但一眼看得出）。两档都不做重装饰 —— 这列是拿来**筛人**的。
 */
.pf-used { font-size: 12px; color: var(--green); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* 收货地址那一列：几个收货点 + 「地址管理」入口 */
.pf-addr-cell { display: inline-flex; align-items: center; gap: 8px; }
.pf-addr-n { font-size: 12px; color: var(--ink-2); white-space: nowrap; }
.pf-addr-n.is-none { color: var(--ink-3); }

/**
 * ★ 2026-09-21（用户口径）：「**序号 客户编号这一行固定不动**」——
 *  · 横向滚动：最左边的**固定列**（`data-col-fixed`）钉住不动，后面的列从右边滑过去。
 *    每格的 `left` 由 JS 按前面几列的**定宽**加起来算（见 widgets.js 的 `applyStickyCols`）——
 *    所以这里只给样式，不写死偏移。
 *  · 纵向滚动：表头也钉住。
 * 背景必须**不透明**（半透明的话下面的行会透出来，钉住的列就糊了）。
 */
/* 「列设置」放进页面工具条时（`[data-cols-slot]`）：按钮贴着右边，浮层从它下面弹出来 */
.col-slot { display: inline-flex; align-items: center; margin-left: 4px; }
.col-slot .col-strip { position: relative; margin: 0; }
.col-slot .col-strip .pc-panel { right: 0; left: auto; }
/**
 * ★ 2026-09-25（用户口径）：「标签的样式设置放在这里」——
 * 列表里「列设置」那一行（工具条下面那条）：左边放这一屏自己的设置按钮（如「标签样式」），
 * 右边那个插槽里是通用件的「列设置」（`bindColTables` 会往里塞）。
 */
.col-strip-row { display: flex; justify-content: flex-end; align-items: center; gap: 6px; padding: 2px 0 5px; }
.col-strip-row .col-strip { padding: 0; }
.col-strip-row > button { display: inline-flex; align-items: center; gap: 5px; }

/* 列表渲染上限的提示（超长列表只渲染前 N 行时显示） */
/**
 * ★ 2026-09-22（用户口径）：仓位查询里「商品名 / 仓位」是**点开才查**的入口
 *   （点商品名看它在所有仓位的数量，点仓位看这一格里有什么）。
 *
 *   · 第一版是"别像按钮"——一行**虚线下划线**的文字；但那样更像"这词有提示"，
 *     不像"能点"，用户口径「**能够点击查询的字段，改变一下ui**」。
 *   · 现在做成**带放大镜的小胶囊**：平时安安静静（透明底、无描边，只有一枚浅灰放大镜标着"能查"），
 *     鼠标移上去才浮出一层浅底 + 细描边、放大镜变蓝 —— 一眼看得出能点，又不把一屏 20 行搅花。
 */
.locate-cell {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 100%;
  padding: 3px 8px;
  /* 负外边距把胶囊的留白抵消掉：文字仍和表头、和别的列左对齐 */
  margin: -3px -8px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 160ms ease, border-color 160ms ease;
}
.locate-cell:hover { background: var(--surface-2); border-color: var(--line); }
/**
 * 商品名那一格是"名字 + 编码 · 规格"两行（仓位那格只有一行，不用这层）。
 * ⚠️ `align-items: flex-start` 会让两个 span 按**内容宽**排，长名字会**顶出单元格、
 *    压到右边「仓库」那一列上**（列是定宽的，`table-layout: fixed` 不会替你收）——
 *    所以这两行都得 `max-width: 100%` 兜住；名字允许换行，编码那行本来就带省略号。
 */
.locate-cell-main { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; }
.locate-cell .biz-name,
.locate-cell .biz-sub { max-width: 100%; min-width: 0; }
.locate-cell .biz-name { white-space: normal; overflow-wrap: anywhere; }
.locate-cell .mono { font-variant-numeric: tabular-nums; }
/* 放大镜：**唯一常驻的"能点"提示**，平时很轻，悬停才亮成蓝 */
.locate-cell-ic { display: inline-flex; flex: none; color: var(--ink-3); }
.locate-cell:hover .locate-cell-ic { color: var(--blue); }
.locate-cell:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
.locate-detail .table-scroll { max-height: 62vh; overflow: auto; }
.locate-detail .pt-thumb { width: 40px; height: 40px; }

.table-cap { padding: 8px 12px; font-size: 12px; color: var(--ink-3); border-top: 1px solid var(--line-2); background: var(--surface-2); }

.col-table th.is-col-stuck,
.col-table td.is-col-stuck { position: sticky; z-index: 3; background: var(--surface); }
.col-table thead th { position: sticky; top: 0; z-index: 2; background: var(--surface-2); }
.col-table thead th.is-col-stuck { z-index: 5; background: var(--surface-2); }
.col-table tbody tr:hover td.is-col-stuck { background: var(--surface-2); }

.dp-stage-acts { white-space: nowrap; }
.dp-stage-acts .link-btn { margin-right: 6px; }
.dp-stage-acts .link-btn[disabled] { opacity: 0.4; cursor: default; text-decoration: none; }

/**
 * ★ 2026-09-21（用户口径）：「一键采购 配货 派单 按照罐装车间 组装车间**一块一块操作布局**」。
 *
 * 每个车间块头上一条**操作带**：买这一块缺的 / 给这一块配货 / 把这一块派给车间。
 * 贴在材料表**上面**（先看该干什么、再翻明细，不用滑到底找按钮）。
 */
.dp-stage-acts-block {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  padding: 6px 12px;
  background: var(--surface);
  border-bottom: 1px solid var(--line-2);
  white-space: normal;
}
.dp-stage-acts-block .dp-stage-buy { color: var(--blue); font-weight: 650; }
/* ★ 2026-09-21：表里勾了行 → 这把按钮的口径变成"只买勾中的"（文案也跟着变），给个更重的颜色 */
.dp-stage-acts-block .dp-stage-buy.is-picked { color: var(--brand); }
/* 块头上那个「谁负责 / 报了多少」——比车间名轻、比徽标重 */
.dp-stock-stage-who { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }

/* 库存状态最下面那一排：「一键采购」主按钮 / 「已下采购申请单」的状态药丸
   （用户口径 2026-09-18：按钮放下面、要显眼；已经下过申请单就不再摆按钮） */
.dp-stock-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 2px; }
.dp-stock-foot .primary { font-size: 12.5px; padding: 6px 14px; }
.dp-stock-done {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--green-soft);
  color: var(--green);
  font-size: 12.5px;
}
.dp-stock-done .dp-stock-done-state { color: var(--green); opacity: 0.8; }
/* 窄屏（手机）：分批发货计划那张表 8 列挤不下 —— 序号 / 电话这两列最不重要，先收起来，
   地址 / 联系人 / 数量 / 已发 / 状态 / 出库单 仍然看得全（2026-09-18 在 430px 上量的）。 */
@media (max-width: 560px) {
  .dp-tracking .dp-sm-hide { display: none; }
}

/* ---- 一键采购：库存状态 → 采购申请单（2026-09-18）---- */
.dp-req-line { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.dp-req-chip {
  display: inline-block;
  padding: 1px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 11.5px;
  white-space: nowrap;
}
/**
 * ★ 2026-09-22（用户口径）：「跟单员的**一键采购**上面加上一个**订单交期**」——
 *   弹窗顶上「申请人 / 项目号 / 订单号」那一行的尾巴上挂一枚药丸：日期 + 「剩 N 天」，
 *   底色跟着急缓走（红 / 黄 / 默认），和页脚「交货日期」那行 `.dp-due-left` 同一套口径；
 *   区别是这里在**行内**（那行是 `.small muted` 的一句话），所以字号压住、不做块级。
 */
.req-order-due {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  margin: 0 2px;
  padding: 1px 9px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 12px;
  font-weight: 650;
  white-space: nowrap;
}
.req-order-due b { font-weight: 700; }
.req-order-due .req-order-due-left { font-style: normal; font-size: 11.5px; font-weight: 700; }
/* 剩得多（`muted`）= 蓝，和页脚那枚「剩 N 天」同一套：蓝「还早」/ 黄「紧了」/ 红「逾期」 */
.req-order-due.muted { background: var(--blue-soft); color: var(--blue); }
.req-order-due.due-red { background: var(--red-soft); color: var(--red); }
.req-order-due.due-red b,
.req-order-due.due-red .req-order-due-left { color: var(--red); }
.req-order-due.due-warn { background: var(--yellow-soft); color: var(--yellow); }
.req-order-due.due-warn b,
.req-order-due.due-warn .req-order-due-left { color: var(--yellow); }

.req-form { display: flex; flex-direction: column; gap: 10px; }
.req-rows { display: flex; flex-direction: column; gap: 6px; }
/* 表头与行用同一套列宽（用户口径 2026-09-20：图片 / 名称 / 需求量 / 仓储量 / 在购途中 / 建议采购数） */
.req-head,
.req-row {
  display: grid;
  grid-template-columns: 22px 34px minmax(0, 1fr) 92px 92px 92px 108px 34px;
  gap: 8px;
  align-items: center;
}
.req-head {
  font-size: 11px;
  color: var(--ink-3);
  padding-bottom: 5px;
  border-bottom: 1px solid var(--line-2);
}
.req-head .num { text-align: right; }
.req-head .req-pic-head { text-align: center; }
.req-pick { display: flex; align-items: center; justify-content: center; }
/* 缩略图：**看图用的**（悬停出大图预览、双击开大图）——不是勾选入口，所以光标是放大镜；
   勾选走「名字」那一格（`.req-name` 是那个 checkbox 的 label）。 */
.req-pic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface);
  cursor: zoom-in;
}
.req-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.req-pic.is-empty { color: var(--ink-3); border-style: dashed; }
.req-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
.req-name:hover { color: var(--blue); }
/* 需求量 / 仓储量 / 在购途中：都是只读的数（建议采购数才是可改的输入框） */
.req-need { text-align: right; font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
.req-stock { text-align: right; font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
.req-onway { text-align: right; font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
.req-qty { text-align: right; font-variant-numeric: tabular-nums; }
.req-unit { text-align: left; }

/* ============================================================================
   「有新版本」胶囊（app.js 的 offerReload / showReloadPill）
   用户口径 2026-09-18：「工作台还是没有之前流畅」—— 查出来主因之一是**页面被反复整页刷掉**：
   多会话同时改代码时，core/live.js 一广播 reload、客户端就无条件 location.reload()。
   现在：外壳里不自动刷、浏览器里只在"人没动且没开弹窗"时刷，其余情况落这颗胶囊，点一下才刷。
   位置放**底部居中**（右下角是 toast 的地盘），颜色全走令牌。
   ============================================================================ */
.live-pill {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px 7px 15px;
  border-radius: 999px;
  /**
   * ⚠️ 别用 `background: var(--ink); color: #fff`（toast 那套）——**深色主题下 `--ink` 是浅色**
   * （#e8ecf3），会变成浅底白字，一个字都看不清。这里按"浮起来的小卡片"做：
   * 底走 surface / 字走 ink / 描边走 line，四套主题都读得清；「刷新」用链接蓝，不填色。
   */
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--line);
  font-size: 12.5px;
  white-space: nowrap;
  box-shadow: var(--shadow-lg);
}
.live-pill-go {
  border: 0;
  border-radius: 999px;
  padding: 3px 10px;
  background: transparent;
  color: var(--blue);
  font: inherit;
  font-weight: 650;
  cursor: pointer;
}
.live-pill-go:hover { background: var(--blue-soft); }
.live-pill-x {
  border: 0;
  background: transparent;
  padding: 0 2px;
  color: var(--ink-3);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.live-pill-x:hover { opacity: 1; }


/* 料体配方明细：用户口径 2026-09-18「这个表格优化下，太丑了」
   —— 数字一律右对齐、编号等宽小字、名字一行不换行、损耗率做成灰 chip、金额加粗收尾。
   （.ft-table 是本仓库现成的明细表样式：表头浅底、行分隔线、hover 高亮） */
.fs-recipe-wrap { border: 1px solid var(--line-2); border-radius: var(--radius); overflow: hidden; }
.fs-recipe { width: 100%; border-collapse: collapse; }
.fs-recipe thead th {
  padding: 8px 12px; text-align: left; white-space: nowrap;
  font-size: 11.5px; font-weight: 550; letter-spacing: 0.3px; color: var(--ink-3);
  background: var(--surface-2); border-bottom: 1px solid var(--line-2);
}
.fs-recipe thead th.num, .fs-recipe td.num { text-align: right; font-variant-numeric: tabular-nums; }
.fs-recipe tbody td { padding: 9px 12px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.fs-recipe tbody tr:last-child td { border-bottom: 0; }
.fs-recipe tbody tr:hover td { background: var(--surface-2); }
.fs-recipe .fs-cell-code { color: var(--ink-3); font-size: 12px; white-space: nowrap; }
/* 明细表的名字列：允许折 2 行（列宽是定死的，长名字原来直接被省略号吃掉） */
.fs-recipe .fs-cell-name {
  color: var(--ink);
  white-space: normal;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fs-recipe .fs-cell-strong { font-weight: 600; }
.fs-recipe .fs-cell-amount { font-weight: 600; white-space: nowrap; }
.fs-recipe .fs-loss {
  display: inline-block; padding: 0 6px; border-radius: 999px;
  background: var(--surface-3); color: var(--ink-3); font-size: 11.5px;
}

/* 顶部那行：成本 + 几个小 chip */
.fs-recipe-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.fs-recipe-cost { display: inline-flex; align-items: baseline; gap: 6px; }
.fs-recipe-cost b { font-size: 17px; }
.fs-chip {
  display: inline-flex; align-items: baseline; gap: 4px; padding: 2px 9px; border-radius: 999px;
  background: var(--surface-2); color: var(--ink-3); font-size: 12px;
}
.fs-chip b { color: var(--ink-2); }


/* 料体卡片上那颗星：一键「设为常用」（用户口径 2026-09-18）
   ★ = 常用（琥珀色 + 浅底），☆ = 还不是（灰，hover 变琥珀） */
.fs-star {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; border: 0; border-radius: 999px;
  background: transparent; color: var(--ink-3); cursor: pointer;
  transition: color 150ms ease, background-color 150ms ease;
}
.fs-star svg { fill: none; stroke: currentColor; }
.fs-star:hover { background: var(--surface-3); color: #c8891a; }
.fs-star.is-on { color: #c8891a; background: rgba(200, 137, 26, 0.12); }
.fs-star.is-on svg { fill: currentColor; }
.fs-star:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }

/* 建档案时直接录收货地址（用户口径 2026-09-18：「建立客户的时候，也可以直接输入收货地址，
   可以是多个」）—— 一行一条：收货人 / 电话 / 地址 / 删。 */
.pf-addr-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.pf-addr-row input { flex: 1; min-width: 0; }
.pf-addr-row input[data-addr-f='name'] { flex: 0 0 130px; }
.pf-addr-row input[data-addr-f='phone'] { flex: 0 0 150px; }
.pf-addr-row input[data-addr-f='address'] { flex: 1 1 auto; }
.pf-addr-row button { flex: none; }

/* ============================================================================
   销售开单弹窗的版式（用户口径 2026-09-19：「ui 太太太垃圾，重新优化」）

   三个毛病，都在这一块里治：
     ① 行距太松：`.grid{gap:14px}` + `label.field{margin-bottom:10px}` 叠加成 24px，
        一屏放不下几行 —— 表单里把两者压到 10px / 0。
     ② 一长串输入框平铺、没有落点 —— 用 `.form-section` 分块（基本信息 / 明细 / 地址 / 收款）。
     ③ 收货地址那块被一个空粘贴框撑掉半屏 —— 粘贴框收进「粘贴识别」按钮里（见 pages-sales.js）。
   **只管开单这一个表单**（`.sl-form` 作用域）：别的表单（商品 / 客户 / 采购…）一个像素都不动。
   ============================================================================ */
.modal:has(.sl-form) .grid { gap: 10px; }
.modal:has(.sl-form) label.field { margin-bottom: 0; }
/* 分区标题在表单里当"块首"用：上边留白、下边贴紧，别把块内行距也撑开 */
.modal:has(.sl-form) .form-section { margin: 4px 0 0; }
.modal:has(.sl-form) .form-section:first-child { margin-top: 0; }

/* 收货地址：标题行（眉标 + 用哪条 + 粘贴识别）＋ 下面三格一行 */
.sl-ship-head .eyebrow { flex: none; }
.sl-ship-head .sl-ship-pick { margin-left: 6px; }
.sl-ship-head > button { flex: none; margin-left: auto; }
.sl-ship-parse[hidden] { display: none; }
.sl-ship-fields { flex-wrap: nowrap; }
@media (max-width: 900px) {
  /* 窄屏（手机）三格排不下才折行 —— 桌面上一律一行，省掉那半屏空白 */
  .sl-ship-fields { flex-wrap: wrap; }
}

/* 收款那一块：已付 + 凭证挤在一行里，别各占一行 */
.sl-pay-row { align-items: flex-start; }
.sl-pay-amt input { max-width: 140px; }

/* 这个客户还没有地址簿时：那句提示独占一行，「粘贴识别」按钮不被挤走（标题行永远一行） */
.sl-ship-block.is-empty-book .sl-ship-head { flex-wrap: nowrap; }
.sl-ship-block.is-empty-book .sl-ship-pick { flex: 0 1 auto; min-width: 0; overflow: hidden; }
.sl-ship-block.is-empty-book .sl-ship-pick > span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 开单这个弹窗内容多，让它比别的表单多占一点高度（少滚一段）；不支持 :has() 的浏览器自动忽略 */
.modal-body:has(.sl-form) { max-height: 78vh; }

/* ============================================================================
   商品选择那一格（用户口径 2026-09-19：「商品选择比较重要，再优化」）

   它原来长得像个普通下拉 —— 看不出"点开是个能搜的面板"、也看不出"还没选"。
   现在：空态虚线 + 放大镜 + 一句话说清能搜什么；选完实线 + 商品图标 + 名称带规格/单位，
   右侧的提示从「▾」变成「换一个」。**只在开单表单里加强**（`.sl-form`）：
   采购 / 退货那几处还是老样子，一行都不用改、也不用重新验。
   ============================================================================ */
.bom-pick .bp-ico { display: inline-flex; flex: none; color: var(--ink-3); }
.bom-pick .bp-hint { flex: none; font-size: 11.5px; color: var(--ink-3); }
.bom-pick:not(.is-empty) .bp-ico { color: var(--blue); }
.bom-pick:not(.is-empty) .bp-caret {
  font-size: 11px; color: var(--ink-3); opacity: 0; transition: opacity 0.12s ease;
}
.bom-pick:not(.is-empty):hover .bp-caret { opacity: 1; }

.modal:has(.sl-form) .bom-pick {
  height: 34px;                 /* 和同一行的数量 / 单价输入框一样高（原来 30px，矮一截） */
  font-size: 13px;
  border-radius: var(--radius-sm);
}
.modal:has(.sl-form) .bom-pick.is-empty {
  border-style: dashed;
  border-color: var(--line-2);
  background: var(--surface-2);
}
.modal:has(.sl-form) .bom-pick.is-empty:hover {
  border-color: var(--blue);
  border-style: solid;
  color: var(--blue);
  background: var(--surface);
}
.modal:has(.sl-form) .bom-pick.is-empty:hover .bp-ico { color: var(--blue); }
.modal:has(.sl-form) .bom-pick:not(.is-empty) { background: var(--surface); }

/* ============================================================================
   开单表单的**分区卡片**（用户口径 2026-09-19：「整体分区太太不明显，看着廉价，再优化」）

   原来每块只有一个灰色小眉标 + 一道**虚线** —— 又弱又像半成品。现在按仓库的卡片化语言来：
   一块一张浅底卡片（圆角 + 实线描边），**标题加粗加深 + 左侧一道主色竖条**，副标题小字说明。
   四块：基本信息（就是 formModal 那个 .grid）/ 销售明细 / 收货地址 / 收款。
   ============================================================================ */
.modal:has(.sl-form) .grid,
.modal:has(.sl-form) .sl-sec {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: 12px 14px;
}
.modal:has(.sl-form) .sl-sec { margin-top: 10px; }
/* 卡片之间不再需要虚线分隔（卡片自己就是分界） */
.modal:has(.sl-form) .sl-ship-block,
.modal:has(.sl-form) .sl-pay-block { border-top: 0; margin-top: 10px; padding-top: 12px; }
.modal:has(.sl-form) .sl-order-sum { margin-top: 10px; padding-top: 0; border-top: 0; }

/* 块标题：加粗 + 左侧主色竖条（一眼看出这是"一块"，不是一行灰字） */
.sl-sec-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.sl-sec-title {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 650; color: var(--ink);
}
.sl-sec-title::before {
  content: ''; width: 3px; height: 13px; border-radius: 2px; background: var(--brand);
}
.sl-sec-sub { font-size: 11.5px; color: var(--ink-3); }

/* 基本信息那块是 formModal 的 .grid 渲染的，标题是 .form-section：把它也做成同一档 */
.modal:has(.sl-form) .form-section {
  margin: 0 0 10px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 650;
  letter-spacing: 0;
}
.modal:has(.sl-form) .form-section > span { display: inline-flex; align-items: center; gap: 8px; }
.modal:has(.sl-form) .form-section > span::before {
  content: ''; width: 3px; height: 13px; border-radius: 2px; background: var(--brand);
}
.modal:has(.sl-form) .form-section::after { display: none; }   /* 卡片自己就是边框，不用再画一条线 */

/* 客户还没有地址簿时：提示换到标题行**下面**独占一行（原来被挤成 0 宽，看着像块空白） */
.sl-ship-block.is-empty-book .sl-ship-head { flex-wrap: wrap; }
.sl-ship-block.is-empty-book .sl-ship-pick { flex: 1 1 100%; order: 9; margin-left: 0; }
.sl-ship-block.is-empty-book .sl-ship-pick > span { white-space: normal; overflow: visible; text-overflow: clip; }

/* 「用哪条 / 还没存过地址」这一行（从标题行里挪出来的，见 pages-sales.js）：
   自己有下边距，和下面三格拉开一点；标题行永远只有"标题 + 副标题 + 粘贴识别"。 */
.sl-ship-pick { margin: 2px 0 10px; }
.sl-ship-block.is-empty-book .sl-ship-pick { margin: 0 0 10px; }
.sl-ship-block.is-empty-book .sl-ship-pick > span { white-space: normal; overflow: visible; }

/* ---------------------------------------------------------------- 选商品面板（2026-09-19 重做）
   用户报了三件事：「搜索框有严重的bug」（其实是**框套框**：面板自己的圆角边框 + 里面输入框
   自己的圆角边框，看着像画坏了）、「要能模糊搜索」、「结果行太小，容易看错产品」。 */
/* ① 搜索行做成**面板顶部的工具条**：顶到边、铺一层浅底、底下一条细线 ——
   输入框不再和面板边框叠成一个"框套框"。 */
.prp-head {
  position: sticky; top: 0; z-index: 2;
  margin: -10px -10px 10px;
  padding: 10px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line-2);
  border-radius: 12px 12px 0 0;
}
.prp-head .zm-i { left: 19px; }
.prp-head input { height: 34px; padding-left: 32px; background: var(--surface); font-size: 13px; }

/* ② 结果行加大成**两行式**：编号 + 名称 / 规格 · 单位 · 客户 —— 一行挤十几个"瑞祥-成品…"最容易点错 */
.prp-panel { width: 560px; }
.prp-list { gap: 2px; }
.prp-item { align-items: flex-start; padding: 8px 10px; font-size: 13px; border-radius: 8px; }
.prp-item .prp-thumb { width: 34px; height: 34px; border-radius: 8px; }
.prp-item .prp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.prp-item .prp-line { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.prp-item .prp-line2 { color: var(--ink-3); font-size: 11.5px; flex-wrap: wrap; }
.prp-item .prp-name { font-size: 13.5px; font-weight: 500; }
.prp-item .prp-code { font-size: 11.5px; }

/* 明细合计里的「应付金额」（用户口径 2026-09-19：「应该有应付金额」）：
   比总金额再重一点 —— 这是真正要收的钱 */
.sl-order-sum .sl-sum-item.is-due { margin-left: 6px; padding-left: 12px; border-left: 1px solid var(--line-2); }
.sl-order-sum .sl-sum-item.is-due b { font-size: 14px; color: var(--ink); }

/* 粘贴识别栏现在是**常显**的（用户口径 2026-09-19：「直接变成粘贴识别栏」）——
   不再藏在按钮后面，所以给它一个清爽的输入条长相，别再像个"大空框" */
.sl-ship-parse textarea { min-height: 40px; background: var(--surface); }

/* 选完商品那一格：序号 / 图片 / 名称 / 编号（用户口径 2026-09-19） */
.ie-no { color: var(--ink-3); font-size: 11.5px; }
.bom-pick .ie-thumb { flex: 0 0 auto; width: 24px; height: 24px; border-radius: 5px; overflow: hidden; border: 1px solid var(--line); }
.bom-pick .ie-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bom-pick .ie-pname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bom-pick .ie-pcode { flex: 0 1 auto; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-3); font-size: 11.5px; }

/* 选商品面板：列表高一点（用户口径 2026-09-19「产品还显示不全」），
   而且**面板内部滚动不关面板**（关闭逻辑见 pages-product.js 的 onScrollClose） */
.prp-body { max-height: min(62vh, 560px); }
.prp-list { gap: 2px; }

/* 明细行里的「商品」格：没选是搜索框、选了是一块商品明细（图片 + 名称 / 编号 + 换一个）
   （用户口径 2026-09-19：「选中后，这里需要显示商品明细 图片 名称 编号」） */
.ie-pick-td { display: flex; align-items: center; gap: 8px; }
.ie-pick-td.is-picked .ie-pick-search { display: none; }
.ie-pick-td:not(.is-picked) .ie-pick-detail { display: none; }
.ie-pick-detail { display: flex; align-items: center; gap: 9px; min-width: 0; flex: 1 1 auto; }
.ie-pick-lines { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ie-pick-lines .ie-pname { font-size: 12.5px; line-height: 1.35; }
.ie-pick-lines .ie-pcode { font-size: 11px; line-height: 1.3; }
.ie-pick-search { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
.ie-pick-swap {
  flex: none; padding: 2px 6px; border: 0; border-radius: 5px; background: none;
  color: var(--ink-3); font: inherit; font-size: 11.5px; cursor: pointer;
}
.ie-pick-swap:hover { background: var(--surface-2); color: var(--blue); }
.bom-editor .ie-thumb.is-empty { display: inline-flex; align-items: center; justify-content: center; color: var(--ink-3); }

/* 开单弹窗再宽一点（用户口径 2026-09-19：「这里整体布局再调整下，产品显示太少」）——
   880px 在 1680 的屏上，商品名稍长就被省略号切掉；只加宽**开单这个弹窗**，别的不动 */
/* ⚠️ 光写 width 没用 —— `.modal` 自己有 `max-width: 880px`，得一起抬（实测踩过） */
.modal:has(.sl-form) {
  width: min(1080px, calc(100vw - 48px));
  max-width: min(1080px, calc(100vw - 48px));
}
/* 明细表最后一列（删行那个 ✕）窄一点，省下的都给「商品」 */
.modal:has(.sl-form) .bom-editor .items-table th:last-child { width: 34px; }
/* 商品明细块：名称/编号给足一行，别挤 */
.modal:has(.sl-form) .ie-pick-detail { gap: 10px; }
.modal:has(.sl-form) .ie-pick-lines { flex: 1 1 auto; }
.modal:has(.sl-form) .ie-pick-lines .ie-pname { font-size: 13px; }
.modal:has(.sl-form) .ie-pick-lines .ie-pcode { font-size: 11.5px; }

/* ============================================================================
   开单弹窗的**视觉统一**收尾（用户口径 2026-09-19：「整体的 ui 设计太太太丑了，重新设计优化」）

   目标：把这一个表单里的字号 / 间距 / 层次收成**一套**，别再东一块西一块。
   仍然只作用于 `.sl-form`（别的表单一个像素不动）。
   ============================================================================ */
/* ① 字号层次：标签 11.5 / 输入 13 / 区块标题 13.5 / 合计数字 14~15 —— 四级就够，别再散 */
.modal:has(.sl-form) label.field > span.lbl { font-size: 11.5px; color: var(--ink-2); }
.modal:has(.sl-form) input, .sl-form select, .sl-form textarea { font-size: 13px; }
.modal:has(.sl-form) .sl-sec-title { font-size: 13.5px; }
.modal:has(.sl-form) .sl-sec-sub { font-size: 11.5px; }
/* ② 卡片之间统一 12px，卡片内边距统一 14px（原来 10/12 混着来） */
.modal:has(.sl-form) .sl-sec { margin-top: 12px; padding: 13px 14px; }
.modal:has(.sl-form) .grid { padding: 13px 14px; }
/* ③ 合计那一行：数字右对齐成一列，看起来是一张"账单"而不是散落的字 */
.modal:has(.sl-form) .sl-order-sum { gap: 18px; }
.modal:has(.sl-form) .sl-order-sum .sl-sum-item { display: inline-flex; align-items: baseline; gap: 6px; }
.modal:has(.sl-form) .sl-order-sum .sl-sum-item .k { font-size: 11.5px; color: var(--ink-3); }
.modal:has(.sl-form) .sl-order-sum .sl-sum-item b { font-size: 14px; }
/* ④ 收货地址三格：标签宽度一致，输入框对齐 */
.modal:has(.sl-form) .sl-ship-fields .sf .k { min-width: 44px; font-size: 11.5px; color: var(--ink-2); }
/* ⑤ 弹窗底部的按钮尺寸统一（取消 / 存草稿 / 建单并提交 一样高） */
.modal:has(.sl-form) ~ * .modal-foot button, .modal:has(.sl-form) .modal-foot button { min-height: 34px; }
/* ⑥ 窄屏（手机 / 分栏窗口）：字段一律占满一行，别再挤成两栏 */
@media (max-width: 760px) {
  .modal:has(.sl-form) { width: calc(100vw - 16px); }
  .modal:has(.sl-form) .grid > .field { grid-column: span 12; }
  .modal:has(.sl-form) .sl-ship-fields { flex-wrap: wrap; }
  .modal:has(.sl-form) .sl-ship-fields .sf { flex: 1 1 100%; }
  .modal:has(.sl-form) .sl-order-sum { flex-wrap: wrap; gap: 10px 16px; }
}

/* ⚠️ 作用域说明（2026-09-19 踩的坑）：`formModal` 的结构是
   `<form><div class="grid">字段…</div>{extra}</form>` ——
   `extra` 里的 `.sl-form` 和字段那个 `.grid` 是**兄弟**，不是父子！
   所以 `.sl-form …` 这些选择器**管不到「基本信息」那几个字段**（字号/窄屏规则全落空）。
   改成 `.modal:has(.sl-form) …`（弹窗级作用域）才覆盖得到两边。
   这条 media 就是补窄屏的字段排布。 */
@media (max-width: 760px) {
  .modal:has(.sl-form) .grid > .field { grid-column: span 12; }
  .modal:has(.sl-form) label.field > span.lbl { font-size: 14px; }
}

/* ============================================================================
   开单弹窗：**去掉那一片灰**（用户口径 2026-09-19：「整体的这种灰色调，太丑了，优化」）

   问题：我给每块卡片填了 `--surface-2`（#f2f4f7）——四块灰叠在一起，整屏就是一片灰，
   白输入框反而成了"挖出来的洞"，看着脏。
   改法（也符合工作台的令牌用法：**白面 + 细线 + 极轻投影**才是卡片）：
     · 卡片改**白底**（`--surface`）+ 1px 细边 + 一层几乎看不见的投影（层次靠投影，不靠灰）；
     · 输入框的边**加重一档**（白底上原来那圈 #dfe1e6 太淡，框会"消失"）；
     · 区块标题左侧那道主色竖条留着 —— 那点颜色是全局唯一的色块，正好当锚点。
   仍然只作用于开单这个弹窗。
   ============================================================================ */
.modal:has(.sl-form) .grid,
.modal:has(.sl-form) .sl-sec {
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 1px 2px rgba(15, 23, 32, 0.04);
}
/* 卡片里再套一层网格（发货时间那块）就不该再有边框/底色了，不然"盒子里套盒子" */
.modal:has(.sl-form) .sl-sec .grid {
  background: transparent;
  border: 0;
  box-shadow: none;
  padding: 0;
}
/* 白底上的输入框：边加重一档，不然框会"消失" */
.modal:has(.sl-form) input:not([type='checkbox']),
.modal:has(.sl-form) select,
.modal:has(.sl-form) textarea {
  border-color: #d7dbe3;
}
.modal:has(.sl-form) input:not([type='checkbox']):focus,
.modal:has(.sl-form) select:focus,
.modal:has(.sl-form) textarea:focus { border-color: var(--blue); }
/* 区块标题那一行：给一点点极浅的底，把它和内容分开（不靠整块灰） */
.modal:has(.sl-form) .sl-sec-head { margin-bottom: 12px; }
.modal:has(.sl-form) .sl-sec-sub { color: var(--ink-3); }

/* 明细行里那个商品缩略图：**必须钉死尺寸**（不然按原图大小撑开，一行被顶得老高）
   34×34 + 圆角 + 细边，hover 放大走 bindPicZoom 的浮层 */
.ie-pick-detail .ie-thumb {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 6px; overflow: hidden;
  border: 1px solid var(--line); background: var(--surface);
}
.ie-pick-detail .ie-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ie-pick-detail .ie-thumb.is-empty { display: inline-flex; align-items: center; justify-content: center; color: var(--ink-3); border-style: dashed; }

/* ============================================================================
   列表页的横向滚动条**钉在可视区底部**（用户口径 2026-09-19：
   「滑动栏放在销售单界面最下面」）

   原来 `.prod-scroll` 只有 `flex:1 1 auto` —— 高度跟着**内容**走，
   表格宽 2000+px 时横向滚动条就浮在"表格容器自己的底边"上（截图里它出现在第一行下面，
   看着像条多余的分隔线）。给它一个**视口高度上限**，容器就撑到屏幕底，
   横向滚动条自然落在最下面。
   300px 是上面那几层（顶栏 + 页签 + 搜索/筛选栏 + 表头）的经验高度，
   宁可留一点空隙也别把内容压掉。
   ============================================================================ */
/* ⚠️ 用 `height` 而不是 `max-height`：`max-height` 只封顶，内容短的时候容器还是缩着的，
   滚动条照样浮在半空（实测：底边离窗口底还差 83px）。`height` 才让它**贴到窗口底**。
   240px = 顶栏 + 页签 + 搜索栏 + 表头的经验高度（实测容器顶边在 ~217px）。

   ★ 2026-09-20 逐页 WebKit 实测确认（交接单第 3 节「别只信同一个类所以没事」）——
   1440×960 视口下，用了 `.prod-scroll` 的页面容器底边都在 892~928 / 960 之间
   （销售单·待发货·退货·客户 / 采购单·申请·退货·供应商 / 价格 / 仓储六页：928；
   商品档案·商品BOM：892 —— 那 36px 是页面底部的**分页条**，滚动条落在它上面是对的；
   历史：915，底下也是分页条）。**没有一页溢出窗口**，横向滚动条都落在容器自己的底边。
   ⚠️ 项目（流程看板）/ 通讯录 / 配方（区块布局）/ 商品分类（占位页）**不用 `.prod-scroll`**，
   这条规则对它们不生效 —— 以后别拿这页的观感去推它们。
   ⚠️ 商品档案 / 商品BOM 实测容器高 **592px**（不是 720px）—— 是 `flex: 1 1 auto` 的 shrink
   把它压进了剩余空间，底下才留得下分页条；`flex` 这一截别动。 */
.prod-page .prod-scroll { height: calc(100vh - 240px); }
@media (max-height: 700px) {
  .prod-page .prod-scroll { height: calc(100vh - 180px); }
}

/* 采购单号那一行：单号 ……… 状态章 + 剩余时间（都在一行里，别换行 —— 换一行卡片就高一截） */
.bc-doc .bc-chip { flex: none; }

/* ============================================================================
   毛玻璃（2026-09-22）—— 用户口径：「整体的设计，融合进去毛玻璃 ui 设计的风格」

   令牌在 :root（四套主题各有一套 `--aurora` / `--glass-*`），这一段只负责**贴到哪些面**。
   贴哪不贴哪，唯一的依据是**背后有没有东西可糊**（理由和性能账写在 :root 那段注释里）：

     ✅ 贴：弹窗 / 右键菜单 / 下拉浮层 · 吸顶表头 · 面板与卡片
            —— 背后是光场（--aurora）或正在滚过去的内容，磨砂看得见
     ❌ 不贴：侧栏这种**静态整列** —— 半透明可以，backdrop-filter 不行
            （2026-09-18 撤过一次，web.test.js 里有断言盯着，不许再加回来）

   ⚠️ 数一下同屏的模糊层：面板 1~2 层 + 吸顶表头 1 层 + 弹窗 1 层。**别再往上加小元件**
      （待办卡 `.card` 一屏几十张，一张一层模糊就是自找卡顿）。
   ============================================================================ */

/* ---- 面板 / 占位卡 / 列表页整页白卡：整站最大的几块面 ---- */
.panel,
.ph-card,
.pt-wrap,
.sl-card {
  background: var(--glass-bg);
  background-image: var(--glass-sheen);
  -webkit-backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur));
  border-color: var(--glass-line);
  box-shadow: var(--glass-shadow);
}

/* ---- 首页四象限：整块板**一层**玻璃就够（四块各自糊是白花钱） ---- */
.quad-grid {
  background: var(--glass-bg);
  background-image: var(--glass-sheen);
  -webkit-backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur));
  border-color: var(--glass-line);
  box-shadow: var(--glass-shadow);
}
.quad { background: transparent; }
/* 分区头带原来是不透明的 surface-2，压在玻璃上会变成一条实心灰带 —— 换成更浓一档的玻璃 */
.quad-head { background: var(--glass-bg-strong); }

/* ---- 弹窗：浮在遮罩上，最该是玻璃的地方 ---- */
.modal {
  background: var(--glass-bg-strong);
  background-image: var(--glass-sheen);
  -webkit-backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur));
  border: 1px solid var(--glass-line);
}
/* 弹窗底：原来这里 `backdrop-filter` 写了两遍（3px 被后面 2px 盖掉），一并收拾了 */
.mask {
  background: rgba(20, 21, 26, 0.4);
  -webkit-backdrop-filter: blur(3px) saturate(120%);
  backdrop-filter: blur(3px) saturate(120%);
}

/* ---- 浮层 / 右键菜单：原来就在用 `--glass`，统一到新令牌 ---- */
.module-flyout,
.card-menu {
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  backdrop-filter: saturate(180%) blur(16px);
  border-color: var(--glass-line);
}

/* ---- 吸顶表头：内容从底下滚过去，玻璃最经得起看的地方（同屏只有一条，代价可控） ---- */
.ft thead th,
.ft thead th.ft-card-col,
.pt thead th,
.mpk-table thead th,
.col-table thead th,
.col-table thead th.is-col-stuck {
  background: var(--glass-bg);
  -webkit-backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur-sm));
  backdrop-filter: saturate(var(--glass-sat)) blur(var(--glass-blur-sm));
}

/* ---- 静态整列：**半透明，但不挂 backdrop-filter** ----
   依据见 :root 里的性能账（`web.test.js` 还有一条断言盯着侧栏）。
   项目页左栏 `.proj-side` 同理：它和内容并排、内容不从它底下过，糊了也看不见，只是白花钱。 */
.sidebar,
.proj-side { background: var(--glass-bg-soft); }

/* ---- 兜底 ----
   ① 引擎不支持 backdrop-filter：退回不透明的 --surface，别让字糊在光场上；
   ② 用户在系统里开了「降低透明度」：同样退回不透明（Safari/WKWebView 认这条）。 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .panel, .ph-card, .pt-wrap, .sl-card, .quad-grid, .modal, .module-flyout, .card-menu,
  .ft thead th, .ft thead th.ft-card-col, .pt thead th, .mpk-table thead th,
  .col-table thead th, .col-table thead th.is-col-stuck, .quad-head { background: var(--surface); }
  .sidebar, .proj-side { background: var(--surface); }
}
@media (prefers-reduced-transparency: reduce) {
  .panel, .ph-card, .pt-wrap, .sl-card, .quad-grid, .modal, .module-flyout, .card-menu,
  .ft thead th, .ft thead th.ft-card-col, .pt thead th, .mpk-table thead th,
  .col-table thead th, .col-table thead th.is-col-stuck, .quad-head,
  .sidebar, .proj-side, .mask {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .panel, .ph-card, .pt-wrap, .sl-card, .quad-grid, .quad-head, .sidebar, .proj-side { background: var(--surface); }
  .modal, .module-flyout, .card-menu { background: var(--surface); }
}

/* ============================================================================
   耗材分析（仓储 → 耗材分析）
   ★ 2026-09-25（用户口径）：月采购/消耗/结存/周转率/呆滞≈浪费/客户维度/单件单耗 ——
   顶上那排指标卡。六个一排在宽屏，窄屏自动折行（PDA/手机上也看得清）。
   ============================================================================ */
.cons-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 8px;
  margin: 8px 2px 4px;
}
.cons-card {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  min-width: 0;
}
.cons-card-label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  color: var(--ink-3);
  margin-bottom: 3px;
}
.cons-card-value { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }
.cons-card-value .mono { font-size: 17px; }
.cons-card-sub { font-size: 11px; color: var(--ink-3); margin-top: 2px; }
@media (max-width: 560px) {
  .cons-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cons-card-value { font-size: 15px; }
}

/* ★ 2026-09-25：把枪顶部那个仓名现在**可点**（换仓）—— 给它一点"能按"的样子 */
.pda-wh-btn { background: transparent; border: 0; color: inherit; font: inherit; padding: 0 4px; display: inline-flex; align-items: center; gap: 3px; cursor: pointer; }
.pda-wh-btn:active { opacity: 0.6; }
/* ★ 2026-09-25：换仓那个「选」面板挂在壳上（不在扫码框下面），给它一点边距别贴着标题 */
.pda-opt-wrap { padding: 0 10px; }

/**
 * ★ 2026-09-25（用户口径）：「选择仓库的地方太不显眼了」——
 * 把枪的换仓从"顶栏一行小白字"改成**整行一条**：左边「仓库」小标 + 中间仓名加粗 + 右边「切换 ›」，
 * 高 46px（触屏点得准）、底色 surface 和顶栏的黑分开、悬停/按下有反馈。
 */
.pda-whbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 46px;
  padding: 0 12px;
  background: var(--surface);
  border: 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}
.pda-whbar:active { background: var(--surface-2); }
.pda-whbar.on { background: var(--surface-2); }
.pda-whbar-k {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: none;
  color: var(--ink-3);
  font-size: 12px;
}
.pda-whbar-v { font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pda-whbar-x {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  color: var(--blue, var(--ink-2));
  font-size: 12px;
}

/**
 * ★ 2026-09-25：拣货作业台里"套件（瓶子）的子件指路"——每个子件一行：
 * 要拿多少 + 哪些格子里有货。单据仍然只有瓶子一行，这几行只是给配货员照着拿的提示。
 */
.kit-parts { margin-top: 4px; padding: 4px 6px; background: var(--surface-2); border-radius: var(--radius-sm); }
.kit-part { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline; }
.kit-part .kit-name { font-weight: 600; }

/**
 * ★ 2026-09-24（用户口径）：「**销售 跟单 财务工作台的这里，都加入搜索入口，可以根据
 *   销售订单号 项目卡号 采购单号 商品名称去搜索**」——工作台筛选条那一行右边那口搜索：
 * 一个框（回车 / 点「搜索」才查）+ 底下的结果下拉（按家族分组，点一条开单的面板）。
 * 下拉用 `position: absolute` 挂在框下面，窄屏收成 `min(420px, 100vw-24px)`。
 */
.wb-search { position: relative; display: inline-flex; align-items: center; gap: 6px; flex: none; }
.wb-search-box { position: relative; display: inline-flex; align-items: center; }
.wb-search-ico {
  position: absolute;
  left: 8px;
  display: inline-flex;
  color: var(--ink-3);
  pointer-events: none;
}
.wb-search-box input {
  width: 268px;
  max-width: 100%;
  padding: 5px 8px 5px 26px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
}
.wb-search-box input:focus { outline: none; border-color: var(--blue); background: var(--blue-soft); }
/* 结果下拉：贴着框挂，列表自己滚（不把页面撑开） */
.wb-hits {
  position: absolute;
  right: 0;
  top: calc(100% + 5px);
  z-index: 45;
  width: min(420px, calc(100vw - 24px));
  max-height: min(62vh, 520px);
  overflow: auto;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-md);
}
.wb-hits[hidden] { display: none; }
.wb-hits-head { padding: 2px 6px 6px; font-size: 11.5px; color: var(--ink-3); }
.wb-hits-empty { padding: 6px; font-size: 12px; color: var(--ink-3); line-height: 1.7; }
.wb-hits-group + .wb-hits-group { margin-top: 4px; padding-top: 4px; border-top: 1px dashed var(--line-2); }
.wb-hits-glabel { padding: 3px 6px; font-size: 11px; font-weight: 700; color: var(--ink-3); letter-spacing: 0.04em; }
.wb-hits-more { padding: 2px 6px 4px; }
.wb-hit {
  display: block;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.wb-hit:hover { background: var(--blue-soft); }
.wb-hit-line { display: flex; align-items: baseline; gap: 8px; }
.wb-hit-no { font-size: 12.5px; font-weight: 650; }
.wb-hit-type { flex: none; margin-left: auto; font-size: 10.5px; color: var(--ink-3); }
.wb-hit-sub {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/**
 * 窄屏（手机）：那一行本来就挤（筛选条 + 搜索 + 三个入口挤在 430px 里），
 * 所以**让这一行折行**、搜索框自己占满一行；结果下拉改成**从左边缘展开** ——
 * 不然贴着右边那个被压成 ~88px 宽的小盒子挂，会从屏幕左边溢出去（实测 left = -198）。
 */
@media (max-width: 560px) {
  .quad-toolbar { flex-wrap: wrap; }
  .wb-search { flex: 1 1 100%; }
  .wb-search-box { flex: 1 1 auto; }
  .wb-search-box input { width: 100%; }
  .wb-hits { left: 0; right: auto; width: 100%; }
}

/**
 * ★ 2026-09-26（用户口径）：「**仓储部入库员的工作台，需要调整为 待入库 / 部分入库**」——
 * 入库员那两列里的一张卡：不是"一件事"，而是"一张待入库的单"，所以自己一套版式
 * （单号 + 仓库 / 供应商 · 品种 / 采购 · 已入 · 还差 / 预计到货 / 项目 · 销售单 / 去验收）。
 */
.inb-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
}
.inb-card:hover { border-color: var(--blue); }
.inb-head { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.inb-no { font-size: 12.5px; font-weight: 650; color: var(--ink); }
.inb-wh {
  flex: none;
  margin-left: auto;
  max-width: 46%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 10.5px;
}
.inb-sup { font-size: 12px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inb-qty { font-size: 11.5px; color: var(--ink-2); }
.inb-qty .inb-sep { margin: 0 5px; color: var(--line); }
.inb-when { font-size: 11.5px; color: var(--ink-2); }
.inb-late {
  margin-left: 5px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-weight: 600;
}
.inb-ref { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inb-go { align-self: flex-start; margin-top: 1px; }
/* 这张单的明细行上没写进哪个仓（老数据）—— 提示色，别让人以为货就在自己这个仓 */
.inb-wh.is-warn { background: var(--yellow-soft); color: var(--yellow); }
