/* ============================================================================
   主题与密度变量表（2026-09-29）
   ---------------------------------------------------------------------------
   规则：**颜色 / 圆角 / 间距 / 字号一律从这里取，不许再写死色值**。
   换肤 = 给 <html> 挂 data-theme，13 个页面自动生效，不用改任何 HTML。
   挂法见 web/theme-init.js（首帧前挂好，防闪白）+ common.js 的顶栏切换器。

   ⚠️ 语义色（涨跌红绿、状态橙、缺货红）**不随主题走** —— 那是业务约定，
   换肤不能把「跌」的绿换成别的。所以 --ok/--warn/--mid 在四套主题里
   保持同一个色系，只有**底色深浅**跟着主题调整（深色主题下浅绿底 + 深绿字会刺眼）。
   `tools/check_theme_css.py` 会守着这条：主题块之外不许出现硬编码色。
   ============================================================================ */
:root {
  /* —— 品牌 / 主色 —— */
  --brand: #2f7ff0;
  --brand-hover: #1f6ae0;
  --brand-soft: #eaf2fe;      /* 主色浅底：选中行、激活胶囊 */
  --brand-fg: #1a56c4;        /* 主色当文字用（保证对比度） */
  --on-brand: #fff;           /* 主色底上的文字 */

  /* —— 顶部条（云盘风是白的，藏青/深色是深底）—— */
  --hdr-bg: #fff;
  --hdr-fg: #1b2430;
  --hdr-fg-2: #5b6675;
  --hdr-3: #8a94a3;           /* 「P1 · 数据底座」那种角标 */
  --hdr-line: #e9edf4;

  /* —— 面 —— */
  --page: #f1f4f9;            /* 页面底 */
  --surface: #fff;            /* 卡片面 */
  --surface-2: #f7f9fc;       /* 次级面：表头、KPI 卡、只读格 */
  --surface-3: #f0f3f8;

  /* —— 线 —— */
  --line: #e9edf4;            /* 卡片边、分割线 */
  --line-2: #dfe5ee;          /* 加强边 */
  --line-in: #ced6e0;         /* 输入框边 */
  --row-line: #f0f3f7;        /* 表格行分隔线（比 --line 更浅） */

  /* —— 字 —— */
  --text: #1b2430;
  --text-2: #5b6675;          /* 次要 */
  --text-3: #8a94a3;          /* 提示 */
  --text-4: #aab3bf;          /* 最弱（占位符） */

  /* —— 语义：字色固定，底色随主题 —— */
  --ok-bg: #e6f4ea;    --ok-fg: #1e7e34;
  --warn-bg: #fdecea;  --warn-fg: #c62828;
  --mid-bg: #fff3e0;
  --mid-line: #e8cfa8;   --mid-fg: #b57a00;
  --info-bg: #e8f0fe;
  --ok-line: #a8d5b5; --warn-line: #f0c6c6; --info-line: #85b7eb;  --info-fg: #1a56c4;
  --gray-bg: #eef1f6;  --gray-fg: #556677;
  --gold: #e0a020;            /* 趋势图「件数」柱 */
  /* —— 金额向好/向差（财务页涨跌）—— 语义色：色相不跟主题走，只允许深色调深浅 —— */
  --good: #0a9a5b;            /* 收入 / 上涨 */
  --bad: #d92b2b;             /* 支出 / 下跌 */

  /* —— 形状 —— */
  --r: 12px;
  --r-sm: 8px;
  --r-xs: 6px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 6px 16px rgba(16, 24, 40, .05);

  /* —— 密度（compact / comfortable 会覆盖这五个）—— */
  --gap: 16px;                /* 卡片之间 */
  --pad-y: 18px;
  --pad-x: 20px;
  --row-y: 8px;               /* 表格行上下留白 */
  --row-x: 10px;
  --fs: 13px;
  --fs-h: 15px;               /* 卡片标题 */
  --fs-num: 26px;             /* KPI 大数字 */
}

/* ============================================================================
   主题（皮肤）已搬到 web/themes/ 目录 —— 2026-10-23
   ---------------------------------------------------------------------------
   一套皮肤一个文件，服务端把它们拼成 /themes.css 一次返回，所以**这里不再有主题块**。
   加/改皮肤去 web/themes/，规矩见 web/themes/README.md。

   上面 :root 那张表仍然是「默认值 / 兜底值」—— 主题只覆盖它想改的那部分，
   没写的自动落回 :root。这就是「新皮肤只用写 8 个变量也能跑」的原因。
   ============================================================================ */

/* ============================================================================
   密度两档 —— 跟主题正交：可以「云盘风 + 紧凑」，也可以「深色 + 舒适」
   ============================================================================ */
html[data-density="compact"] {
  --gap: 10px; --pad-y: 12px; --pad-x: 14px;
  --row-y: 5px; --row-x: 9px;
  --fs: 12.5px; --fs-h: 14px; --fs-num: 22px;
}
html[data-density="comfortable"] {
  --gap: 16px; --pad-y: 18px; --pad-x: 20px;
  --row-y: 9px; --row-x: 11px;
  --fs: 13.5px; --fs-h: 15px; --fs-num: 28px;
}

/* ============================================================================
   以下为页面样式：结构一律不动，只把色值 / 圆角 / 间距换成变量
   ============================================================================ */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; background: var(--page);
  color: var(--text); font-size: var(--fs); }
header { background: var(--hdr-bg); color: var(--hdr-fg); padding: 14px 24px;
  display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
  border-bottom: 1px solid var(--hdr-line); }
header h1 { font-size: 17px; font-weight: 600; }
header h1 a { color: var(--hdr-fg); text-decoration: none; }
header .muted { color: var(--hdr-3); }
.stat { font-size: 13px; color: var(--hdr-fg-2); opacity: .96; }
.stat b { font-size: 16px; margin-right: 2px; color: var(--hdr-fg); }
/* 导航有 10 个入口，窗口窄时要能换行，不能被裁掉（之前不换行，右边几项会跑到屏幕外） */
nav { background: var(--surface); border-bottom: 1px solid var(--line); padding: 0 24px;
  display: flex; flex-wrap: wrap; }
nav a, nav button { background: none; border: none; padding: 11px 14px; font-size: 13.5px;
  cursor: pointer; color: var(--text-2); border-bottom: 2px solid transparent;
  text-decoration: none; display: inline-block; white-space: nowrap; font-family: inherit; }
nav a.active, nav button.active { color: var(--brand-fg); border-bottom-color: var(--brand);
  font-weight: 600; }
nav a:hover, nav button:hover { color: var(--brand-fg); }
.toolbar { padding: 14px 24px; display: flex; gap: 10px; flex-wrap: wrap;
  background: var(--surface); border-bottom: 1px solid var(--line); align-items: center; }
.toolbar input, .toolbar select { padding: 7px 10px; border: 1px solid var(--line-in);
  border-radius: var(--r-xs); font-size: var(--fs); background: var(--surface); color: var(--text); }
.toolbar input[type=text], .toolbar input:not([type]) { width: 260px; }
/* 品类胶囊：全部 / 智能 / 非智能 */
.pills { display: flex; border: 1px solid var(--line-in); border-radius: var(--r-xs);
  overflow: hidden; }
.pills .pill { background: var(--surface); border: none; border-right: 1px solid var(--line);
  padding: 7px 12px; font-size: var(--fs); cursor: pointer; color: var(--text-2); }
.pills .pill:last-child { border-right: none; }
.pills .pill b { color: var(--brand-fg); margin-left: 3px; }
.pills .pill.on { background: var(--brand); color: var(--on-brand); }
.pills .pill.on b { color: var(--on-brand); }
.chk-inline { display: flex; align-items: center; gap: 5px; font-size: 12px;
  color: var(--text-2); cursor: pointer; }
.chk-inline input[type=checkbox] { width: auto; }
main { padding: 16px 24px 40px; }
table { width: 100%; border-collapse: collapse; background: var(--surface);
  font-size: var(--fs); border-radius: var(--r); overflow: hidden; box-shadow: var(--shadow); }
th, td { padding: var(--row-y) var(--row-x); text-align: left;
  border-bottom: 1px solid var(--row-line); white-space: nowrap; }
th { background: var(--surface-2); color: var(--text-2); font-weight: 600;
  position: sticky; top: 0; border-bottom: 1px solid var(--line-2); }
tr:hover td { background: var(--brand-soft); }
.tag { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 12px; }
.tag-full { background: var(--ok-bg); color: var(--ok-fg); }
.tag-lite { background: var(--mid-bg); color: var(--mid-fg); }
.tag-cat { background: var(--gray-bg); color: var(--gray-fg); }
.tag-new { background: var(--info-bg); color: var(--info-fg); }
.tag-ok { background: var(--ok-bg); color: var(--ok-fg); }
.tag-warn { background: var(--warn-bg); color: var(--warn-fg); }
/* 报价来源筛选条：一个项目挂多份报价时（易来智能 + 非智能灯具…）才出现 */
.src-bar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 12px; background: var(--gray-bg);
  border-radius: var(--r-sm); font-size: 13px; }
.src-bar-tip { margin-left: auto; font-size: 12px; }
.src-chip { padding: 3px 10px; border: 1px solid var(--line-in); border-radius: 20px;
  background: var(--surface); color: var(--text-2); font-size: 12px;
  cursor: pointer; font-family: inherit; }
.src-chip:hover { border-color: var(--brand); }
.src-chip.on { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.src-chip.on .muted { color: inherit; opacity: .8; }
.muted { color: var(--text-3); }
.count { padding: 8px 2px; color: var(--text-3); font-size: 12px; }
.btn { padding: 8px 16px; border: none; border-radius: var(--r-sm); background: var(--brand);
  color: var(--on-brand); font-size: var(--fs); cursor: pointer; font-family: inherit; }
.btn:hover { background: var(--brand-hover); }
.btn:disabled { background: var(--line-in); color: var(--text-3); cursor: not-allowed; }
.btn-light { background: var(--surface); color: var(--brand-fg); border: 1px solid var(--line-in); }
.btn-light:hover { background: var(--brand-soft); }
/* 表格行尾的小按钮：原来一行行写 style="padding:2px 10px"，行内样式优先级
   压过样式表，窄屏下盖不住、被裁成「修正」「删」（2026-10-20 收进类里） */
.btn-xs { padding: 2px 10px; font-size: 12px; text-decoration: none; }
.card { background: var(--surface); border-radius: var(--r); box-shadow: var(--shadow);
  padding: var(--pad-y) var(--pad-x); margin-bottom: var(--gap); }
.card h3 { font-size: var(--fs-h); margin-bottom: 12px; color: var(--text); font-weight: 600; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px 18px; }
.form-grid label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
.form-grid input, .form-grid select, .form-grid textarea { width: 100%; padding: 7px 9px;
  border: 1px solid var(--line-in); border-radius: var(--r-xs); font-size: var(--fs);
  font-family: inherit; background: var(--surface); color: var(--text); }
.hint { color: var(--text-3); font-size: 12px; margin-top: 8px; }
.row-skip td { opacity: .4; text-decoration: line-through; }
.pill { display: inline-block; background: var(--gray-bg); border-radius: 10px;
  padding: 1px 8px; margin-right: 4px; font-size: 12px; color: var(--gray-fg); }
.summary { display: flex; gap: 18px; flex-wrap: wrap; }
.summary div b { font-size: 18px; color: var(--brand-fg); }
.card table { box-shadow: none; margin-bottom: 0; }
.grp-head { background: var(--brand-soft) !important; color: var(--brand-fg) !important; }
/* 方案表：每个分组是一张独立表格，用固定列宽 + colgroup 保证各分组的列上下对齐 */
.plan-tbl { table-layout: fixed; }
.plan-tbl td, .plan-tbl tbody th { overflow: hidden; text-overflow: ellipsis;
  vertical-align: middle; }
.plan-tbl .col-head th, .plan-tbl tbody td { overflow: hidden; text-overflow: ellipsis; }
.plan-tbl .num { text-align: right; }
.plan-tbl .ops { text-align: center; overflow: visible; }
.plan-tbl .cell-name { white-space: normal; word-break: normal; overflow-wrap: break-word; }
/* 第一列在多份来源时还要塞一个来源标签，得允许换行——
   表格是 nowrap + ellipsis，不放开的话标签会被直接裁掉（少字，还不报错） */
.plan-tbl .cell-src { white-space: normal; }
.plan-tbl select { max-width: 100%; }
/* 已装齐的行整体淡化：清单先让人扫到「还欠什么」，装完的别跟待装的抢眼（2026-10-27 老板要）。
   只压文字颜色，不动「替代 / 定制件」标签的底色 —— 那是「这行特殊」的提示，淡掉就没用了。 */
.pg-row-done td, .pg-row-done td b { color: var(--text-3); }
.modal-mask { position: fixed; inset: 0; background: rgba(20, 30, 45, .45); display: flex;
  align-items: flex-start; justify-content: center; padding: 60px 20px; z-index: 100;
  overflow: auto; }
.modal-box { background: var(--surface); border-radius: var(--r); padding: 20px 22px;
  width: min(780px, 100%); box-shadow: 0 10px 40px rgba(0, 0, 0, .25); color: var(--text); }
.modal-box h3 { font-size: var(--fs-h); color: var(--text); margin-bottom: 14px; }
textarea { font-family: inherit; }
code { background: var(--surface-3); padding: 1px 6px; border-radius: 4px; font-size: 12px; }
.cell-in { padding: 4px 6px; border: 1px solid var(--line); border-radius: 4px;
  background: var(--surface-2); font-size: var(--fs); font-family: inherit; text-align: right;
  color: var(--text); }
.cell-in:focus { border-color: var(--brand); background: var(--surface); outline: none; }
.ar { text-align: right; }
.lbl { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
.subtabs { background: var(--surface); border-bottom: 1px solid var(--line); padding: 0 24px;
  display: flex; gap: 6px; }
.subtabs button { background: none; border: none; padding: 10px 14px; font-size: var(--fs);
  cursor: pointer; color: var(--text-2); border-bottom: 2px solid transparent;
  font-family: inherit; }
.subtabs button.active { color: var(--brand-fg); border-bottom-color: var(--brand);
  font-weight: 600; }

/* ---------- 产品库 ---------- */
/* ★ 2026-10-20：这张表 16 列，桌面 1440 下原来撑到 1996px（超屏 556px，得左右拖）。
   根因是 table-layout 默认 auto —— colgroup 里的百分比只是「建议」，单元格 nowrap 的
   最小宽度一超，浏览器就把表撑开。改成 fixed，百分比变成硬约束，表宽恒定 = 容器宽。
   代价是超长内容不能再把格子顶开，所以：窄列单行省略（悬停看 title）、
   名称/简称/档案允许换行、下拉和输入框一律 100% 填满格子。 */
.prod-tbl { table-layout: fixed; }
.prod-tbl th { white-space: normal; font-size: 12px; line-height: 1.3;
  padding: 8px 7px; vertical-align: bottom; }
.prod-tbl td { overflow: hidden; text-overflow: ellipsis; padding: 7px; }
/* 名称、简称、档案：宁可换行也不省略（这三列必须看全） */
.prod-tbl td.cell-name, .prod-tbl td.cell-alias, .prod-tbl td.cell-prof {
  white-space: normal; word-break: normal; overflow-wrap: break-word; }
.prod-tbl td.cell-alias { font-size: 12px; color: var(--text-2); }
/* 分类·品牌：分类 tag 在上，品牌小字在下 */
.prod-tbl td.cell-cat .sub-line { display: block; margin-top: 3px; font-size: 11.5px;
  color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prod-tbl td.cell-cat .tag { font-size: 11.5px; padding: 1px 6px; }
/* 系列/颜色/规格：一行展示，超了省略，悬停看全 */
.prod-tbl td.cell-spec { font-size: 12px; color: var(--text-2); }
/* 格子里的控件不许撑破列：下拉、输入框都按格子宽度算（勾选框例外，它自己给 width:auto） */
.prod-tbl td select, .prod-tbl td input.cell-in { width: 100%; max-width: 100%;
  box-sizing: border-box; padding: 3px 4px; font-size: 12px;
  border: 1px solid var(--line-in); border-radius: 4px; background: var(--surface); }
/* 原生 select 的箭头画在内容区右端，列一窄就被挤出去（实测「库存品」那列箭头直接没了）。
   自己画一个，位置就永远是「右边 4px」，跟文字长短无关。颜色取中性灰，深浅主题都看得见。 */
.prod-tbl td select {
  appearance: none; -webkit-appearance: none; padding-right: 15px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'><path d='M1 1l3.5 3.5L8 1' fill='none' stroke='%238b93a1' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat; background-position: right 4px center; }
.prod-tbl td.cell-num input { text-align: right; }
.prod-tbl td.cell-prof .tag { font-size: 11.5px; padding: 1px 6px; }
/* 行尾操作：三个小按钮挤在一格里，必须让它自己排 */
.prod-tbl td.cell-ops { white-space: nowrap; text-align: right; }
.prod-tbl td.cell-ops .btn { padding: 2px 5px; font-size: 11.5px; margin-left: 2px; }
.prod-tbl td.cell-ops .btn-del { color: var(--warn-fg); }
/* 表头点击排序：升序 → 降序 → 恢复默认。箭头位固定宽度，切换时不抖列宽 */
.prod-tbl th.sortable { cursor: pointer; user-select: none; }
.prod-tbl th.sortable:hover { background: var(--brand-soft); color: var(--brand-fg); }
.prod-tbl th.sorted { background: var(--brand-soft); color: var(--brand-fg); }
.prod-tbl th .sarr { display: inline-block; width: 9px; font-size: 9px; font-style: normal;
  text-align: right; color: var(--brand-fg); }
.prod-tbl th.sorted .sarr { font-weight: 700; }
.sort-now { color: var(--brand-fg); font-weight: 600; }
.sort-clr { color: var(--text-3); margin-left: 6px; }

/* ---------- 总览看板 ---------- */
.kpis { display: grid; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 12px; }
.kpi { background: var(--surface-2); border-radius: var(--r-sm); padding: 12px 14px; }
.kpi-v { font-size: var(--fs-num); font-weight: 600; color: var(--brand-fg); line-height: 1.25; }
.kpi-v.warn { color: var(--warn-fg); }
.kpi-l { font-size: 13px; color: var(--text); margin-top: 2px; }
.kpi-s { font-size: 12px; color: var(--text-3); margin-top: 3px; }
.cols { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-start; }
.sbar { display: flex; align-items: center; gap: 10px; padding: 5px 6px; cursor: pointer; }
.sbar-n { width: 62px; color: var(--text-2); font-size: 12px; flex: none; }
.sbar-bar { flex: 1; height: 10px; background: var(--gray-bg); border-radius: 6px; overflow: hidden; }
.sbar-bar i { display: block; height: 100%; background: var(--brand); border-radius: 6px; }
.alert-row { padding: 8px 6px; border-bottom: 1px solid var(--row-line); cursor: pointer; }
.alert-row:hover { background: var(--brand-soft); }
.alert-name { font-size: 13px; margin-bottom: 4px; }
.prog { height: 8px; background: var(--gray-bg); border-radius: 5px; overflow: hidden;
  min-width: 110px; }
.prog i { display: block; height: 100%; border-radius: 5px; }
.prog-s { font-size: 11px; color: var(--text-3); margin-top: 3px; }
.chk { display: flex; gap: 6px; align-items: center; font-size: 13px; color: var(--text-2); }

/* ---------- 流水看板 ---------- */
.sbar-v { font-size: 12px; color: var(--text-2); width: 140px; text-align: right; flex: none; }
.trend { display: flex; gap: 6px; align-items: flex-end; height: 180px; padding: 4px 2px 0; }
.trend .tb { flex: 1; display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; height: 100%; min-width: 0; }
/* 每个月一个柱组：笔数 / 件数并排、底端对齐，共用同一刻度 */
.trend .tb .tbar { display: flex; gap: 4px; align-items: flex-end; justify-content: center;
  width: 100%; flex: 1 1 auto; min-height: 0; }
.trend .tb .tbar i { display: block; width: 100%; max-width: 24px; border-radius: 4px 4px 0 0;
  min-height: 2px; }
.trend .tb .tbar i.s-cnt { background: var(--brand); }
.trend .tb .tbar i.s-qty { background: var(--gold); }
.trend .tb .tvals { display: flex; gap: 8px; justify-content: center; font-size: 10px;
  line-height: 1.2; margin-bottom: 3px; white-space: nowrap; }
.trend .tb .tvals em { font-style: normal; font-weight: 600; }
.trend .tb .tvals em.s-cnt { color: var(--brand-fg); }
.trend .tb .tvals em.s-qty { color: var(--mid-fg); }
.trend .tb span { font-size: 11px; color: var(--text-3); margin-top: 5px; white-space: nowrap; }
/* 笔数 / 件数 切换 + 图例 */
.trend-pick { display: flex; gap: 12px; align-items: center; font-size: 12px; font-weight: 400; }
.trend-pick label { display: flex; gap: 5px; align-items: center; color: var(--text-2);
  cursor: pointer; }
.trend-lg { display: inline-flex; gap: 12px; align-items: center; margin-right: 12px;
  vertical-align: middle; }
.trend-lg .lg { display: inline-flex; gap: 5px; align-items: center; color: var(--text-2); }
.trend-lg .lg i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.trend-lg .lg i.s-cnt { background: var(--brand); }
.trend-lg .lg i.s-qty { background: var(--gold); }
.matrix td, .matrix th { text-align: right; }
.matrix td:first-child, .matrix th:first-child { text-align: left; }

/* ---------- 售后 ---------- */
.as-steps { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 4px 0 14px; }
.as-step { font-size: 12px; padding: 3px 10px; border-radius: 12px; background: var(--gray-bg);
  color: var(--gray-fg); }
.as-step.done { background: var(--ok-bg); color: var(--ok-fg); }
.as-step.cur { background: var(--brand); color: var(--on-brand); font-weight: 600; }
.as-sep { color: var(--line-in); font-size: 12px; }
.as-done { color: var(--ok-fg); font-weight: 600; }
.as-todo { color: var(--warn-fg); font-weight: 600; }

/* ---------- 项目 · 安装进度速览（默认折叠，点标题展开）---------- */
.pg-head { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.card .pg-head h3 { margin: 0; }
.pg-head:hover h3 { color: var(--brand-fg); }
.pg-caret { color: var(--brand-fg); font-size: 12px; width: 12px; flex: none; }
.pg-brief { font-size: 13px; color: var(--text-2); }
.pg-brief b { color: var(--brand-fg); font-size: 15px; }
.pg-brief b.pg-ok { color: var(--ok-fg); }
.pg-brief b.pg-warn { color: var(--warn-fg); }
.pg-brief b.pg-mid { color: var(--mid-fg); }
.pg-wrap { margin: 14px 0 12px; }
.pg-bar { display: flex; height: 12px; background: var(--gray-bg); border-radius: 6px;
  overflow: hidden; }
.pg-bar i { display: block; min-width: 0; }
.pg-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--text-2);
  margin-top: 6px; }
.pg-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 2px;
  margin-right: 5px; vertical-align: middle; }
.pg-legend b { color: var(--text); }
.pg-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.pg-fbtn { padding: 4px 12px; font-size: 12.5px; }
.pg-fbtn.on { background: var(--brand); color: var(--on-brand); border-color: var(--brand);
  font-weight: 600; }
.pg-fn { color: var(--text-3); }
.pg-fbtn.on .pg-fn { color: var(--hdr-fg-2); }
.pg-tbl td { padding: 6px 10px; }
.pg-row-done td { opacity: .5; }

/* ---------- 顶栏风格切换器（common.js 注入，13 个页面共用）---------- */
.ui-sw { position: relative; margin-left: auto; display: flex; align-items: center; gap: 8px; }
.ui-sw-btn { background: var(--hdr-bg); color: var(--hdr-fg); border: 1px solid var(--hdr-line);
  border-radius: var(--r-xs); padding: 5px 11px; font-size: 12.5px; cursor: pointer;
  font-family: inherit; display: inline-flex; align-items: center; gap: 6px; }
html[data-theme="navy"] .ui-sw-btn, html[data-theme="dark"] .ui-sw-btn {
  border-color: rgba(255, 255, 255, .3); }
.ui-sw-btn:hover { background: var(--brand-soft); color: var(--brand-fg); }
.ui-sw-btn .sw-dot { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--line-in); }
.ui-sw-pop { position: absolute; right: 0; top: calc(100% + 8px); z-index: 200;
  background: var(--surface); color: var(--text); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: 0 10px 30px rgba(0, 0, 0, .18); padding: 12px 14px;
  width: 268px; display: none; }
.ui-sw-pop.open { display: block; }
.ui-sw-pop h4 { font-size: 12px; font-weight: 600; color: var(--text-3); margin: 0 0 8px; }
.ui-sw-pop .sw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.ui-sw-pop .sw-opt { display: flex; align-items: center; gap: 7px; padding: 7px 9px;
  border: 1px solid var(--line); border-radius: var(--r-sm); cursor: pointer; font-size: 12.5px;
  background: var(--surface); }
.ui-sw-pop .sw-opt:hover { border-color: var(--brand); }
.ui-sw-pop .sw-opt.on { border-color: var(--brand); background: var(--brand-soft);
  color: var(--brand-fg); font-weight: 600; }
.ui-sw-pop .sw-opt i { width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--line-in);
  flex: none; }
.ui-sw-pop .sw-dens { display: flex; gap: 8px; margin-top: 4px; }
.ui-sw-pop .sw-dens .sw-opt { flex: 1; justify-content: center; }
.ui-sw-pop .sw-foot { font-size: 11.5px; color: var(--text-3); margin-top: 10px;
  padding-top: 8px; border-top: 1px solid var(--line); }

/* 首页「外观」卡片里的色点：同一套数据，铺开摆大一点。
   样式放 style.css 而不是 home.html 内联，是为了以后设置页/任何页面都能直接复用
   window.smartpmRenderSkinPicker(box, note)。 */
.skins { display: flex; flex-wrap: wrap; gap: 9px; }
.skin { display: flex; align-items: center; gap: 8px; padding: 8px 14px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface);
  font-size: 13px; color: var(--text); transition: border-color .15s, background .15s; }
.skin:hover { border-color: var(--brand); }
.skin.on { border-color: var(--brand); background: var(--brand-soft);
  color: var(--brand-fg); font-weight: 500; }
.skin i { width: 13px; height: 13px; border-radius: 50%; flex: none;
  border: 1px solid var(--line-in); }
.skin-note { font-size: 11.5px; color: var(--text-3); margin-top: 9px; }

/* ============================================================================
   打印一律用浅色 —— 交付单会被打印 / 存 PDF，深色主题直接打出来是一整片黑。
   注意选择器要写 `html[data-theme]`（带属性）才压得住主题块里的同名变量。
   ============================================================================ */
@media print {
  html[data-theme] {
    --page: #fff; --surface: #fff; --surface-2: #fff; --surface-3: #fff;
    --line: #ccc; --line-2: #ccc; --line-in: #ccc; --row-line: #e5e5e5;
    --text: #000; --text-2: #333; --text-3: #666; --text-4: #888;
    --hdr-bg: #fff; --hdr-fg: #000; --hdr-fg-2: #333; --hdr-3: #666; --hdr-line: #ccc;
    --brand: #000; --brand-hover: #000; --brand-soft: #fff; --brand-fg: #000;
    --on-brand: #fff;
    --ok-bg: #fff; --ok-fg: #1e7e34;
    --warn-bg: #fff; --warn-fg: #c62828;
    --mid-bg: #fff; --mid-fg: #8a5a00;
    --info-bg: #fff;
  --ok-line: #ccc; --warn-line: #ccc; --info-line: #ccc; --info-fg: #1a56c4;
    --gray-bg: #fff; --gray-fg: #333;
    --gold: #b57a00;
    --shadow: none;
  }
  .ui-sw { display: none !important; }
}

/* ============================================================================
   追踪卡（2026-10-20）
   ---------------------------------------------------------------------------
   项目详情首屏就这一张卡：阶段条 + 停留天数 + 下一步 + 现场信息 + 现场问题。
   约束：
     1. **金额不许进来** —— 本系统服务的是安装施工和调试，钱在别的环节算。
     2. 手机上整张卡要一屏看得完：字号小、间距紧、能横滑就横滑。
     3. 颜色一律取变量（check_theme_css.py 会守这条）。
   ============================================================================ */
.track { padding-bottom: 10px; }
.track-top { display: flex; justify-content: space-between; align-items: flex-start;
  gap: 12px; flex-wrap: wrap; }
.track-meta { font-size: 12px; color: var(--text-2); margin-top: 4px; }
.track-acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* —— 阶段条 ——
   四个状态四种画法，缺一不可：
     done     实心绿点 + 实线（流水里有这条记录）
     skipped  空心虚线点 + 灰线（已经过了但没有记录 = 当年跳过了这一步）
     current  主色点 + 光晕
     todo     空心灰点 */
.flow { display: flex; overflow-x: auto; padding: 16px 0 2px; margin: 0 -2px;
  scrollbar-width: none; }
.flow::-webkit-scrollbar { display: none; }
.flow-node { flex: 1 0 74px; min-width: 74px; text-align: center; position: relative;
  padding: 0 4px; cursor: default; }
.flow-node::before, .flow-node::after { content: ""; position: absolute; top: 6px;
  height: 2px; background: var(--line-2); }
.flow-node::before { left: 0; right: 50%; }
.flow-node::after { left: 50%; right: 0; }
.flow-node:first-child::before { display: none; }
.flow-node:last-child::after { display: none; }
.flow-node.done::before, .flow-node.done::after { background: var(--ok-fg); }
.flow-node.current::before { background: var(--ok-fg); }
.flow-node.skipped::before, .flow-node.skipped::after {
  background: repeating-linear-gradient(to right, var(--text-4) 0 3px, transparent 3px 7px); }
.fn-dot { width: 14px; height: 14px; border-radius: 50%; margin: 0 auto 7px;
  background: var(--surface); border: 2px solid var(--line-in); box-sizing: border-box;
  position: relative; z-index: 1; }
.flow-node.done .fn-dot { background: var(--ok-fg); border-color: var(--ok-fg); }
.flow-node.current .fn-dot { background: var(--brand); border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft); }
.flow-node.skipped .fn-dot { border-style: dashed; border-color: var(--text-4); }
.fn-name { font-size: 12px; color: var(--text-2); white-space: nowrap; }
.flow-node.done .fn-name { color: var(--text); }
.flow-node.current .fn-name { color: var(--brand-fg); font-weight: 500; }
.flow-node.skipped .fn-name { color: var(--text-3); }
.fn-date { font-size: 11px; color: var(--text-3); margin-top: 2px; min-height: 14px; }

.track-stall { font-size: 13px; color: var(--text-2); margin: 4px 0 12px; }
.track-stall b { font-size: 16px; margin: 0 2px; }
.track-stall.warn { color: var(--mid-fg); }
.track-stall.bad { color: var(--warn-fg); }

.track-next { display: flex; justify-content: space-between; align-items: center;
  gap: 12px; flex-wrap: wrap; padding: 12px 14px;
  background: var(--surface-2); border-radius: var(--r-sm); }
.tn-left { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1 1 240px; }
.tn-label { font-size: 11px; color: var(--on-brand); background: var(--brand);
  padding: 2px 9px; border-radius: 20px; white-space: nowrap; }
.tn-text { font-size: 14px; color: var(--text); line-height: 1.4; }
.tn-right { display: flex; gap: 8px; margin-left: auto; }

/* —— 现场信息：门锁 / WiFi / 米家 —— */
.site { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 12px; }
.site-head { font-size: 12px; color: var(--text-2); display: flex; align-items: center;
  gap: 8px; margin-bottom: 8px; }
.site-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.site-cell { padding: 8px 10px; background: var(--surface-2);
  border: 1px solid var(--line); border-radius: var(--r-xs); cursor: pointer;
  transition: border-color .15s; min-width: 0; }
.site-cell:hover { border-color: var(--line-2); }
.sc-label { font-size: 11px; color: var(--text-3); margin-bottom: 2px; }
.sc-val { font-size: 13px; color: var(--text); word-break: normal; overflow-wrap: break-word; line-height: 1.4; }
.site-cell.empty .sc-val { color: var(--text-4); }
.sc-val.plain { color: var(--brand-fg); }
.site-btns { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }

/* —— 现场问题 —— */
.prob { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
.prob-head { font-size: 13px; display: flex; align-items: center; gap: 8px;
  color: var(--text-2); flex-wrap: wrap; }
.prob-head b { color: var(--warn-fg); font-size: 15px; }
.prob-head.muted { color: var(--text-3); }
.pb-list { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.pb-row { display: flex; align-items: flex-start; gap: 8px; font-size: 13px;
  padding: 7px 10px; background: var(--warn-bg); border-radius: var(--r-xs); }
.pb-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--warn-fg);
  margin-top: 6px; flex: 0 0 auto; }
.pb-txt { flex: 1; color: var(--text); line-height: 1.5; }
.pb-ok { font-size: 12px; color: var(--brand-fg); background: none; border: none;
  cursor: pointer; padding: 0; white-space: nowrap; font-family: inherit; }

/* —— 阶段清单弹窗 —— */
.stage-box { width: min(560px, 100%); }
.ck-list { max-height: 56vh; overflow: auto; margin-bottom: 10px; }
.ck-row { padding: 10px 0; border-bottom: 1px solid var(--line); }
.ck-row:last-child { border-bottom: none; }
.ck-main { margin-bottom: 8px; }
.ck-label { font-size: 14px; color: var(--text); font-weight: 500; }
.ck-hint { font-size: 12px; color: var(--text-3); margin-top: 3px; line-height: 1.5; }
.ck-prev { display: inline-block; font-size: 12px; color: var(--info-fg);
  background: var(--info-bg); padding: 1px 7px; border-radius: 4px; margin-top: 5px; }
.tri { display: inline-flex; border: 1px solid var(--line-in); border-radius: var(--r-xs);
  overflow: hidden; }
.tri button { border: none; background: var(--surface); color: var(--text-2);
  font-size: 12px; padding: 7px 13px; cursor: pointer; font-family: inherit;
  border-right: 1px solid var(--line-in); }
.tri button:last-child { border-right: none; }
.tri button.on { background: var(--brand); color: var(--on-brand); }
.tri button[data-s="fix"].on { background: var(--warn-fg); }
.tri button[data-s="na"].on { background: var(--text-3); }
.ck-note { width: 100%; margin-top: 8px; padding: 6px 9px; border: 1px solid var(--line-in);
  border-radius: var(--r-xs); font-size: 13px; font-family: inherit; box-sizing: border-box; }
.ck-blockers { color: var(--warn-fg); min-height: 16px; font-size: 12px;
  margin-bottom: 8px; line-height: 1.6; }

/* —— 阶段记录卡（2026-10-24）——
   放样/预埋/调试这些「必须人到现场判断」的清单，以前只能靠推进阶段顺带填，
   填完就埋进流水，页面上没地方回看。这张卡把它们摆出来，只读为主，
   改是逐条点「修改」进弹窗（就地改会把卡片搞成一堆输入框，学习成本反而高）。 */
.sl-row { padding: 12px 0; border-bottom: 1px solid var(--row-line); }
.sl-row:first-child { padding-top: 2px; }
.sl-row:last-child { border-bottom: none; padding-bottom: 0; }
.sl-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 7px; }
.sl-meta { font-size: 12px; color: var(--text-3); }
.sl-gap { flex: 1; }
.sl-fixed { font-size: 11px; color: var(--mid-fg); background: var(--mid-bg);
  padding: 0 6px; border-radius: 8px; }
.sl-empty { font-size: 12px; color: var(--text-3); }
.sl-tail { font-size: 12px; color: var(--text-3); margin-top: 5px; }
/* 两列：放样六项一屏看完，手机上一列 */
.sl-ck { display: grid; grid-template-columns: 1fr 1fr; gap: 1px 20px; }
.sl-item { display: flex; align-items: baseline; gap: 6px; font-size: 13px;
  padding: 3px 0; min-width: 0; flex-wrap: wrap; }
.sl-dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  position: relative; top: -2px; }
.sl-dot.ok { background: var(--ok-fg); }
.sl-dot.fix { background: var(--warn-fg); }
.sl-dot.na { background: var(--text-4); }
.sl-lb { color: var(--text-2); }
.sl-v { font-size: 12px; }
.sl-v.ok { color: var(--ok-fg); }
.sl-v.fix { color: var(--warn-fg); }
.sl-v.na { color: var(--text-3); }
.sl-prev { font-size: 11px; color: var(--info-fg); background: var(--info-bg);
  padding: 0 6px; border-radius: 4px; }
.sl-n { font-size: 12px; color: var(--text-3); }
.sl-note { font-size: 12px; color: var(--text-2); background: var(--surface-2);
  padding: 6px 9px; border-radius: var(--r-xs); margin-top: 7px; line-height: 1.6;
  white-space: pre-wrap; }
/* 阶段记录里那一段的现场照片（2026-10-27）：一排缩略图，点开看原图。
   grid 自动铺 + 定宽定高 = 几张都整齐，不会被横图竖图撑变形。 */
.sl-photos { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.sl-photos a { display: block; line-height: 0; border-radius: var(--r-xs);
  overflow: hidden; border: 1px solid var(--row-line); }
.sl-photos img { width: 92px; height: 92px; object-fit: cover; display: block;
  background: var(--surface-2); }
.sl-photos a:hover { border-color: var(--brand); }
@media (max-width: 720px) {
  .sl-photos img { width: 76px; height: 76px; }
}

/* ============================================================================
   移动端（2026-10-20）
   ---------------------------------------------------------------------------
   老板和师傅都在手机上用，之前整个样式表只有 @media print、零响应式断点。
   这里补第一层：窄屏下把「并排」拆成「竖排」，表格交给各页自己的 overflow:auto。
   ============================================================================ */
@media (max-width: 720px) {
  main, .wrap { padding-left: 10px !important; padding-right: 10px !important; }
  .card { padding: 14px 12px; border-radius: var(--r-sm); }
  .form-grid { grid-template-columns: 1fr !important; }
  h3 { font-size: 15px; }
  /* 顶栏：手机上最占地方的就是这两块。原来 header 挤成两行、nav 换行成三行，
     加起来吃掉近 300px，首屏几乎看不到内容。改成「标题一行 + 导航一行横滑」。 */
  header { padding: 10px 12px; gap: 10px; }
  header h1 { font-size: 15px; }
  header h1 .muted { display: none; }
  header .stat { font-size: 12px; }
  nav { padding: 0 6px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  nav::-webkit-scrollbar { display: none; }
  nav a, nav button { padding: 9px 11px; font-size: 13px; }
  .track-acts { width: 100%; }
  .track-acts .btn { flex: 1 1 auto; }
  .tn-right { width: 100%; margin-left: 0; }
  .tn-right .btn { flex: 1 1 auto; }
  .site-grid { grid-template-columns: repeat(2, 1fr); }
  .flow-node { flex: 0 0 64px; min-width: 64px; }
  .fn-name { font-size: 11px; }
  /* 弹窗在手机上要占满，否则清单滚不动 */
  .stage-box { width: 100%; max-height: 92vh; }
  .ck-list { max-height: 52vh; }
  .tri button { padding: 8px 12px; }
  /* 阶段记录卡：两列清单在手机上挤成麻花，改一列 */
  .sl-ck { grid-template-columns: 1fr; }
}

/* 列表页列显隐（2026-10-20）：11 列在手机上没法看，次要列窄屏隐藏，
   小区/房号/安装进度并进项目名那一行（.only-sm）。
   手机剩 4 列，必须定宽 —— table-layout:auto 会按内容撑，直接把表撑到屏幕外。 */
.only-sm { display: none; }
/* 小字副行里不该被拆开的词（「不算上工」「领：某某」） */
.nw { white-space: nowrap; }
@media (max-width: 1024px) {
  .hide-sm { display: none !important; }
  .only-sm {
    display: block !important; color: var(--text-3); font-size: 12px;
    margin-top: 2px; font-weight: 400; line-height: 1.45;
  }
  .list-tbl { table-layout: fixed; }
  .list-tbl th, .list-tbl td { padding: 9px 6px; font-size: 13px; }
  /* td 默认 nowrap（第 217 行），手机上副行只留一行、放不下就省略号，
     不能换行 —— 换行会把「0/218 件」这种数字从中间劈开，比截断还难读 */
  .list-tbl .col-name { width: auto; white-space: normal; }
  .list-tbl .col-name .only-sm {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .list-tbl .col-name b { font-size: 13.5px; }
  .list-tbl .col-stage { width: 92px; }
  .list-tbl .col-stage .tag { font-size: 11.5px; padding: 2px 6px; }
  .list-tbl .col-stage .only-sm { font-size: 11.5px; margin-top: 3px; }
  /* 平板/窄桌面这一档（601~1024）用**百分比**：表格宽度从 692 到 948 都能铺满。
     手机那一档（≤600）单独在文件后面用 px 覆盖，见「手机档」那段。 */
  .list-tbl th:nth-child(2)  { width: 4%  !important; }
  .list-tbl th:nth-child(3)  { width: 53% !important; }
  .list-tbl th:nth-child(6)  { width: 19% !important; }
  .list-tbl th:nth-child(11) { width: 11% !important; }
  .list-tbl th:nth-child(12) { width: 13% !important; }
  .list-tbl .col-star { padding-left: 0; padding-right: 0; }
  .list-tbl .star-btn { font-size: 15px; padding: 2px 0; }
  /* 阶段副行「228/228 100%」不许跟着 td 的 nowrap 单行顶着 —— 单行要 82px，
     手机上排不下（加上星标列之后更排不下）。
     做法：手机上把百分比藏掉（.pct-sm），只留「已装/应装」；百分比在桌面版的
     「安装进度」列里本来就有，窄屏让位给名称更划算。
     万一遇到 117.5/117.5 这种长数字，允许在 **"/" 后面**折行（不加 word-break:
     break-word —— 那会在数字中间断，出现「117.5/117.」+「5」这种看着像错的断法）。 */
  .list-tbl .col-stage .pct-sm { display: none; }
  .list-tbl .col-stage .only-sm { white-space: normal; }
  .list-tbl .col-stall { text-align: right; }
  .list-tbl .col-act { text-align: right; }
  .list-tbl .col-act .btn { padding: 3px 7px !important; font-size: 12px; }

  /* 产品表：13 列在 390px 上每列只剩 20~50px，等于什么都看不见。
     手机上只留 4 列 —— 名称（编码 · 价格压成副行）/ 品类 / 当前库存 / 操作。
     ★ 这里不能改 colgroup 的百分比：col 是按「实际渲染出来的单元格」顺次套的，
       .hide-sm 把 td 藏了以后，col 序列就跟 DOM 位置错位了（实测名称列套到 0px、
       品类列套到名称的 42%）。所以手机上直接把 colgroup 关掉，改由第一行 th 定宽。 */
  .prod-tbl colgroup { display: none; }
  .prod-tbl th:nth-child(2)  { width: 38%; }
  .prod-tbl th:nth-child(4)  { width: 16%; }
  .prod-tbl th:nth-child(10) { width: 14%; }
  .prod-tbl th:nth-child(13) { width: 32%; }
  .prod-tbl th, .prod-tbl td { padding: 8px 5px; font-size: 12.5px; }
  .prod-tbl td.cell-name { line-height: 1.4; }
  .prod-tbl td.cell-name .only-sm {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .prod-tbl td.cell-ops { text-align: right; }
  .prod-tbl td.cell-ops .btn { padding: 2px 3px; font-size: 10.5px; margin-left: 1px; }
}

/* ============================================================================
   项目列表 · 手机档（≤600px）：5 列用 px 定宽，只让名称列吃剩下的
   ----------------------------------------------------------------------------
   百分比在这一档不能用：表格只有 316px，阶段列分到 19% ≈ 60px，
   「预埋验收 / 0/218」那行会被 td 的 overflow:hidden 直接吃掉。
   而且这种裁剪**逐格溢出检测会漏掉**（副行带 .only-sm、被当成「故意省略」跳过），
   只能靠实测宽度发现 —— 所以这里写死的数字是量出来的，不要凭感觉改：
     360px 屏：表格 316 = 星 20 + 名称 124 + 阶段 76 + 停留 44 + 打开 52
   ⚠️ 必须 !important：桌面那组列宽在文件后面（约 780 行），同优先级（0,2,1）后出现
   的赢；不加 !important 这段全是死代码（上面那条 .col-stage{width:92px} 就是死了一年的例子）。
   ============================================================================ */
@media (max-width: 600px) {
  .list-tbl th:nth-child(2)  { width: 20px !important; }
  .list-tbl th:nth-child(3)  { width: auto !important; }
  .list-tbl th:nth-child(6)  { width: 76px !important; }
  .list-tbl th:nth-child(11) { width: 44px !important; }
  .list-tbl th:nth-child(12) { width: 52px !important; }
  .list-tbl .col-act .btn { padding: 3px 5px !important; font-size: 12px; }
}

/* 门店库存表（11 列）和流水台账（10 列）在 1440 下也会被内容撑开
   （实测 1571 / 1405px），同样用 fixed + 百分比定死。 */
.stk-tbl td { overflow: hidden; text-overflow: ellipsis; }
.stk-tbl th { white-space: normal; line-height: 1.3; }
.stk-tbl td.cell-name { white-space: normal; word-break: normal; overflow-wrap: break-word; }
.tbl-stock { table-layout: fixed; }
.tbl-stock th:nth-child(1)  { width: 8%; }    /* 编码 */
.tbl-stock th:nth-child(2)  { width: 26%; }   /* 产品名称 */
.tbl-stock th:nth-child(3)  { width: 7%; }    /* 品类 */
.tbl-stock th:nth-child(4)  { width: 7%; }    /* 品牌 */
.tbl-stock th:nth-child(5)  { width: 8%; }    /* 分类 */
.tbl-stock th:nth-child(6)  { width: 6%; }    /* 初始库存 */
.tbl-stock th:nth-child(7)  { width: 6%; }    /* 累计入库 */
.tbl-stock th:nth-child(8)  { width: 6%; }    /* 累计领出 */
.tbl-stock th:nth-child(9)  { width: 8%; }    /* 当前库存 */
.tbl-stock th:nth-child(10) { width: 8%; }    /* 最近流水 */
.tbl-stock th:nth-child(11) { width: 10%; }   /* 历史 */
.tbl-flow { table-layout: fixed; }
.tbl-flow th:nth-child(1)  { width: 8%; }     /* 日期 */
.tbl-flow th:nth-child(2)  { width: 15%; }    /* 项目 */
.tbl-flow th:nth-child(3)  { width: 22%; }    /* 产品 */
.tbl-flow th:nth-child(4)  { width: 5%; }     /* 数量 */
.tbl-flow th:nth-child(5)  { width: 8%; }     /* 动作 */
.tbl-flow th:nth-child(6)  { width: 8%; }     /* 来源 */
.tbl-flow th:nth-child(7)  { width: 9%; }     /* 位置 */
.tbl-flow th:nth-child(8)  { width: 7%; }     /* 领用人 */
.tbl-flow th:nth-child(9)  { width: 15%; }    /* 备注 */
.tbl-flow th:nth-child(10) { width: 3%; }     /* 删 */

/* 库存类表格行尾的操作按钮：原来每个按钮都带一段行内 padding，手机上盖不住、
   内联优先级高，把「修正数量」裁成「修正」。统一收到这里来管。 */
.stk-tbl td.cell-ops { white-space: nowrap; }
.stk-tbl td.cell-ops .btn { padding: 2px 9px; font-size: 12px; }
.stk-tbl td.cell-ops .btn + .btn { margin-left: 3px; }

/* ========== 库存 / 流水 / 师傅表：窄屏只留主列（2026-10-20） ==========
   产品库这四张表桌面 11/6/10/8 列，手机上原来整张表宽 550~880px，
   得左右拖着看一行，等于没法用。窄屏一律「藏次要列 + 名称下面补一行小字」，
   剩下的列用 table-layout:fixed 定死百分比，永远等于屏幕宽。
   nth-child 按 DOM 位置算（含被藏掉的列），所以要对着原始列序数。 */
@media (max-width: 1024px) {
  .stk-tbl { table-layout: fixed; }
  .stk-tbl th, .stk-tbl td { padding: 8px 6px; font-size: 12.5px; }
  .stk-tbl th { white-space: normal; line-height: 1.3; }
  .stk-tbl td.cell-name { white-space: normal; word-break: normal; overflow-wrap: break-word; line-height: 1.4; }
  .stk-tbl td.cell-name .only-sm {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .stk-tbl td.cell-ops { white-space: nowrap; text-align: right; }
  .stk-tbl td.cell-ops .btn { padding: 3px 5px; font-size: 11px; margin-left: 2px; }
  /* 门店库存：名称 / 当前库存 / 历史 */
  .tbl-stock   th:nth-child(2)  { width: 58%; }
  .tbl-stock   th:nth-child(9)  { width: 20%; }
  .tbl-stock   th:nth-child(11) { width: 22%; }
  /* 暂存箱在途：名称 / 在途 / 积压 / 整理+修正 */
  .tbl-transit th:nth-child(2)  { width: 40%; }
  .tbl-transit th:nth-child(3)  { width: 16%; }
  .tbl-transit th:nth-child(5)  { width: 16%; }
  .tbl-transit th:nth-child(6)  { width: 28%; }
  /* 流水台账：产品（带日期·项目·动作副行）/ 数量 / 删 */
  .tbl-flow    th:nth-child(3)  { width: 62%; }
  .tbl-flow    th:nth-child(4)  { width: 18%; }
  .tbl-flow    th:nth-child(10) { width: 20%; }
  /* 师傅：姓名（带属性副行）/ 累计工时 / 派工次数 / 改名+删除 */
  .tbl-worker  th:nth-child(1)  { width: 40%; }
  .tbl-worker  th:nth-child(6)  { width: 16%; }
  .tbl-worker  th:nth-child(7)  { width: 16%; }
  .tbl-worker  th:nth-child(8)  { width: 28%; }
}

/* 项目列表（11 列）：768 / 1024 下原来溢出 378 / 122px —— 平板和笔记本上
   光看项目列表就要横向拖，这是最不该发生的一张表。 */
.list-tbl { table-layout: fixed; }
/* 12 列（2026-10-20 加「收藏」星标列，插在编号和项目名称中间）。
   ⚠️ 这组 nth-child 是**按位置**写的，前面插一列，后面所有序号都得往后挪一位。
   改完必须跑 tools/test_table_structure.py（它逐列对照 thead/tbody）+ 溢出实测。 */
.list-tbl th:nth-child(1)  { width: 7%; }    /* 编号 */
.list-tbl th:nth-child(2)  { width: 3%; }    /* 收藏 ★ */
.list-tbl th:nth-child(3)  { width: 20%; }   /* 项目名称 */
.list-tbl th:nth-child(4)  { width: 11%; }   /* 小区 */
.list-tbl th:nth-child(5)  { width: 8%; }    /* 房号 */
.list-tbl th:nth-child(6)  { width: 8%; }    /* 阶段 */
.list-tbl th:nth-child(7)  { width: 13%; }   /* 安装进度 */
.list-tbl th:nth-child(8)  { width: 5%; }    /* 方案项数 */
.list-tbl th:nth-child(9)  { width: 5%; }    /* 应装总数 */
.list-tbl th:nth-child(10) { width: 9%; }    /* 最近动静 */
.list-tbl th:nth-child(11) { width: 5%; }    /* 停留 */
.list-tbl th:nth-child(12) { width: 6%; }    /* 打开 */
/* 收藏星标：没收藏是空心灰、收藏后实心金。用按钮不用 span，
   键盘也能 Tab 到、回车能切（师傅用平板时手粗，点击区要够大）。 */
.list-tbl .col-star { text-align: center; }
.star-btn { background: none; border: 0; padding: 2px 4px; cursor: pointer;
  font-size: 17px; line-height: 1; color: var(--text-3); border-radius: 4px; }
.star-btn:hover { color: var(--gold); background: var(--surface-2); }
.star-btn.on { color: var(--gold); }
.star-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.list-tbl td { overflow: hidden; text-overflow: ellipsis; }
.list-tbl td.col-name { white-space: normal; }

/* ========== 项目详情页四张明细表（2026-10-20） ==========
   领料/上门 9 列、出入库流水 7 列、方案变更 8 列、附件 6 列、方案版本 5 列 ——
   一直是 table-layout:auto，手机上整页溢出 564px，得左右拖才看得完一行。
   这里同样 fixed + 百分比，窄屏再「藏次要列 + 主列补小字」。 */
.sub-tbl { table-layout: fixed; }
.sub-tbl th { white-space: normal; line-height: 1.3; }
.sub-tbl td { overflow: hidden; text-overflow: ellipsis; }
.sub-tbl td.cell-name { white-space: normal; word-break: normal; overflow-wrap: break-word; }
/* 单元格里的说明性小字（「明细来自：…」「凭证：…」）必须能换行，
   否则 td 的 nowrap 会把它顶出格子再被 overflow:hidden 裁掉 */
.sub-tbl .hint, .sub-tbl td > div { white-space: normal; overflow-wrap: break-word; }
.sub-tbl td select { max-width: 100%; box-sizing: border-box; font-size: 12px; }
.sub-tbl td.cell-ops { white-space: nowrap; text-align: right; }
.sub-tbl td.cell-ops .btn { margin-left: 3px; }
/* 领料/上门：日期 / 类型 / 师傅 / 领用人 / 带去 / 实装 / 退回 / 算上工 / 状态 */
.tbl-constr th:nth-child(1) { width: 12%; }
.tbl-constr th:nth-child(2) { width: 9%; }
.tbl-constr th:nth-child(3) { width: 15%; }
.tbl-constr th:nth-child(4) { width: 11%; }
.tbl-constr th:nth-child(5) { width: 7%; }
.tbl-constr th:nth-child(6) { width: 7%; }
.tbl-constr th:nth-child(7) { width: 7%; }
.tbl-constr th:nth-child(8) { width: 9%; }
.tbl-constr th:nth-child(9) { width: 23%; }
/* 出入库流水：日期 / 动作 / 产品 / 数量 / 领用人 / 来源 / 备注 */
.tbl-constr-flow th:nth-child(1) { width: 12%; }
.tbl-constr-flow th:nth-child(2) { width: 8%; }
.tbl-constr-flow th:nth-child(3) { width: 30%; }
.tbl-constr-flow th:nth-child(4) { width: 7%; }
.tbl-constr-flow th:nth-child(5) { width: 10%; }
.tbl-constr-flow th:nth-child(6) { width: 10%; }
.tbl-constr-flow th:nth-child(7) { width: 23%; }
/* 现场加装待核实：产品 / 方案 / 实装 / 加装 / 处理 */
.tbl-chg-pend th:nth-child(1) { width: 44%; }
.tbl-chg-pend th:nth-child(2) { width: 10%; }
.tbl-chg-pend th:nth-child(3) { width: 10%; }
.tbl-chg-pend th:nth-child(4) { width: 12%; }
.tbl-chg-pend th:nth-child(5) { width: 24%; }
/* 方案变更：时间 / 产品 / 变更前 / 变更后 / 变化 / 原因 / 来源 / 状态 */
.tbl-chg th:nth-child(1) { width: 13%; }
.tbl-chg th:nth-child(2) { width: 27%; }
.tbl-chg th:nth-child(3) { width: 7%; }
.tbl-chg th:nth-child(4) { width: 7%; }
.tbl-chg th:nth-child(5) { width: 7%; }
.tbl-chg th:nth-child(6) { width: 22%; }
.tbl-chg th:nth-child(7) { width: 9%; }
.tbl-chg th:nth-child(8) { width: 8%; }
/* 附件：预览 / 类型 / 文件名 / 角色 / 上传时间 / 操作 */
.tbl-att th:nth-child(1) { width: 10%; }
.tbl-att th:nth-child(2) { width: 8%; }
.tbl-att th:nth-child(3) { width: 34%; }
.tbl-att th:nth-child(4) { width: 12%; }
.tbl-att th:nth-child(5) { width: 16%; }
.tbl-att th:nth-child(6) { width: 20%; }
/* 方案版本：版次 / 版本名 / 抓取时间 / 内容 / 操作 */
.tbl-ver th:nth-child(1) { width: 5%; }
.tbl-ver th:nth-child(2) { width: 30%; }
.tbl-ver th:nth-child(3) { width: 13%; }
.tbl-ver th:nth-child(4) { width: 32%; }
.tbl-ver th:nth-child(5) { width: 20%; }

/* 窄屏：明细表只留 3~5 列，其余并进主列下面那行小字 */
@media (max-width: 1024px) {
  .sub-tbl { table-layout: fixed; width: 100%; }
  .sub-tbl th, .sub-tbl td { padding: 8px 5px; font-size: 12.5px; }
  /* 表头字号略收一点：「变更前」「带去」三四个字在 12% 的窄列里会被拆成两行 */
  .sub-tbl th { white-space: normal; line-height: 1.25; font-size: 11.5px; }
  .sub-tbl td.cell-name { white-space: normal; word-break: normal;
    overflow-wrap: break-word; line-height: 1.4; }
  /* ★ 副行必须能换行：这里跟列表页不一样 —— 明细表的副行装着时间/来源/原因，
     截断等于把「谁在什么时候为什么改的」吃掉，宁可让这一行变高 */
  .sub-tbl td.cell-name .only-sm {
    white-space: normal; overflow-wrap: break-word; font-size: 11.5px;
  }
  .sub-tbl td.cell-ops { white-space: normal; text-align: right; line-height: 1.9; }
  .sub-tbl td.cell-ops .btn { padding: 3px 6px; font-size: 11px; margin-left: 2px; }
  .sub-tbl td select { font-size: 11.5px; padding: 2px 4px; }
  /* 领料/上门：日期（带副行）/ 带去 / 实装 / 退回 / 状态 */
  .tbl-constr th:nth-child(1) { width: 40%; }
  .tbl-constr th:nth-child(5) { width: 11%; }
  .tbl-constr th:nth-child(6) { width: 12%; }
  .tbl-constr th:nth-child(7) { width: 11%; }
  .tbl-constr th:nth-child(9) { width: 26%; }
  /* 出入库流水：日期 / 产品（带副行）/ 数量 */
  .tbl-constr-flow th:nth-child(1) { width: 24%; }
  .tbl-constr-flow th:nth-child(3) { width: 56%; }
  .tbl-constr-flow th:nth-child(4) { width: 20%; }
  /* 待核实：产品 / 方案 / 实装 / 加装 / 处理 */
  .tbl-chg-pend th:nth-child(1) { width: 34%; }
  .tbl-chg-pend th:nth-child(2) { width: 9%; }
  .tbl-chg-pend th:nth-child(3) { width: 9%; }
  .tbl-chg-pend th:nth-child(4) { width: 13%; }
  .tbl-chg-pend th:nth-child(5) { width: 35%; }
  /* 方案变更：产品（带副行）/ 变更前 / 变更后 / 变化 / 状态 */
  .tbl-chg th:nth-child(2) { width: 32%; }
  .tbl-chg th:nth-child(3) { width: 14%; }
  .tbl-chg th:nth-child(4) { width: 14%; }
  .tbl-chg th:nth-child(5) { width: 13%; }
  .tbl-chg th:nth-child(8) { width: 27%; }
  /* 附件：文件名（带副行）/ 角色 / 下载+删除 */
  .tbl-att th:nth-child(3) { width: 40%; }
  .tbl-att th:nth-child(4) { width: 24%; }
  .tbl-att th:nth-child(6) { width: 36%; }
  /* 方案版本：版本名（带副行）/ 内容 / 看内容+设为合同版+删除 */
  .tbl-ver th:nth-child(2) { width: 34%; }
  .tbl-ver th:nth-child(4) { width: 30%; }
  .tbl-ver th:nth-child(5) { width: 36%; }
  /* 「29 种 / 141 件」宁可折成两行，也别截成「29 种 / 14…」 */
  .tbl-ver td { white-space: normal; }
}

/* ========== 表格定宽：桌面（>720）这一套（2026-10-20） ==========
   原来只在手机断点里定宽，结果 768 / 1024 / 1280 这三档全是自动布局：
   看板在 768 下溢出 584px、项目页溢出 378px、账户 154px —— 笔记本和平板才是
   最常对着的宽度，反而漏了。这里给每张宽表在桌面上也把列宽钉死，
   手机断点里的规则写在后面，优先级更高，会覆盖掉这一层。 */
.dash-tbl, .recent-tbl, .acc-tbl, .disp-tbl, .cfg-tbl, .matrix { table-layout: fixed; }
/* 看板：项目 / 阶段 / 进度 / 已装 / 在途 / 开工 / 计划交付 / 最近动静 / 提示 / 打开 */
.dash-tbl th:nth-child(1)  { width: 20%; }
.dash-tbl th:nth-child(2)  { width: 7%; }
.dash-tbl th:nth-child(3)  { width: 16%; }
.dash-tbl th:nth-child(4)  { width: 6%; }
.dash-tbl th:nth-child(5)  { width: 6%; }
.dash-tbl th:nth-child(6)  { width: 9%; }
.dash-tbl th:nth-child(7)  { width: 9%; }
.dash-tbl th:nth-child(8)  { width: 12%; }
.dash-tbl th:nth-child(9)  { width: 10%; }
.dash-tbl th:nth-child(10) { width: 5%; }
.dash-tbl td { overflow: hidden; text-overflow: ellipsis; }
.dash-tbl td.cell-name, .dash-tbl td.cell-alerts { white-space: normal; }
/* 最近流水：日期 / 项目 / 动作 / 产品 / 数量 */
.recent-tbl th:nth-child(1) { width: 11%; }
.recent-tbl th:nth-child(2) { width: 22%; }
.recent-tbl th:nth-child(3) { width: 11%; }
.recent-tbl th:nth-child(4) { width: 46%; }
.recent-tbl th:nth-child(5) { width: 10%; }
/* 账户：手机号 / 姓名 / 绑定师傅 / 角色 / 状态 / 权限 / 登录后 / 最后登录 / 操作 */
.acc-tbl th:nth-child(1) { width: 11%; }
.acc-tbl th:nth-child(2) { width: 7%; }
.acc-tbl th:nth-child(3) { width: 8%; }
.acc-tbl th:nth-child(4) { width: 9%; }
.acc-tbl th:nth-child(5) { width: 6%; }
.acc-tbl th:nth-child(6) { width: 20%; }
.acc-tbl th:nth-child(7) { width: 7%; }
.acc-tbl th:nth-child(8) { width: 13%; }
.acc-tbl th:nth-child(9) { width: 19%; }
/* 定位后两列必须能折行：全局 td 是 nowrap，「权限 重置密码 停用 删除」四个链接
   横排要 210px，定宽后放不下就被裁（2026-10-20 老板报「操作按钮被遮挡」）。 */
.acc-tbl td:nth-child(8) { white-space: normal; }
.acc-tbl td.ops { white-space: normal; text-align: right; }
/* 派工：勾选 / 日期 / 项目 / 上门 / 师傅 / 品种 / 带去 / 实装 / 工时 / 来源 / 状态 / 操作 */
.disp-tbl th:nth-child(1)  { width: 3%; }
.disp-tbl th:nth-child(2)  { width: 8%; }
.disp-tbl th:nth-child(3)  { width: 15%; }
.disp-tbl th:nth-child(4)  { width: 6%; }
.disp-tbl th:nth-child(5)  { width: 8%; }
.disp-tbl th:nth-child(6)  { width: 7%; }
.disp-tbl th:nth-child(7)  { width: 5%; }
.disp-tbl th:nth-child(8)  { width: 5%; }
.disp-tbl th:nth-child(9)  { width: 5%; }
.disp-tbl th:nth-child(10) { width: 9%; }
.disp-tbl th:nth-child(11) { width: 7%; }
.disp-tbl th:nth-child(12) { width: 22%; }
.disp-tbl td.cell-name { white-space: normal; }
/* 操作列三个按钮（收工登记/修改/取消）横着排要 200px 出头，定宽后放不下时
   必须允许换行 —— 全局 td 是 nowrap，不覆盖就会被裁掉（2026-10-20 老板报「被遮挡」）。 */
.disp-tbl td.cell-ops { white-space: normal; text-align: right; line-height: 1.9; }
.disp-tbl td.cell-ops .btn { margin-left: 3px; }
/* 设置页配置表：最后一列是「去掉」，其余按内容分 */
.cfg-tbl td input, .cfg-tbl td select, .cfg-tbl td textarea {
  max-width: 100%; box-sizing: border-box; }
.matrix th:first-child, .matrix td:first-child { width: 32%; }

/* ========== 看板 / 账户 / 最近流水：窄屏只留主列（2026-10-20） ==========
   这三张表手机上是全站最宽的几张（看板 10 列 1103px、账户 9 列 883px）。
   同样是「藏次要列 + 主列下面补小字」，剩下的列定死百分比。 */
@media (max-width: 1024px) {
  .dash-tbl, .recent-tbl, .acc-tbl { table-layout: fixed; width: 100%; }
  .dash-tbl th, .dash-tbl td,
  .recent-tbl th, .recent-tbl td,
  .acc-tbl th, .acc-tbl td { padding: 8px 6px; font-size: 12.5px; }
  .dash-tbl th, .recent-tbl th, .acc-tbl th { white-space: normal; line-height: 1.3; }
  .dash-tbl td.cell-name, .recent-tbl td.cell-name, .acc-tbl td.cell-name {
    white-space: normal; word-break: normal; overflow-wrap: break-word; line-height: 1.4; }
  .dash-tbl td.cell-name .prog-s { font-size: 11px; margin-top: 2px; }
  .dash-tbl td.cell-alerts { white-space: normal; }
  .dash-tbl td.cell-alerts .tag { font-size: 11px; padding: 1px 5px; }
  .dash-tbl td.cell-ops, .acc-tbl td.ops { text-align: right; }
  .dash-tbl td.cell-ops .btn { padding: 4px 8px; font-size: 12px; }
  /* 账户：操作列有四五个链接，手机上让它换行，别挤成一长条顶出格子 */
  .acc-tbl td.ops { white-space: normal; line-height: 1.9; }
  .acc-tbl td.ops a { margin-right: 8px; }
  .acc-tbl td.cell-perm { white-space: normal; font-size: 11.5px; line-height: 1.45; }
  /* 列宽（nth-child 按 DOM 位置，含被藏掉的列） */
  .dash-tbl th:nth-child(1)  { width: 38%; }   /* 项目 */
  .dash-tbl th:nth-child(3)  { width: 28%; }   /* 进度 */
  .dash-tbl th:nth-child(9)  { width: 20%; }   /* 提示 */
  .dash-tbl th:nth-child(10) { width: 14%; }   /* 打开 */
  .recent-tbl th:nth-child(4) { width: 80%; }  /* 产品 */
  .recent-tbl th:nth-child(5) { width: 20%; }  /* 数量 */
  .acc-tbl th:nth-child(2)  { width: 26%; }    /* 姓名 */
  .acc-tbl th:nth-child(6)  { width: 34%; }    /* 权限 */
  .acc-tbl th:nth-child(9)  { width: 40%; }    /* 操作 */
}

/* ========== 派工列表：窄屏只留「项目 / 带去 · 实装 / 状态 / 操作」（2026-10-20） ==========
   12 列在 390px 下要拖三屏才看完一行。日期·上门·师傅·品种·工时·来源压成项目下面两行小字，
   带去/实装/状态/操作留着 —— 派工单在手机上是师傅当天要照着干活的。 */
@media (max-width: 1024px) {
  .disp-tbl { table-layout: fixed; }
  .disp-tbl th, .disp-tbl td { padding: 8px 4px; font-size: 12.5px; }
  .disp-tbl th { white-space: normal; line-height: 1.3; }
  .disp-tbl td.cell-name { white-space: normal; word-break: normal; overflow-wrap: break-word; line-height: 1.4; }
  /* 副行允许折到第二行：这里装着「带X · 装X」，手机上 44% 只有 150px，
     一行放不下就省略，等于把这两个数白白藏掉。数字之间有空格分隔，折行不会劈开。 */
  .disp-tbl td.cell-name .only-sm {
    white-space: normal; overflow: visible; text-overflow: clip;
    font-size: 11.5px; line-height: 1.35;
  }
  .disp-tbl td.cell-ops { white-space: normal; text-align: right; line-height: 2; }
  .disp-tbl td.cell-ops .btn { padding: 3px 7px; font-size: 11.5px; }
  /* 手机上可见 4 列（勾选 / 项目+副行 / 状态 / 操作）。
     带去·实装·品种都并进副行 —— 留着 5 列时项目名只剩 118px，折成三行，
     状态标签还被裁成「待」（2026-10-20）。 */
  .disp-tbl th:nth-child(1)  { width: 7%; }    /* 勾选 */
  .disp-tbl th:nth-child(3)  { width: 44%; }   /* 项目（带副行） */
  .disp-tbl th:nth-child(11) { width: 19%; }   /* 状态 */
  .disp-tbl th:nth-child(12) { width: 30%; }   /* 操作 */
}

/* ========== 设置页 / 流水月度报表：窄屏按格子宽度排（2026-10-20） ==========
   设置页那几张是「输入框 + 去掉」的配置表，宽度全被输入框自己撑出来（最宽 804px）。
   窄屏一律 table-layout:fixed + 控件 100% 宽，输入框就跟着格子走，不再顶出屏幕。 */
@media (max-width: 1024px) {
  .cfg-tbl { table-layout: fixed; }
  .cfg-tbl th, .cfg-tbl td { padding: 7px 5px; font-size: 12px; }
  .cfg-tbl th { white-space: normal; line-height: 1.3; font-size: 11.5px; }
  .cfg-tbl td input, .cfg-tbl td select, .cfg-tbl td textarea {
    width: 100% !important; max-width: 100%; box-sizing: border-box; }
  .cfg-tbl td .btn { padding: 2px 5px; font-size: 11px; }
  .cfg-tbl th:last-child, .cfg-tbl td:last-child { width: 15%; text-align: right; }

  /* 流水月度报表（动作 × 笔数/数量）：数字都很短，定宽后刚好一屏。
     首列收到 26% —— 32% 时数值列只剩 46px，「2,660.5」被裁 3px（360px 宽）。 */
  .matrix { table-layout: fixed; }
  .matrix th, .matrix td { padding: 7px 5px; font-size: 12px; }
  .matrix th { white-space: normal; line-height: 1.3; font-size: 11.5px; }
  .matrix th:first-child, .matrix td:first-child { width: 26%; }
  /* 行标签是产品名（「YLP-E系列-低压灯带-裸板-宽度8mm-10…」），给宽度也不够，
     允许折行 —— 省略号在这里等于看不出是哪个产品。 */
  .matrix td:first-child { white-space: normal; word-break: normal; overflow-wrap: break-word; }
}

/* ========== 项目动态 / 时间线（2026-10-20） ==========
   详情页追踪卡下面那条：左边时间、中间一条竖线串圆点、右边事件 + 类型标签，
   按天分组。圆点和标签的颜色按类型分（阶段蓝 / 派工绿 / 方案橙 / 售后红 / 版本紫），
   扫一眼就知道这段时间在干什么。 */
.tl-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 10px; }
.tl-chip { padding: 4px 10px; border: 1px solid var(--line-in); border-radius: 20px;
  background: var(--surface); color: var(--text-2); font-size: 12px; cursor: pointer;
  font-family: inherit; line-height: 1.5; }
.tl-chip:hover { border-color: var(--brand); }
.tl-chip.on { background: var(--brand-soft); border-color: var(--brand);
  color: var(--brand-fg); font-weight: 500; }
.tl-chip b { margin-left: 4px; font-weight: 600; opacity: .7; }

.tl-day { padding: 12px 0 2px; font-size: 12px; font-weight: 600; color: var(--text-2); }
.tl-day:first-child { padding-top: 2px; }
.tl-day span { background: var(--gray-bg); padding: 2px 9px; border-radius: 10px; }
.tl-daybody { position: relative; }
/* 竖线：时间列 46 + 圆点列 16 的一半 = 54 */
.tl-daybody::before { content: ''; position: absolute; left: 54px; top: 2px; bottom: 8px;
  width: 1px; background: var(--line); }
.tl-row { display: flex; align-items: flex-start; padding: 7px 0; flex-wrap: wrap; }
.tl-at { flex: 0 0 46px; font-size: 12px; color: var(--text-3); text-align: right;
  padding-top: 2px; font-variant-numeric: tabular-nums; }
.tl-dot { flex: 0 0 16px; display: flex; justify-content: center; padding-top: 6px; }
.tl-dot::before { content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--text-3); box-shadow: 0 0 0 3px var(--surface); }
/* flex:1 会让标签被顶到卡片最右边 —— 1364px 宽的时候标签离正文一千多像素，
   看着像另起一列。改成按内容宽度，标签紧跟标题后面；窄屏放不下就换行。 */
.tl-body { flex: 0 1 auto; min-width: 0; padding-left: 8px; max-width: calc(100% - 62px); }
.tl-title { font-size: 13px; color: var(--text); line-height: 1.45;
  word-break: normal; overflow-wrap: break-word; }
.tl-desc { font-size: 12px; color: var(--text-2); margin-top: 2px; line-height: 1.45;
  word-break: normal; overflow-wrap: break-word; }
.tl-tag { flex: 0 0 auto; font-size: 11px; padding: 1px 8px; border-radius: 10px;
  background: var(--gray-bg); color: var(--gray-fg); margin-left: 8px; margin-top: 1px; }
.tl-more { margin-top: 10px; text-align: center; }
/* 按类型上色：圆点 + 标签 */
.tl-row.k-stage .tl-dot::before { background: var(--brand); }
.tl-row.k-stage .tl-tag { background: var(--brand-soft); color: var(--brand-fg); }
.tl-row.k-dispatch .tl-dot::before { background: var(--ok-fg); }
.tl-row.k-dispatch .tl-tag { background: var(--ok-bg); color: var(--ok-fg); }
.tl-row.k-plan .tl-dot::before { background: var(--mid-fg); }
.tl-row.k-plan .tl-tag { background: var(--mid-bg); color: var(--mid-fg); }
.tl-row.k-after_sale .tl-dot::before { background: var(--warn-fg); }
.tl-row.k-after_sale .tl-tag { background: var(--warn-bg); color: var(--warn-fg); }
.tl-row.k-version .tl-dot::before { background: var(--info-fg); }
.tl-row.k-version .tl-tag { background: var(--info-bg); color: var(--info-fg); }
.tl-row.k-flow .tl-dot::before { background: var(--line-in); }

@media (max-width: 720px) {
  .tl-at { flex: 0 0 36px; font-size: 11px; }
  .tl-dot { flex: 0 0 14px; }
  .tl-daybody::before { left: 43px; }
  .tl-daybody { padding-left: 0; }
  .tl-body { padding-left: 6px; }
  .tl-title { font-size: 12.5px; }
  /* 手机上标题一长，标签就被挤到下一行；靠右对齐，跟没换行的那几行看着是一列 */
  .tl-tag { font-size: 10px; padding: 1px 6px; margin-left: auto; }
  .tl-chips { gap: 5px; }
  .tl-chip { padding: 3px 8px; font-size: 11.5px; }
}
/* 动态卡不折叠，标题行不该出现「可点」的手型 */
#tlCard .pg-head { cursor: default; }
#tlCard .pg-head:hover h3 { color: inherit; }
