/* ============================================================
   设计系统 - 所有组件类名以 ds- 为前缀
   新代码必须用设计系统类名，禁止写 inline style
   如需新增组件类，统一加到本文件，不在业务代码中写内联样式
   ------------------------------------------------------------
   v2 重设计：保留品牌色（海军蓝 #1B2A4A + 品牌红 #E63946），
   升级中性色 / 阴影 / 层级，新增 KPI 指标卡、状态徽章、深色模式。
   类名与旧版完全兼容，业务 JS 无需改动。
   ============================================================ */

/* ---------- 设计令牌（浅色，默认）---------- */
:root {
  /* 品牌 */
  --primary: #1B2A4A;          /* 海军蓝 · 品牌主色（也用作 dashboard 数字文字色）*/
  --primary-600: #24365c;
  --on-primary: #ffffff;       /* 覆盖在 --primary 实心块上的文字色 */
  --accent: #E63946;           /* 品牌红 · 强调/激活/危险操作 */
  --accent-600: #d02c39;
  --on-accent: #ffffff;
  /* 导航底色（与 --primary 分离，保证深色模式下数字文字色可读）*/
  --nav-bg: #24365c;
  --nav-bg-2: #1B2A4A;

  /* 语义色（与品牌红区分开）*/
  --success: #1f8a4c; --success-bg: #e7f5ec;
  --warning: #b7791f; --warning-bg: #fdf3e2;
  --danger:  #c0392b; --danger-bg:  #fce9e7;
  --info:    #2563a8; --info-bg:    #e8f1fb;

  /* 中性色：带一点海军蓝倾向，不是死灰 */
  --bg:       #f3f5fa;
  --card-bg:  #ffffff;
  --text:     #1c2333;
  --text-2:   #3c455c;
  --muted:    #6b7488;
  --border:   #e5e9f1;
  --border-2: #eef1f6;
  --hover:    #f5f7fb;

  /* 形状 & 阴影 */
  --radius: 10px; --radius-sm: 7px; --radius-lg: 14px;
  --shadow-sm: 0 1px 2px rgba(20,30,60,.06), 0 1px 3px rgba(20,30,60,.04);
  --shadow-md: 0 4px 12px rgba(20,30,60,.08), 0 2px 4px rgba(20,30,60,.04);
  --shadow-nav: 0 2px 10px rgba(10,18,40,.18);
  --ring: 0 0 0 3px rgba(230,57,70,.22);
}

/* ---------- 深色模式（跟随系统）---------- */
@media (prefers-color-scheme: dark) {
  :root {
    --primary: #6f9bd9; --primary-600: #5b88c9; --on-primary: #0e1420;
    --accent: #ef4b57; --accent-600: #ff6670; --on-accent: #ffffff;
    --nav-bg: #1a2942; --nav-bg-2: #0f1b30;
    --success: #4ecb7d; --success-bg: #16281f;
    --warning: #e0a54a; --warning-bg: #2a2013;
    --danger:  #f26a5c; --danger-bg:  #2c1815;
    --info:    #63a4e8; --info-bg:    #14243a;
    --bg:       #0e1420; --card-bg: #161d2c;
    --text:     #e8ecf5; --text-2: #c3cadb; --muted: #8b95ab;
    --border:   #27314a; --border-2: #202940; --hover: #1c2536;
    --shadow-sm: 0 1px 2px rgba(0,0,0,.4);
    --shadow-md: 0 6px 18px rgba(0,0,0,.45);
    --shadow-nav: 0 2px 12px rgba(0,0,0,.5);
    --ring: 0 0 0 3px rgba(239,75,87,.3);
  }
}
/* 手动切换覆盖系统偏好（两个方向都要能生效）*/
:root[data-theme="light"] {
  --primary:#1B2A4A; --primary-600:#24365c; --on-primary:#fff; --accent:#E63946; --accent-600:#d02c39; --on-accent:#fff;
  --nav-bg:#24365c; --nav-bg-2:#1B2A4A;
  --success:#1f8a4c; --success-bg:#e7f5ec; --warning:#b7791f; --warning-bg:#fdf3e2; --danger:#c0392b; --danger-bg:#fce9e7; --info:#2563a8; --info-bg:#e8f1fb;
  --bg:#f3f5fa; --card-bg:#fff; --text:#1c2333; --text-2:#3c455c; --muted:#6b7488; --border:#e5e9f1; --border-2:#eef1f6; --hover:#f5f7fb;
  --shadow-sm:0 1px 2px rgba(20,30,60,.06),0 1px 3px rgba(20,30,60,.04); --shadow-md:0 4px 12px rgba(20,30,60,.08),0 2px 4px rgba(20,30,60,.04); --shadow-nav:0 2px 10px rgba(10,18,40,.18); --ring:0 0 0 3px rgba(230,57,70,.22);
}
:root[data-theme="dark"] {
  --primary:#6f9bd9; --primary-600:#5b88c9; --on-primary:#0e1420; --accent:#ef4b57; --accent-600:#ff6670; --on-accent:#fff;
  --nav-bg:#1a2942; --nav-bg-2:#0f1b30;
  --success:#4ecb7d; --success-bg:#16281f; --warning:#e0a54a; --warning-bg:#2a2013; --danger:#f26a5c; --danger-bg:#2c1815; --info:#63a4e8; --info-bg:#14243a;
  --bg:#0e1420; --card-bg:#161d2c; --text:#e8ecf5; --text-2:#c3cadb; --muted:#8b95ab; --border:#27314a; --border-2:#202940; --hover:#1c2536;
  --shadow-sm:0 1px 2px rgba(0,0,0,.4); --shadow-md:0 6px 18px rgba(0,0,0,.45); --shadow-nav:0 2px 12px rgba(0,0,0,.5); --ring:0 0 0 3px rgba(239,75,87,.3);
}

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

body {
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* 数字对齐（金额/数量列加上 .num）*/
.num { font-variant-numeric: tabular-nums; }

/* ---------- 导航栏 ---------- */
.ds-navbar {
  display: flex;
  align-items: center;
  gap: .25em;
  background: linear-gradient(180deg, var(--nav-bg), var(--nav-bg-2));
  color: #fff;
  padding: 0 1.5em;
  height: 60px;
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: var(--shadow-nav);
}
.ds-navbar-brand { font-size: 16px; font-weight: 700; margin-right: 1.5em; letter-spacing: .01em; white-space: nowrap; }
.ds-navbar-spacer { flex: 1; }
/* 顶栏左边那个 ☰：收起/展开左侧菜单树（宽屏记住选择，窄屏默认收起）*/
.ds-side-toggle {
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.22);
  color: #fff;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: .45em .62em;
  margin-right: .9em;
  transition: background .15s;
}
.ds-side-toggle:hover { background: rgba(255,255,255,0.18); }
.ds-side-toggle:focus-visible { outline: none; box-shadow: var(--ring); }

/* ---------- 左侧菜单树（文件夹=大模块，子项=模块下的具体功能）----------
   结构见 core.js 的 NAV_ITEMS。文件夹展开状态记在 localStorage，刷新后还在。 */
.ds-layout { display: flex; align-items: flex-start; }
.ds-sidebar {
  flex: 0 0 208px;
  width: 208px;
  position: sticky;
  top: 60px;                      /* 顶栏高度，跟 .ds-navbar 的 height 对齐 */
  max-height: calc(100vh - 60px);
  overflow-y: auto;
  overscroll-behavior: contain;    /* 菜单滚到底不要带着整页一起滚 */
  background: var(--card-bg);
  border-right: 1px solid var(--border);
  padding: .8em .55em 2em;
  font-size: 14px;
}
/* 未登录时菜单是空的，整条侧边栏不占位 */
.ds-sidebar:empty { display: none; }
body.side-off .ds-sidebar { display: none; }

.ds-side-item, .ds-side-sub, .ds-side-folder {
  display: flex;
  align-items: center;
  gap: .55em;
  width: 100%;
  padding: .5em .7em;
  margin-bottom: 1px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  font: inherit;
  font-weight: 550;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.ds-side-item:hover, .ds-side-sub:hover, .ds-side-folder:hover { background: var(--hover); color: var(--text); }
.ds-side-item:focus-visible, .ds-side-sub:focus-visible, .ds-side-folder:focus-visible { outline: none; box-shadow: var(--ring); }
/* 当前页：品牌红 + 左侧一道竖条（用 inset 阴影画，不占布局宽度）*/
.ds-side-item.active, .ds-side-sub.active {
  background: var(--hover);
  color: var(--accent);
  font-weight: 700;
  box-shadow: inset 3px 0 0 var(--accent);
}
/* 文件夹收起时，靠加深文字提示"当前在这个大模块里" */
.ds-side-group.current > .ds-side-folder { color: var(--text); }

/* 侧边栏角标：待办条数。ds-side-txt 会 ellipsis，所以角标必须 flex:0 0 auto
   顶在右边（margin-left:auto），不然长菜单名一挤它就没了 */
.ds-side-badge {
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 1.5em;
  padding: 0 .45em;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: .72em;
  font-weight: 700;
  line-height: 1.55;
  text-align: center;
}
/* 有新留言/新进度的红点。和数字角标并存：数字是「手上多少活」，
   点是「有新动静」—— 没有待办、只有未读时它自己也要能亮 */
.ds-side-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  margin-left: .4em;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 0 0 rgba(192, 57, 43, .5);
  animation: ds-side-dot-pulse 2s ease-out infinite;
}
/* 只有数字角标时点贴在它右边，没有角标时靠自己顶到右侧 */
.ds-side-badge + .ds-side-dot { margin-left: .35em; }
.ds-side-item > .ds-side-dot:not(.ds-side-badge + .ds-side-dot) { margin-left: auto; }
@keyframes ds-side-dot-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(192, 57, 43, .5); }
  70%  { box-shadow: 0 0 0 6px rgba(192, 57, 43, 0); }
  100% { box-shadow: 0 0 0 0 rgba(192, 57, 43, 0); }
}
/* 系统设了「减少动态效果」就别闪 —— 提示照旧在，只是不动 */
@media (prefers-reduced-motion: reduce) {
  .ds-side-dot { animation: none; }
}
.ds-side-ico { flex: 0 0 1.25em; text-align: center; }
.ds-side-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-side-caret { margin-left: auto; font-size: 11px; color: var(--muted); transition: transform .15s; }
.ds-side-group.open .ds-side-caret { transform: rotate(90deg); }

/* 子项：缩进 + 一条竖线，一眼看出属于上面那个文件夹 */
.ds-side-kids {
  display: none;
  margin: .1em 0 .35em 1.35em;
  padding-left: .25em;
  border-left: 1px solid var(--border);
}
.ds-side-group.open .ds-side-kids { display: block; }
.ds-side-sub { font-size: 13.5px; font-weight: 500; padding-left: .75em; }

/* 窄屏（平板/手机）：侧边栏改成盖在内容上的浮层，选完自动收起（见 core.js navigate）*/
@media (max-width: 900px) {
  .ds-sidebar {
    position: fixed;
    left: 0; top: 60px; bottom: 0;
    max-height: none;
    z-index: 95;
    box-shadow: var(--shadow-md);
  }
  .ds-main { width: 100%; }
}

.ds-navbar-user { display: flex; align-items: center; gap: .55em; font-size: 13px; color: rgba(255,255,255,0.9); }
/* 导航栏里的按钮（如“退出”）在深色底上保持可读 */
.ds-navbar .ds-btn-outline {
  color: #fff;
  border-color: rgba(255,255,255,0.28);
  background: rgba(255,255,255,0.08);
}
.ds-navbar .ds-btn-outline:hover { background: rgba(255,255,255,0.16); border-color: rgba(255,255,255,0.4); }

/* ---------- 主内容 ---------- */
.ds-main {
  flex: 1;
  min-width: 0;              /* flex 子项默认 min-width:auto，宽表格会把整页撑爆 */
  padding: 1.75em 1.5em;
  max-width: 1400px;
  margin: 0 auto;
}

/* ---------- 侧边栏接管后隐藏页内 Tab 条 ----------
   这些模块的子项已经提到左侧菜单树里（见 core.js NAV_ITEMS），
   页内那排横向 Tab 留着就成了同一组入口出现两次。
   **只隐藏、不删代码**：按钮和事件仍在 DOM 里照常工作（侧边栏就是靠它们切换的），
   想退回横向 Tab 删掉这一条规则即可，JS 一行都不用动。
   任务中心的 #tcTabs 不在名单里 —— 它的 Tab 是接口按岗位下发的，没进侧边栏。 */
#whTabs, #selTabs, #opTabs, #adTabs, #csTabs { display: none; }

/* 页头（可选：<div class="ds-page-head"><h1>标题</h1>...</div>）*/
.ds-page-head { display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 1em; margin-bottom: 1.25em; }
.ds-page-head h1 { font-size: 21px; font-weight: 700; letter-spacing: -.01em; }
.ds-page-head .sub { color: var(--muted); font-size: 13px; margin-top: .15em; }

/* ---------- 卡片 ---------- */
.ds-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.2em 1.4em;
  box-shadow: var(--shadow-sm);
  margin-bottom: 1em;
}
.ds-card-title {
  display: flex;
  align-items: center;
  gap: .5em;
  font-size: 15px;
  font-weight: 650;
  margin-bottom: 1em;
  padding-bottom: .7em;
  border-bottom: 1px solid var(--border-2);
}
/* 标题左侧红色小竖条（可选：<span class="ds-card-title-bar"></span>）*/
.ds-card-title-bar { width: 3px; height: 15px; border-radius: 3px; background: var(--accent); display: inline-block; }

/* ---------- KPI 指标卡（新增，dashboard 可选升级用）----------
   用法：
   <div class="ds-stat" style="--stat-accent:var(--success)">
     <div class="ds-stat-label">📦 最新日销量</div>
     <div class="ds-stat-value num">1,284</div>
     <div class="ds-stat-foot">
       <span class="ds-delta up">▲ 5.1%</span> ...
     </div>
   </div>
*/
.ds-stat {
  position: relative;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.15em 1.25em .9em;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: box-shadow .2s, transform .2s;
}
.ds-stat:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.ds-stat::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--stat-accent, var(--accent)); }
/* 指标卡左侧色条配色（修饰类，避免业务代码写 inline style）*/
.ds-stat--primary { --stat-accent: var(--primary); }
.ds-stat--success { --stat-accent: var(--success); }
.ds-stat--warning { --stat-accent: var(--warning); }
.ds-stat--info    { --stat-accent: var(--info); }
.ds-stat--accent  { --stat-accent: var(--accent); }
.ds-stat-label { display: flex; align-items: center; gap: .4em; font-size: 12.5px; color: var(--muted); font-weight: 500; }
.ds-stat-value { font-size: 28px; font-weight: 750; letter-spacing: -.02em; margin: .15em 0 .35em; line-height: 1.1; }
.ds-stat-foot { display: flex; align-items: center; justify-content: space-between; gap: .5em; }
.ds-delta { display: inline-flex; align-items: center; gap: .2em; font-size: 12px; font-weight: 650; padding: .1em .5em; border-radius: 20px; }
.ds-delta.up { color: var(--success); background: var(--success-bg); }
.ds-delta.down { color: var(--danger); background: var(--danger-bg); }
.ds-spark { height: 26px; width: 82px; }
/* 同一张 sparkline 里的两条线：销量跟随按钮色，BSR 排名（纵轴反转）用警示色区分 */
.ds-spark-line { stroke: currentColor; }
.ds-spark-line--rank { stroke: var(--warning); }

/* ---------- 按钮 ---------- */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4em;
  padding: .55em 1.15em;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  transition: all .16s;
}
.ds-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.ds-btn:disabled { opacity: .5; cursor: not-allowed; }
.ds-btn-primary { background: var(--primary); color: var(--on-primary); }
.ds-btn-primary:hover:not(:disabled) { background: var(--primary-600); }
.ds-btn-danger { background: var(--accent); color: var(--on-accent); box-shadow: 0 1px 2px rgba(230,57,70,.3); }
.ds-btn-danger:hover:not(:disabled) { background: var(--accent-600); }
/* 别名：主强调操作（品牌红），语义等同 danger 的红色实心 */
.ds-btn-accent { background: var(--accent); color: var(--on-accent); box-shadow: 0 1px 2px rgba(230,57,70,.3); }
.ds-btn-accent:hover:not(:disabled) { background: var(--accent-600); }
.ds-btn-outline { background: var(--card-bg); border: 1px solid var(--border); color: var(--text-2); }
.ds-btn-outline:hover:not(:disabled) { border-color: var(--muted); background: var(--hover); }
.ds-btn-sm { padding: .35em .7em; font-size: 12.5px; }

/* ---------- 表单控件 ---------- */
.ds-main input[type="text"],
.ds-main input:not([type]),
.ds-main input[type="search"],
.ds-main input[type="number"],
.ds-main input[type="password"],
.ds-main select,
.ds-input {
  height: 36px;
  padding: 0 .8em;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  color: var(--text);
  font-size: 13.5px;
  font-family: inherit;
  transition: border-color .15s, box-shadow .15s;
}
.ds-main input:focus,
.ds-main select:focus,
.ds-input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
/* 筛选栏（可选：<div class="ds-filter-bar">...</div>）*/
.ds-filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: .7em; margin-bottom: 1.1em; }
.ds-field { display: flex; flex-direction: column; gap: .3em; }
.ds-field label { font-size: 11.5px; color: var(--muted); font-weight: 600; letter-spacing: .02em; }

/* ---------- 表格 ---------- */
.ds-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
.ds-table th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--hover);
  text-align: left;
  padding: .75em .9em;
  font-weight: 650;
  font-size: 12.5px;
  color: var(--text-2);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.ds-table td {
  padding: .7em .9em;
  border-bottom: 1px solid var(--border-2);
  font-size: 13px;
}
/* table-layout:fixed 下超出格子的内容会画到隔壁列上（尤其是把列拖窄之后），
   统一单行 + 省略号截断，放不下的靠悬浮提示或把列拖宽来看 */
.ds-table th,
.ds-table td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ds-table tbody tr { transition: background .12s; }
.ds-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--hover) 45%, transparent); }
.ds-table tbody tr:hover { background: var(--hover); }

/* ---------- 拖拽把手 ---------- */
.col-resize-handle {
  position: absolute;
  right: 0;
  top: 0;
  width: 4px;
  height: 100%;
  cursor: col-resize;
  background: rgba(0,0,0,0.08);
}

/* ---------- 输入框清空按钮 ---------- */
.inp-clear-wrap { position: relative; display: inline-flex; align-items: center; }
.inp-clear-wrap input { padding-right: 1.8em; }
.inp-clear-btn {
  position: absolute;
  right: 8px;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  display: none;
}
.inp-clear-wrap input:not(:placeholder-shown) + .inp-clear-btn { display: block; }

/* ---------- 徽章 ---------- */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: .35em;
  padding: .2em .65em;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
}
.ds-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.ds-badge-success { background: var(--success-bg); color: var(--success); }
.ds-badge-warning { background: var(--warning-bg); color: var(--warning); }
.ds-badge-danger  { background: var(--danger-bg);  color: var(--danger); }
.ds-badge-info    { background: var(--info-bg);    color: var(--info); }

/* ---------- 提示 ---------- */
.ds-toast {
  position: fixed;
  top: 76px;
  right: 1.5em;
  background: var(--primary);
  color: var(--on-primary);
  padding: .8em 1.5em;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  z-index: 9999;
  animation: slideIn .3s ease;
}
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } }

/* ---------- 布局工具类 ---------- */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-sm { gap: .5em; }
.gap-md { gap: 1em; }
.gap-lg { gap: 1.5em; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.1em; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1em; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1em; }
@media (max-width: 900px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
}

/* ---------- 趋势迷你图 / 图表弹窗 ---------- */
/* 表格行内的可点击 sparkline：按钮外观全部剥掉，只留图形本身 */
.ds-spark-btn {
  display: block;
  padding: 2px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
  line-height: 0;
  color: var(--info);
}
.ds-spark-btn:hover { background: var(--hover); border-color: var(--border); }
.ds-spark-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.ds-spark-empty { color: var(--muted); font-size: 12px; }

/* 表格里的次要说明文字（如「3天前」这类相对时间），不与主值抢视线 */
.ds-cell-nowrap { white-space: nowrap; }
.ds-text-muted { color: var(--muted); font-size: .85em; }

/* 数值单元格里的滞销角标（如 FBA可用「13 滞5」）：提示这堆货里有多少是压箱底的老货。
   用文字色而非徽章底色 —— 它跟在数字后面，加底色会把整列搅乱 */
.ds-aged-mark { color: var(--warning); font-size: .85em; margin-left: .3em; white-space: nowrap; }

/* 审批单元格（补货计划「审批」列）：徽章 + 领星单号一行，按钮另起一行。
   2026-07-27 修：原来三者挤在一行且 td 是 nowrap，而列宽只有 70px，一旦行有了状态徽章
   （待审批/已驳回/已通过），按钮就被挤出单元格边界、被右边「状态/操作」列盖在上面，
   鼠标点下去打到的是别的格子 —— 表现为「提交过一次的行再也点不动」。
   position:relative + z-index：列宽可以被用户拖窄（`_colWidths` 记在内存里，刷新才复位），
   窄到连按钮本身都放不下时溢出部分照样会被右边的格子盖住；抬一层保证按钮无论如何都点得到。 */
.ds-approve-cell { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: .25em; }
/* 单号允许换到第二行：否则「已通过 PP260727008」一行要 150px，为它加宽整列不划算 */
.ds-approve-cell .ds-approve-meta { display: flex; align-items: center; gap: .3em; flex-wrap: wrap; }

/* 行内操作按钮格（如待审批的「通过/驳回」）：同上，列拖窄时溢出的按钮也要保持可点 */
.ds-cell-actions { position: relative; z-index: 1; white-space: nowrap; }

/* 补货明细 19 列。.ds-table 是 width:100%+table-layout:fixed，19 列平分下来每列只有约 70px，
   SKU/FNSKU/店铺 这些文字列一律被省略号吃掉（2026-07-27 用户反馈「产品信息没有完全显示」）。
   同报关宽表的做法：只给这张表兜一个最小宽度，容器 #rpTableWrap 自己横向滚，不动全局 .ds-table。 */
.rp-table-wide { min-width: 1944px; }   /* = 表头里写死的 19 个列宽之和，改列宽要跟着改 */

/* 表头/标签旁的说明角标：鼠标悬停出现解释（用原生 title，不引第三方 tooltip） */
.ds-help {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: 3px;
  border-radius: 50%; border: 1px solid var(--muted);
  color: var(--muted); font-size: 10px; font-weight: 700; line-height: 1;
  cursor: help; user-select: none; vertical-align: middle;
}
.ds-help:hover { color: var(--text-2); border-color: var(--text-2); }
/* 一格里叠放多行（如「上次采购单」的单号/时间/数量），行距收紧不撑高表格 */
.ds-cell-stack { display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }

/* 表格里的产品缩略图（选品中心产品池/FOB测算的主图列）。
   固定尺寸，图没加载出来/存量行没图时也不会把行高撑变形。 */
.ds-thumb {
  width: 40px; height: 40px; border-radius: 4px;
  object-fit: contain; background: var(--hover);
  border: 1px solid var(--border); vertical-align: middle;
}
.ds-thumb-empty {
  display: inline-block; width: 40px; height: 40px; border-radius: 4px;
  background: var(--hover); border: 1px dashed var(--border); vertical-align: middle;
}

/* 一格里既有缩略图又有文字（选品核验的「待核验」列：图 + 关键词是一起给的，
   两样都要显示——光看词认不出是哪个件，光看图认不出零件号）。 */
.ds-cell-imgtext {
  display: inline-flex; align-items: center; gap: .5em; min-width: 0;
}
.ds-cell-imgtext > .ds-thumb { flex: none; }

/* 表单里的必填星号 */
.ds-text-danger { color: var(--danger); }

/* 「AI 判定不同品类、按口径未做外观比对」的行/徽章：淡化但不隐藏 ——
   这些行仍要能看见（编号一致跨品类时恰恰要人工看一眼），只是不用细看。 */
.ds-badge-muted {
  background: var(--hover); color: var(--muted); border: 1px solid var(--border);
}
.ds-row-muted > td { opacity: .55; }
.ds-row-muted:hover > td { opacity: 1; }

/* 任务来源色带（2026-08-22）：首列左边一道 3px 竖条，区分「系统自动派的单」和
   「人工发起的单」—— 两者的收尾方式完全不同（人工的要发起人验收、系统的提交完成即关闭），
   在一张混排的列表里得能一眼扫出来。
   用 inset box-shadow 而不是 border-left：这些表是 table-layout:fixed + 列宽可拖，
   加 border 会占掉首列 3px 把内容顶偏；box-shadow 不参与布局。
   颜色取语义：人工=accent（有个活人在等结果），系统=info（自动化的例行单）。 */
.ds-row-human > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.ds-row-sys   > td:first-child { box-shadow: inset 3px 0 0 var(--info); }
/* 图例里的小色块，跟上面两条色带同色同宽 */
.ds-src-key { display: inline-block; width: 3px; height: .9em; vertical-align: -1px; margin-right: .3em; }
.ds-src-key-human { background: var(--accent); }
.ds-src-key-sys { background: var(--info); }

/* 盈亏数值（如 FOB）：正=可做，负=倒挂 */
.ds-num-pos { font-weight: 600; color: var(--success); }
.ds-num-neg { font-weight: 600; color: var(--danger); }

.ds-modal-overlay {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(10, 18, 40, .45);
}
.ds-modal {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: 1.2em 1.3em 1.3em;
  max-width: 94vw; max-height: 92vh; overflow: auto;
}
.ds-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1em; margin-bottom: .9em;
}
.ds-modal-title { font-size: 15px; font-weight: 650; color: var(--text); }
.ds-modal-sub { font-size: 12px; color: var(--muted); margin-top: .2em; }
.ds-modal-close {
  border: none; background: none; cursor: pointer;
  font-size: 20px; line-height: 1; color: var(--muted);
  padding: .1em .3em; border-radius: var(--radius-sm);
}
.ds-modal-close:hover { background: var(--hover); color: var(--text); }
/* Chart.js 容器必须有确定高度，否则 maintainAspectRatio:false 会塌成 0 */
.ds-chart-box { position: relative; width: 600px; max-width: 88vw; height: 320px; }

/* ---------- 子模块标签页（模块内切换视图，如选品中心的四个子模块）---------- */
.ds-tabs {
  display: flex;
  gap: .25em;
  flex-wrap: wrap;
  margin-bottom: 1.1em;
  border-bottom: 1px solid var(--border);
}
.ds-tab {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 14px;
  font-weight: 550;
  color: var(--muted);
  padding: .55em .95em;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color .12s, background .12s;
}
.ds-tab:hover { color: var(--text); background: var(--hover); }
.ds-tab:focus-visible { outline: none; box-shadow: var(--ring); }
.ds-tab.active { color: var(--accent); border-bottom-color: var(--accent); }

/* ---------- 出入库统计用到的通用件 ---------- */
/* 表格/卡片上方的口径说明。字号压小、颜色压灰，别和数据抢视线 */
.ds-hint { font-size: 12px; color: var(--muted); line-height: 1.7; margin-bottom: .8em; }
/* 栅格下方留白（替代 <div style="margin:1em 0"> 这类内联写法）*/
.ds-mb-1 { margin-bottom: 1em; }
/* 表格里当链接用的按钮：外观是链接，语义是按钮（可聚焦、可键盘触发）*/
.ds-link-btn {
  border: none; background: none; padding: 0; cursor: pointer;
  font: inherit; color: var(--info); text-decoration: underline;
  text-underline-offset: 2px;
}
.ds-link-btn:hover { color: var(--accent); }
/* 正文里的站内跳转链接（任务说明里那种 #hash 直达）。外观同 ds-link-btn，
   但它真的是个 <a>，中键新开、右键复制地址都能用 */
.ds-link { color: var(--info); text-decoration: underline; text-underline-offset: 2px; }
.ds-link:hover { color: var(--accent); }
.ds-link-btn:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-sm); }
/* 宽表横向滚动容器：表格自己撑开，页面本身不出现横向滚动条 */
.ds-scroll-x { overflow-x: auto; }

/* 表格内联编辑（超期批次的原因/预计发货时间）：保存成功/失败的一次性反馈 */
.wh-note { min-width: 118px; }
select.wh-note { min-width: 96px; }
.wh-saved { border-color: var(--success) !important; }
.wh-save-err { border-color: var(--danger) !important; }

/* 占比条（库龄分布用）。宽度是数据算出来的，由 JS 赋 style.width，不写死在类里 */
.ds-bar { background: var(--border-2); border-radius: 3px; height: 8px; width: 100%; min-width: 60px; overflow: hidden; }
.ds-bar-fill { background: var(--info); height: 100%; border-radius: 3px; }

/* ---------- 空态（无数据 / 内容待补充）---------- */
.ds-empty {
  padding: 3em 1.5em;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.8;
}
.ds-empty-title { font-size: 15px; font-weight: 650; color: var(--text-2); margin-bottom: .3em; }

/* ---------- 表格操作列（行内小按钮组）---------- */
.ds-row-actions { display: flex; gap: .35em; flex-wrap: wrap; }

/* ---------- 表单栅格（弹窗里的字段布局）---------- */
.ds-form-grid { display: grid; grid-template-columns: repeat(2, minmax(160px, 1fr)); gap: .8em 1em; }
.ds-form-grid .ds-field { display: flex; flex-direction: column; gap: .25em; }
.ds-form-grid .ds-field label { font-size: 12px; color: var(--muted); }
.ds-form-grid .span-2 { grid-column: span 2; }
.ds-form-foot { display: flex; justify-content: flex-end; gap: .6em; margin-top: 1.1em; }

/* ---------- 图片上传区（点击 / 拖拽二选一）---------- */
.ds-upload {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-height: 260px; padding: 1em;
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  background: var(--hover);
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.ds-upload:hover,
.ds-upload.dragover { border-color: var(--accent); background: var(--card-bg); }
.ds-upload-hint { color: var(--muted); font-size: 13.5px; text-align: center; line-height: 1.9; }
.ds-upload-sub { display: block; font-size: 12px; color: var(--muted); }
.ds-upload-preview { max-width: 100%; max-height: 320px; object-fit: contain; border-radius: var(--radius-sm); }

/* 结果图：透明底要看得见，所以垫棋盘格（抠图产出是透明 PNG）*/
.ds-checker {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #e8e8e8 25%, transparent 25%, transparent 75%, #e8e8e8 75%),
    linear-gradient(45deg, #e8e8e8 25%, transparent 25%, transparent 75%, #e8e8e8 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
}

/* 原图 / 结果图 左右对照 */
.ds-compare { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1em; }
.ds-compare-cell { display: flex; flex-direction: column; gap: .5em; }
.ds-compare-cell > span { font-size: 11.5px; color: var(--muted); font-weight: 600; letter-spacing: .02em; }
.ds-compare-box {
  display: flex; align-items: center; justify-content: center;
  min-height: 240px; padding: .6em;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.ds-compare-box img { max-width: 100%; max-height: 300px; object-fit: contain; }
@media (max-width: 720px) { .ds-compare { grid-template-columns: 1fr; } }

/* ---------- 多行文本框 ---------- */
.ds-textarea {
  width: 100%; min-height: 110px; padding: .6em .8em;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card-bg); color: var(--text);
  font-size: 13.5px; font-family: inherit; line-height: 1.7; resize: vertical;
  transition: border-color .15s, box-shadow .15s;
}
.ds-textarea:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }

/* ---------- 报关模块（财务中心）---------- */
/* 缺报关必填资料的行：淡红底。用变量而非写死颜色，深色模式自动适配。 */
.ds-row-missing > td { background: var(--danger-bg); }
.ds-row-missing:hover > td { filter: brightness(.98); }

/* 缺失单元格里的「＋补全」入口 */
.ds-cell-missing {
  display: inline-block; padding: .1em .6em; cursor: pointer;
  border: 1px dashed var(--danger); border-radius: var(--radius-sm);
  color: var(--danger); font-size: .86em; white-space: nowrap;
}
.ds-cell-missing:hover { background: var(--danger); color: var(--on-accent); }

/* 顶部提醒横幅（缺料提示、导出前风险确认） */
.ds-banner-warn {
  background: var(--warning-bg); color: var(--warning);
  border-left: 3px solid var(--warning);
  padding: .7em 1em; border-radius: var(--radius-sm);
  display: flex; align-items: center; gap: .7em; flex-wrap: wrap;
}
.ds-banner-warn b { color: var(--danger); }

/* 卡片标题行：标题在左、计数在右。
   注意别拿 .ds-card-title-bar 当容器用 —— 那是标题左侧那根 3px 红色小竖条。 */
.cs-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .8em; flex-wrap: wrap; margin-bottom: .6em;
}

/* 32 列宽表需要独立横向滚动，页面本身不能横向滚 */
.cs-scroll { overflow-x: auto; max-height: 560px; overflow-y: auto; }
/* .ds-table 是 width:100%+table-layout:fixed，32 列会被压到每列约 37px 完全没法读。
   只给报关明细这张宽表兜一个最小宽度让容器横向滚动，不动全局 .ds-table（会波及所有模块）。 */
.cs-table-wide { min-width: 2680px; }
/* 单证 24 列，同理需要最小宽度才不会被压扁 */
.cs-table-doc { min-width: 2400px; }
/* 品名知识库 14 列，材质/用途是长文本，压到等宽会完全没法读 */
.cs-table-kb { min-width: 1800px; }
.cs-modal-wide { width: min(960px, 94vw); }
/* 弹窗内的滚动区：比页面里的表格矮，避免弹窗超出视口 */
.cs-scroll-md { max-height: 60vh; }
.cs-scroll-sm { max-height: 40vh; margin: .8em 0; }
/* 长文本字段（报关材质/用途）在弹窗表单里独占整行 */
.cs-field-full { grid-column: 1 / -1; }
/* AI（看产品图）推断出来的单元格：底色区分 + 角标，提示人工复核 */
.cs-ai-cell { background: var(--warning-bg); }
.cs-ai-tag {
  margin-left: .4em; padding: 0 .35em; border-radius: 3px;
  background: var(--warning); color: var(--card-bg);
  font-size: 10px; font-weight: 700; vertical-align: middle;
}
.cs-hidden-file { display: none; }
.cs-hidden { display: none; }
.cs-input-missing { border-color: var(--danger); }

/* ---------- 无障碍 ---------- */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- 意见反馈（feedback.js）---------- */
/* 弹窗：反馈是「一段文字 + 几张截图 + 一串留言」的竖向内容，不需要报关那种 960px 宽表 */
.ds-modal-md { width: min(720px, 94vw); }

/* 截图区：粘贴 / 拖拽 / 点选三合一。比 ds-upload 矮很多——它是附件，不是主角 */
.ds-shots {
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  background: var(--hover);
  padding: .6em .8em;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.ds-shots:hover, .ds-shots.dragover { border-color: var(--accent); background: var(--card-bg); }
.ds-shots-hint { color: var(--muted); font-size: 12.5px; }

.ds-thumb-grid { display: flex; flex-wrap: wrap; gap: .5em; }
.ds-thumb-grid:not(:empty) { margin-top: .6em; }
.ds-thumb-cell { position: relative; display: inline-block; line-height: 0; }
.ds-thumb-lg {
  width: 96px; height: 96px; object-fit: cover; cursor: zoom-in;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--hover);
}
.ds-thumb-del {
  position: absolute; top: -6px; right: -6px;
  width: 18px; height: 18px; line-height: 16px; padding: 0;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--card-bg); color: var(--muted);
  font-size: 13px; cursor: pointer;
}
.ds-thumb-del:hover { color: var(--danger); border-color: var(--danger); }

/* 留言流 */
.ds-feed-post { padding-bottom: .4em; border-bottom: 1px solid var(--border); }
.ds-feed-text { white-space: pre-wrap; word-break: break-word; font-size: 13.5px; line-height: 1.7; }
.ds-feed { display: flex; flex-direction: column; gap: .7em; margin-top: .9em; }
.ds-feed-item {
  border-left: 3px solid var(--border);
  padding: .1em 0 .1em .7em;
}
/* 处理人的发言要一眼认出来（提交人往下翻时找的就是这几条） */
.ds-feed-item.is-admin { border-left-color: var(--accent); }
.ds-feed-head { display: flex; align-items: center; gap: .5em; font-size: 12.5px; margin-bottom: .2em; }
/* 处理结论：全页最该被看到的一块，给一条实心左边框顶住 */
.ds-feed-conclusion { border-left: 4px solid var(--success); margin: .6em 0; }

/* ---------- 运营分析 · 关键词机会页（2026-07-29 新增）---------- */
/* 全部用变量，深色主题自动跟随；只新增，不改上面任何既有类。 */

/* 锚点句：数据源自己算好的那句量化结论，是整页最该先被读到的一句。
   比正文大一号 + 左边框顶住，和下面的洞察条拉开层次。 */
.ds-anchor {
  font-size: 15px; font-weight: 600; line-height: 1.75; color: var(--text);
  border-left: 4px solid var(--accent);
  background: var(--hover);
  padding: .75em 1em; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin-bottom: .9em;
}
/* 洞察条：数据源给的 context_bullets，一条一行，别和锚点句抢视线 */
.ds-insight { margin: 0 0 .9em; padding: 0; list-style: none; }
.ds-insight li {
  position: relative; padding: .35em 0 .35em 1.1em;
  font-size: 13.5px; line-height: 1.75; color: var(--text-2);
}
.ds-insight li::before {
  content: "·"; position: absolute; left: .25em;
  color: var(--accent); font-weight: 700;
}
/* 残余不确定性：数据源明说"这些还看不出来"，必须显示 —— 少了它，
   估算会被当成实测。压灰但不隐藏。 */
.ds-caveat {
  font-size: 12.5px; line-height: 1.7; color: var(--muted);
  border-left: 3px solid var(--border); padding: .5em .9em; margin-bottom: .9em;
}

/* Markdown 渲染块。数据源有几个字段直接给的就是 Markdown（推广评估的证据块、
   经济明细表、竞价区间表），照它给的版式渲染，不拆开重拼。 */
.ds-md { font-size: 13.5px; line-height: 1.8; color: var(--text-2); }
.ds-md p { margin: .4em 0; }
.ds-md b { color: var(--text); }
.ds-md blockquote {
  margin: .5em 0; padding: .55em .9em;
  border-left: 3px solid var(--info); background: var(--info-bg);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.ds-md table { border-collapse: collapse; width: 100%; margin: .6em 0; font-size: 13px; }
.ds-md th, .ds-md td {
  border: 1px solid var(--border); padding: .45em .7em; text-align: left;
}
.ds-md th { background: var(--hover); font-weight: 600; color: var(--text); }

/* 图表容器：.ds-chart-box 是 600px 定宽的（仪表盘那种小图），
   时间序列要全宽才看得出峰谷，所以另起一个。 */
.ds-chart-wide { position: relative; width: 100%; height: 320px; margin-bottom: .6em; }

/* 下钻区的子标签：比 .ds-tabs 小一号，避免和模块级 Tab 混淆 */
.ds-subtabs { display: flex; gap: .3em; flex-wrap: wrap; margin-bottom: .9em; }
.ds-subtab {
  border: 1px solid var(--border); background: var(--card-bg); cursor: pointer;
  font-size: 13px; color: var(--muted);
  padding: .4em .85em; border-radius: var(--radius-sm);
}
.ds-subtab:hover { color: var(--text); background: var(--hover); }
.ds-subtab.active { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }

/* ---------- 运营分析 · 工具说明的分段排版（2026-07-29）----------
   数据源的工具说明本身是有层次的（`入参：` 下面缩进的 · 条目），但以前是
   textContent 直塞，换行全折叠成空格 → 3000 字一整坨。这几个类把层次显示回来。 */
.ds-desc { font-size: 13px; line-height: 1.75; color: var(--text-2); }
/* 一句话概述：整份说明里最该先被读到的一句，比正文重一档 */
.ds-desc-lead {
  font-size: 13.5px; font-weight: 550; color: var(--text);
  margin-bottom: .5em;
}
.ds-desc-head { font-size: 12.5px; font-weight: 600; color: var(--muted); margin: .5em 0 .2em; }
.ds-desc-list { margin: 0 0 .4em; padding: 0; list-style: none; }
.ds-desc-list li { position: relative; padding-left: 1em; }
.ds-desc-list li::before {
  content: "·"; position: absolute; left: .2em; color: var(--muted);
}
/* 不是条目的整句（如「典型触发：用户询问…」），不加圆点 */
.ds-desc-list li.is-text { padding-left: 0; }
.ds-desc-list li.is-text::before { content: none; }
.ds-desc-d1 { margin-left: 1em; }
.ds-desc-d2 { margin-left: 2em; }
/* 折叠段（触发时机 / 入参 / 注意）：信息一条没少，只是不默认占屏 */
.ds-desc-fold { margin: .25em 0; }
.ds-desc-fold > summary {
  cursor: pointer; font-size: 12.5px; color: var(--info);
  padding: .15em 0; list-style: revert;
}
.ds-desc-fold > summary:hover { color: var(--accent); }
/* 兜底：后端解析不出结构时按原文显示，**保留换行** —— 那也比一整坨强 */
.ds-desc-raw { font-size: 12.5px; line-height: 1.7; color: var(--text-2); white-space: pre-wrap; }

/* ---------- 运营分析 · 广告结构页的变体缩略图行（2026-07-29）----------
   一个关键词曝光了哪些变体：图 + ASIN 号，点开跳商品页。
   与表格里的 .ds-thumb 不同，这里图下面还要带一行 ASIN，所以是纵向的小卡。 */
.ds-thumb-row { display: flex; flex-wrap: wrap; gap: .8em; margin: .4em 0 .9em; }
.ds-thumb-row a {
  display: flex; flex-direction: column; align-items: center; gap: .25em;
  text-decoration: none; font-size: 11.5px; color: var(--muted);
}
.ds-thumb-row a:hover { color: var(--accent); }
.ds-thumb-row img {
  width: 56px; height: 56px; object-fit: contain; border-radius: 4px;
  background: var(--hover); border: 1px solid var(--border);
}

/* ---------- 图片放大（选品核验的逐张比对，2026-07-30）----------
   核验这件事本身就是「拿待核验品跟候选品一张张比外观」，40px 的缩略图根本看不出
   差别。点一下在页内弹大图，点任意处或 ESC 关掉 —— 不用 window.open 开新标签，
   否则一走就把结果弹窗（相似度、命中依据、AI 判断依据）丢了，来回对不上。
   z-index 高于 .ds-modal-overlay(1000)：大图基本都是在核验结果弹窗之上打开的。 */
.ds-lightbox {
  position: fixed; inset: 0; z-index: 1100;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .8em; padding: 4vh 4vw;
  background: rgba(0, 0, 0, .78); cursor: zoom-out;
}
.ds-lightbox-img {
  max-width: 100%; max-height: 80vh; object-fit: contain;
  border-radius: var(--radius-sm); background: #fff;
  box-shadow: 0 8px 40px rgba(0, 0, 0, .5);
}
.ds-lightbox-cap {
  max-width: 72ch; text-align: center; color: #fff;
  font-size: 13px; line-height: 1.6; word-break: break-word;
}
.ds-lightbox-close {
  position: absolute; top: 16px; right: 20px;
  width: 34px; height: 34px; padding: 0; line-height: 30px;
  border: 1px solid rgba(255, 255, 255, .45); border-radius: 50%;
  background: rgba(0, 0, 0, .35); color: #fff; font-size: 20px; cursor: pointer;
}
.ds-lightbox-close:hover { background: rgba(255, 255, 255, .18); }
/* 能点开大图的图：给 zoom-in 光标当可点提示（.ds-thumb-lg 自带，不用叠） */
.ds-zoomable { cursor: zoom-in; }
/* 弹窗里的「待核验产品」原图：比 .ds-thumb-lg 大一档，且用 contain 不裁切
   —— 汽配件的形状差异经常就在边角上，cover 一裁就看不出来了 */
.ds-thumb-xl {
  width: 150px; height: 150px; object-fit: contain;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--hover);
}

/* ---------- 实拍图比对（仓储-出入库统计）---------- */
/* 左实拍右产品图并排。窄屏自动堆成上下两栏 —— 仓库的人常拿平板看。 */
.ds-shot-cmp { display: flex; gap: 1em; flex-wrap: wrap; }
.ds-shot-pane { flex: 1 1 300px; min-width: 0; }
.ds-shot-cap { font-size: 12.5px; color: var(--text-2); font-weight: 650; margin-bottom: .4em; }
.ds-shot-big {
  width: 100%; max-height: 46vh; object-fit: contain;
  background: var(--hover); border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: zoom-in;
}
.ds-shot-link { cursor: pointer; }
/* 弹窗底部操作条：左边翻页、右边确认，中间用 .ds-flex-1 撑开 */
.ds-modal-foot { display: flex; align-items: center; gap: .5em; margin-top: 1em; flex-wrap: wrap; }
.ds-flex-1 { flex: 1; }

/* 表格里的分组行（任务中心「按处理人折叠」：一个人一行，点开是他手上的全部任务）。
   做成表格内的一行而不是每组一张表 —— 一张表只有一个表头，列宽天然对齐，
   makeResizable 拖列宽也照旧生效。 */
.ds-group-row > td {
  background: var(--hover);
  border-top: 1px solid var(--border);
  cursor: pointer; user-select: none;
  padding: .55em .8em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-group-row:hover > td { background: var(--border-2); }
.ds-group-row > td > * { margin-right: .5em; }
.ds-group-caret { display: inline-block; width: 1em; color: var(--muted); }
/* 分组视图的工具条：左边「共几条/几个人」，右边全部展开-收起 */
.ds-group-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: .7em; padding: .2em .1em .6em; color: var(--text-2); font-size: .92em;
}

/* ── 采购合同 · 明细行内编辑（2026-08-27）────────────────────────────────
   合同明细在草稿状态可以直接改，格子就是输入框。三个状态要一眼分得清：
   普通格子 / 人工改过的格子（自动重算不会覆盖它，给个蓝框提示）/ 已删除的行。 */
.ct-cell {
  /* 宽度跟着列走：.ds-table 是 table-layout:fixed 且格子 overflow:hidden，
     input 不设 100% 的话会保持默认宽度，把列拖窄之后输入框直接被切掉一半 */
  width: 100%; box-sizing: border-box;
  height: 30px; padding: .25em .5em; font-size: .92em;
}
.ct-cell-edited {
  border-color: var(--info); background: var(--info-bg);
}
.ct-row-dead > td { opacity: .5; text-decoration: line-through; }
.ct-row-dead > td:last-child { opacity: 1; text-decoration: none; }
.ct-add-row {
  margin-top: 1em; padding-top: .8em; border-top: 1px dashed var(--border);
  font-size: .95em;
}
.ct-add-row > b { color: var(--text-2); }

/* 采购合同列表 12 列，「操作」列要放 5 个按钮（明细/PDF/Excel/审核通过/驳回）。
   .ds-table 是 width:100%+table-layout:fixed 且格子 overflow:hidden —— 不给最小宽度的话
   操作列只分到约 130px，第 5 个按钮被裁到框外，人只会以为「没有驳回这个功能」。
   数值 = contract.js 里那 12 个 th 写死宽度之和（34+160+88+230+84+110+150+130+100+100+90+330）。
   改列宽或增删列时两处一起改，对不上就会出现莫名其妙的空白或又被压扁。 */
.ct-table-wide { min-width: 1606px; }

/* 「操作」列钉死在右边。给了 min-width 之后内容不再被裁，但窄屏上最后一列会跑到
   可视区外，人要横向滚才够得到 —— 财务连着两轮反馈「拒绝不了」，第一轮是被裁掉，
   第二轮就是藏在右边。审核/驳回是这个页面最主要的动作，不能靠横向滚才点得到。
   th 本来就是 sticky top（冻结表头），sticky 可以同时吃两个方向，直接补 right 即可。
   背景必须不透明：sticky 单元格浮在别的列上面，透明的话下面的字会透出来叠在一起；
   斑马纹那一行要单独把底色补回去，否则钉住的这一列看起来像少了一道条纹。 */
.ct-table-wide th:last-child,
.ct-table-wide td:last-child {
  position: sticky;
  right: 0;
  background: var(--card-bg);
  box-shadow: inset 1px 0 0 var(--border);
}
.ct-table-wide th:last-child { z-index: 3; background: var(--hover); }
.ct-table-wide td:last-child { z-index: 1; }
.ct-table-wide tbody tr:nth-child(even) td:last-child {
  background: color-mix(in srgb, var(--hover) 45%, var(--card-bg));
}
.ct-table-wide tbody tr:hover td:last-child { background: var(--hover); }

/* 明细弹窗顶部的「合同抬头」一行：标签 + 输入框（撑满）+ 保存按钮。
   输入框要 flex:1 —— 全称二十来个字，给固定宽度的话长一点的公司名看不全。 */
.ct-title-row {
  display: flex; align-items: center; gap: .6em; margin-bottom: .3em;
}
.ct-title-row > label { flex: none; font-weight: 650; color: var(--text-2); }
.ct-title-row > input { flex: 1; min-width: 0; }

/* 驳回弹窗里「点名有问题的明细行」。一份合同最多见过 20 来行，但补充合同并进来
   之后可能更长，给个固定高度自己滚 —— 让弹窗跟着长高的话，确认按钮会被顶出屏幕。 */
.ct-rej-items {
  max-height: 220px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .4em .6em;
}
.ct-rej-item {
  display: flex; align-items: center; gap: .5em;
  padding: .25em 0; font-size: .92em; cursor: pointer;
}
.ct-rej-item:hover { background: var(--hover); }
.ct-rej-item > input { flex: none; }

/* ---------- 模板微调画布（设计中心「模板管理」里拖框改版式）----------
   只做**粗排**：拖框改位置/拉大小。所见即所得的真相源永远是右边那张
   后端渲染的预览 —— 浏览器和 Pillow 的排版算法不一样（自动缩字号、换行、
   去白底、投影），画布上对齐了不代表出图对齐。保存后右边会自动重出。
   几何尺寸（left/top/width/height）由 JS 按画布缩放算，只有这部分写在
   元素的 style 上，其余外观全在这里。 */
.ds-canvas-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 1em; align-items: start; }
.ds-canvas-col > .ds-hint { margin-bottom: .4em; }
.ds-canvas {
  position: relative; margin: 0 auto;
  background: var(--hover);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  touch-action: none; user-select: none;
}
/* 画布图片本身可能只占 .ds-canvas 的一部分：有的槽位会伸到画布外
   （PSD 里智能对象比画布大是常事），那时按「画布 ∪ 所有框」的并集来铺 */
.ds-canvas-img { position: absolute; display: block; }
.ds-canvas-edge {           /* 真正的画布边界，出血部分一眼看得出来 */
  position: absolute; border: 1px dashed var(--muted); pointer-events: none;
}
.ds-canvas-box {
  position: absolute; cursor: move;
  border: 2px solid var(--info); background: rgba(37, 99, 235, .10);
}
.ds-canvas-box.is-slot { border-color: var(--danger); background: rgba(220, 38, 38, .10); }
.ds-canvas-box.is-active { box-shadow: 0 0 0 2px var(--card-bg), 0 0 0 4px var(--info); }
.ds-canvas-box.is-slot.is-active { box-shadow: 0 0 0 2px var(--card-bg), 0 0 0 4px var(--danger); }
.ds-canvas-tag {
  position: absolute; left: -2px; top: -1.5em;
  padding: 0 .3em; border-radius: 3px;
  font-size: 10px; line-height: 1.4; white-space: nowrap;
  background: var(--info); color: #fff; pointer-events: none;
}
.ds-canvas-box.is-slot .ds-canvas-tag { background: var(--danger); }
.ds-canvas-grip {
  position: absolute; right: -6px; bottom: -6px;
  width: 11px; height: 11px; border-radius: 2px;
  background: var(--info); cursor: nwse-resize;
}
.ds-canvas-box.is-slot .ds-canvas-grip { background: var(--danger); }
.ds-canvas-dirty { color: var(--warning); font-weight: 600; }
/* 表格里被画布选中的那一行（点框↔点行是双向的，不高亮就找不到自己在改哪个） */
.ds-table tbody tr.is-active > td { background: var(--info-bg); }

