/* ===================================================
 * ACMELAB 艾科美一体化工作平台 · 品牌设计系统
 * 配色基因：定稿 Logo 绿→蓝渐变
 * =================================================== */

@import './semantic-aliases.css';

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; font-family: var(--font); font-size: var(--fs-body); color: var(--text); background: var(--bg); -webkit-font-smoothing: antialiased; }
button { font-family: inherit; }
input, select, textarea { font-family: inherit; font-size: inherit; }
a { color: var(--primary-text); text-decoration: none; }
.hidden { display: none !important; }

/* ===================================================
 * 登录页 · 全屏品牌沉浸
 * =================================================== */
.login-page {
  min-height: 100vh; display: flex;
  background: var(--grad-login);
  position: relative; overflow: hidden;
}

/* 装饰：左下到右上的柔光 */
.login-page::before {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(circle at 15% 85%, var(--a-white-mid) 0%, transparent 40%),
    radial-gradient(circle at 85% 15%, var(--a-white-weak) 0%, transparent 35%);
  pointer-events: none;
}
/* 装饰：科技感网格 */
.login-page::after {
  content: ''; position: absolute; inset: 0;
  background-image:
    linear-gradient(var(--a-white-weak) 1px, transparent 1px),
    linear-gradient(90deg, var(--a-white-weak) 1px, transparent 1px);
  background-size: 48px 48px;
  pointer-events: none;
  mask-image: radial-gradient(circle at center, black 0%, transparent 75%);
}

.login-wrap {
  position: relative; z-index: 1;
  display: flex; width: 100%; min-height: 100vh;
  align-items: center; justify-content: center;
  padding: var(--sp-xxl);
}

/* 左侧品牌区 */
.login-brand {
  flex: 1; max-width: 560px; color: var(--on-brand, #FFFFFF);
  padding: 60px; display: flex; flex-direction: column; justify-content: center;
}
.login-brand .brand-logo-big {
  width: 220px; height: auto; margin-bottom: var(--sp-huge);
  filter: drop-shadow(0 8px 24px var(--a-black-mid));
}
.login-brand h1 {
  font-size: var(--glyph-splash); font-weight: var(--fw-bold); letter-spacing: 1px;
  margin-bottom: var(--sp-md); line-height: var(--lh-tight);
}
.login-brand .tagline {
  font-size: var(--fs-titleLg); opacity: 0.9; margin-bottom: var(--sp-huge); font-weight: var(--fw-regular);
}
.login-brand .features {
  display: flex; flex-direction: column; gap: var(--sp-lg); margin-top: var(--sp-xxl);
}
.login-brand .feat {
  display: flex; align-items: center; gap: var(--sp-md); font-size: var(--fs-bodyLg); opacity: 0.95;
}
.login-brand .feat::before {
  content: '✓'; width: 22px; height: 22px; border-radius: 50%;
  background: var(--a-white-mid); display: inline-flex;
  align-items: center; justify-content: center; font-size: var(--fs-caption); font-weight: var(--fw-semibold);
}

/* 右侧登录表单卡片 */
.login-box {
  background: var(--surface, #FFFFFF); border-radius: var(--radius-xxl); padding: 44px var(--sp-huge);
  width: 420px; box-shadow: var(--elev-ink-xxl);
  position: relative; overflow: hidden;
}
.login-box::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: var(--grad-brand-h);
}
.login-box .login-title {
  font-size: var(--fs-hero); font-weight: var(--fw-bold); color: var(--text); margin-bottom: var(--sp-sm);
}
.login-box .login-sub {
  font-size: var(--fs-body); color: var(--text-3); margin-bottom: var(--sp-huge);
}
.login-box .form-group { margin-bottom: var(--sp-xl); }
.login-box .form-input {
  height: var(--ctl-secondary); padding: 0 var(--sp-lg) 0 44px; font-size: var(--fs-bodyLg);
  border-radius: var(--radius-md); border: 1.5px solid var(--border);
  background: var(--surface-2); transition: all var(--dur-fast);
}
.login-box .form-input:focus {
  border-color: var(--primary); background: var(--surface, #FFFFFF);
  box-shadow: 0 0 0 4px var(--a-green-weak);
}
.login-box .input-wrap { position: relative; }
.login-box .input-wrap .ic {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--text-3); font-size: var(--fs-title); pointer-events: none;
}
.login-box .btn-primary {
  height: var(--ctl-secondary); font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold);
  border-radius: var(--radius-md); letter-spacing: 1px;
  background: var(--grad-brand); border: none;
  box-shadow: var(--shadow-brand);
}
.login-box .btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: var(--elev-brand-lg);
}
.login-box .test-accounts {
  margin-top: var(--sp-xxl); padding-top: var(--sp-xl);
  border-top: 1px solid var(--border-2);
  font-size: var(--fs-caption); color: var(--text-3);
}
.login-box .test-accounts .ta-title { color: var(--text-2); font-weight: var(--fw-semibold); margin-bottom: var(--sp-sm); }
.login-box .test-accounts .ta-item {
  display: flex; gap: var(--sp-sm); padding: var(--sp-xs) 0;
  font-family: 'SF Mono', Consolas, monospace; font-size: var(--fs-mini);
}
.login-box .test-accounts .ta-item code {
  background: var(--surface-2); padding: var(--sp-xxs) var(--sp-sm); border-radius: var(--radius-xxs);
  color: var(--brand-blue);
}

/* ===================================================
 * 侧边栏 · 现代化
 * =================================================== */
.sidebar {
  width: var(--sidebar-w);
  /* 【W1-9】侧栏渐变原手写 #0F2A50 → #0C2240，与 --brand-deep / --brand-night 同一色相族
     却各自漂移；统一到令牌。
     【2026-10-18】改挂 chrome 段（**恒定型**），不再引用 --brand-deep（主题型）：
     后者深色档是 #1E6BB8 亮蓝，会把侧栏整层提亮成亮底，而侧栏前景恒为浅灰（不随主题变）
     ⇒ 深色主题下三级文字 4.22 / 3.60 / 2.11 全部跌破 AA。详见 semantic-aliases.css 同段注释。 */
  background: linear-gradient(180deg, var(--chrome-sidebar-from) 0%, var(--chrome-sidebar-to) 100%);
  color: var(--chrome-fg);
  display: flex; flex-direction: column;
  position: fixed; top: 0; left: 0; bottom: 0;
  z-index: 100; transition: transform var(--dur-base);
}
.sidebar .brand {
  padding: var(--sp-xl) var(--sp-xl);
  display: flex; align-items: center; gap: var(--sp-md);
  border-bottom: 1px solid var(--a-white-weak);
  /* 【2026-10-18】原为 green .12 + blue .08 **亮叠**，合成底 #0D5481 把版本行（.sub）压到 3.11:1。
     改暗色带：与渐变起点同族、明度更低，版本行反升到 4.98:1，且保留「品牌块区别于导航区」的层次。 */
  background: var(--chrome-brand-bg);
}
.sidebar .brand-logo {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.sidebar .brand-logo img { width: 100%; height: 100%; object-fit: contain; display: block; }
.sidebar .brand .name { font-size: var(--fs-bodyLg); font-weight: var(--fw-bold); color: var(--on-brand, #FFFFFF); letter-spacing: 0.3px; }
.sidebar .brand .sub { font-size: var(--fs-mini); color: var(--chrome-fg-dim); margin-top: var(--sp-xxs); letter-spacing: 0.5px; }

.nav { flex: 1; overflow-y: auto; padding: var(--sp-lg) var(--sp-md); }
.nav-group-title {
  font-size: var(--fs-mini); color: var(--chrome-fg-dim); padding: var(--sp-md) var(--sp-md) var(--sp-sm);
  /* 【三端UI评审 P0-1】原 #6B7E96 → 改 #8DA3BD，注释当时写「5.52:1」。
     【2026-10-18 复核】5.52 是拿 #0F2A50 量的；侧栏真实渐变起点是 #0C447C，对它是 3.80（不达 AA）。
     现归位 chrome.fgDim #A5B6CB：起点 4.76 / 品牌块带 4.98 / 渐变终点 6.81 ✅（同色相，只提 L）。 */
  letter-spacing: 1.2px; font-weight: var(--fw-semibold);
}
.nav-item {
  display: flex; align-items: center; gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-md); margin: var(--sp-xxs) 0; border-radius: var(--radius-sm);
  cursor: pointer; color: var(--chrome-fg-nav); font-size: var(--fs-body);
  line-height: var(--lh-normal); min-height: var(--ctl-web-xl); max-height: var(--ctl-web-xl);
  transition: all var(--dur-base); position: relative;
  overflow: hidden;
}
.nav-item > span.ic {
  flex: 0 0 18px; width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 0; overflow: hidden;
}
.nav-item > span.ic svg { width: 14px; height: 14px; display: block; }
.nav-item > span:not(.ic) {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  letter-spacing: 0;
}
.nav-item:hover { background: var(--a-white-weak); color: var(--on-brand, #FFFFFF); }
.nav-item.active {
  background: linear-gradient(90deg, var(--a-green-mid) 0%, var(--a-blue-mid) 100%);
  color: var(--on-brand, #FFFFFF); font-weight: var(--fw-semibold);
}
.nav-item.active::before {
  content: ''; position: absolute; left: 0; top: 8px; bottom: 8px;
  width: 3px; background: var(--grad-brand-h); border-radius: 0 3px 3px 0;
}
.nav-item .ic {
  width: 18px; text-align: center; font-size: var(--fs-bodyLg);
  font-style: normal; opacity: 0.85;
}

/* ===================================================
 * 主区域
 * =================================================== */
.main { margin-left: var(--sidebar-w); min-height: 100vh; display: flex; flex-direction: column; }
.topbar {
  height: var(--header-h);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; padding: 0 var(--sp-xxl); gap: var(--sp-lg);
  position: sticky; top: 0; z-index: 50;
  box-shadow: var(--elev-ink-sm);
}
/* 【v2.4.92 · 面包屑】下面两条 .crumbs 样式自 v2.4.x 起就在，但**全仓零处渲染**
   （`grep -rn crumbs src/ public/index.html` 只有本文件命中）—— 典型的「样式写了没用上」。
   本次接入 DOM，并补齐 <ol> 复位、父级链接态与末段对齐。
   语义结构 = nav > ol > li（读屏播报「列表 N 项」并给出当前位置，优于一串 span）。 */
.topbar .crumbs { min-width: 0; }
.topbar .crumbs .crumb-list { display: flex; align-items: center; list-style: none; margin: 0; padding: 0; min-width: 0; }
.topbar .crumbs .crumb-item { display: flex; align-items: center; min-width: 0; }
.topbar .crumbs .crumb-parent { font-size: var(--fs-bodyLg); color: var(--text-2); font-weight: var(--fw-medium); white-space: nowrap; }
a.crumb-parent { text-decoration: none; transition: color var(--dur-fast); }
a.crumb-parent:hover { color: var(--text); text-decoration: underline; }
/* 末段是 20px 的 .page-title，它自带 margin-bottom（原为块级布局留白）；
   在 flex 行里会把基线顶偏 ~2px，故在面包屑内清零。 */
.topbar .crumbs .page-title { margin-bottom: 0; }
.topbar .crumbs .sep { margin: 0 var(--sp-sm); color: var(--text-3); font-size: var(--fs-bodyLg); }
.topbar .spacer { flex: 1; }
.topbar .right { display: flex; align-items: center; gap: var(--sp-md); }
.topbar .user {
  display: flex; align-items: center; gap: var(--sp-md); cursor: pointer;
  padding: var(--sp-sm) var(--sp-md); border-radius: var(--radius-sm); transition: all var(--dur-fast);
}
.topbar .user:hover { background: var(--surface-2); }
.topbar .avatar {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--grad-brand); color: var(--on-brand, #FFFFFF);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-body); font-weight: var(--fw-semibold);
}
.topbar .user .role { font-size: var(--fs-mini); color: var(--text-3); }
.topbar .menu-toggle {
  display: none; background: none; border: none; font-size: var(--fs-display); cursor: pointer;
}

.content { flex: 1; padding: var(--sp-xxl) var(--sp-huge); max-width: 1480px; margin: 0 auto; width: 100%; }
.page-title { font-size: var(--fs-display); font-weight: var(--fw-bold); color: var(--text); margin-bottom: var(--sp-xs); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }  /* 【移动端P1】长标题在顶栏折行，改为单行省略 */

/* ===== 移动端走查修复（2026-09-16 晚）===== */
/* P1：搜索图标全局定位——原来 .search-ic 绝对定位只在 .toolbar 作用域内，
   报价移动条(.q-m-bar)与部分模块工具栏的图标会悬浮到输入框外 */
.search-box { position: relative; }
.search-box .search-ic {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-3); pointer-events: none; display: inline-flex;
}
.search-box .form-input { padding-left: var(--sp-huge); }

/* P1：财务「导出财务总表」专用样式，横排紧凑，不再伪装成 KPI 数据卡 */
.stat-card.stat-export { background: linear-gradient(135deg, var(--info, #2563EB), #1e40af); color: var(--on-brand, #FFFFFF); cursor: pointer; }
.stat-card.stat-export:hover { filter: brightness(1.08); }
.stat-export-inner { display: flex; align-items: center; justify-content: center; gap: var(--sp-sm); height: 100%; font-size: var(--fs-body); font-weight: var(--fw-semibold); }
.stat-export-ic { display: inline-flex; }

/* P0：HR 概览双列网格（部门人数分布 + 待审批异动）移动端强制单列 */
@media (max-width: 768px) {
  .hr-ov-grid { grid-template-columns: 1fr !important; }
}
html.force-mobile .hr-ov-grid { grid-template-columns: 1fr !important; }

/* P0：KPI 大数字移动端缩号——28px 在半宽卡内放不下金额（如 ¥2,364,012.00 被裁切） */
@media (max-width: 768px) {
  .stat-card .value { font-size: var(--fs-titleLg); letter-spacing: -0.2px; }
}
html.force-mobile .stat-card .value { font-size: var(--fs-titleLg); letter-spacing: -0.2px; }

/* P2：产品库等 fm-tabs 状态 chips 移动端横滚，与报价 chips 交互统一 */
@media (max-width: 768px) {
  .fm-tabs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: var(--sp-md); -webkit-overflow-scrolling: touch; }
  .fm-tab { flex: none; }
}
html.force-mobile .fm-tabs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: var(--sp-md); -webkit-overflow-scrolling: touch; }
html.force-mobile .fm-tab { flex: none; }
.page-sub { font-size: var(--fs-body); color: var(--text-3); margin-bottom: var(--sp-xl); }

/* ===================================================
 * 通用组件
 * =================================================== */

/* ---- 卡片层级体系（v2.4.105 · 路线图批 3.1）----
   层级 = 元素离「主平面」的距离，与 elevation 档位一一对应：
     静置卡片 → --elev-ink-sm ｜ 悬停抬升 → --elev-ink-md ｜ 拖拽中 → --elev-ink-lg
     ｜ 浮层 / 面板 → --elev-ink-xl ｜ 模态 → --elev-ink-xxl
   🔴 本批修的是「档位越级」：真值源 elevation.ink.md._role 明写「抬升 / 悬停 / 下拉触发
      （卡片 hover、小菜单）」，但历史实现里悬停有的走兼容别名 --shadow-md（其值实为
      ink-lg —— 名字与语义错位，`md` 名对应 `lg` 值）、有的直接跳到 ink-xl，
      导致同屏不同卡片的悬停强度不一致。本批统一：悬停只抬一层，一律 --elev-ink-md。
   🔴 位移固定 -2px（此前 -2px / -3px 混用，同屏卡片「跳得不一样高」）。
   🔴 过渡改具体属性 + motion 令牌（此前 `transition: all 0.2s` 会把 ::after 装饰层
      也卷进过渡，且在低端机上触发全属性重算）。
   🔴 新代码不再引用 --shadow / --shadow-md / --shadow-lg 兼容别名（名字有歧义），
      直接写 --elev-<族>-<档>。别名仅为历史引用保留。 */
.card {
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--elev-ink-sm); padding: var(--sp-xl);
  border: 1px solid var(--border-2);
}
/* 卡片之间的垂直节奏 = --sp-xl(20)，与卡内 padding 同值。
   🔴 此前是 --sp-lg(16) < 卡内 padding(20)：卡与卡之间比卡内部还挤，
      违反接近性原则（格式塔）—— 视觉上「组间比组内松散」才是正确的分组暗示。
   ⚠️ 模板里写在卡片上的 .mt-16 在本条生效时会被覆盖（.card + .card 特异性更高，
      0-2-0 > 0-1-0），卡片间落地间距以此条为准。 */
.card + .card { margin-top: var(--sp-xl); }
.card-title { font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); margin-bottom: var(--sp-lg); color: var(--text); }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--sp-lg); }
.stat-card {
  background: var(--surface); border-radius: var(--radius);
  border: 1px solid var(--border-2); padding: var(--sp-xl) var(--sp-xl);
  box-shadow: var(--elev-ink-sm);
  transition: box-shadow var(--dur-base) var(--ease-standard),
              transform var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard);
  position: relative; overflow: hidden;
}
.stat-card:hover {
  transform: translateY(-2px); box-shadow: var(--elev-ink-md); border-color: var(--primary-2);
}
.stat-card::after {
  content: ''; position: absolute; top: 0; right: 0; width: 60px; height: 60px;
  background: var(--grad-brand-soft); border-radius: 0 0 0 60px; opacity: 0.5;
}
.stat-card .label { font-size: var(--fs-caption); color: var(--text-3); font-weight: var(--fw-medium); }
.stat-card .value { font-size: var(--fs-hero); font-weight: var(--fw-bold); margin-top: var(--sp-sm); color: var(--text); }
.stat-card .value.primary { background: var(--grad-brand-h); -webkit-background-clip: text; background-clip: text; color: transparent; }
.stat-card .trend { font-size: var(--fs-mini); color: var(--text-3); margin-top: var(--sp-sm); }
.stat-card .mom-badge { display: inline-block; font-size: var(--fs-mini); font-weight: var(--fw-semibold); margin-top: var(--sp-sm); }
.stat-card .ic-bg {
  position: absolute; right: 16px; top: 16px;
  width: 36px; height: 36px; border-radius: var(--radius-md);
  background: var(--grad-brand-soft); color: var(--primary-text);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-titleLg); z-index: 1;
}

/* 工具栏 */
.toolbar {
  display: flex; align-items: center; gap: var(--sp-md); margin-bottom: var(--sp-lg);
  flex-wrap: wrap;
}
.toolbar .search-box {
  position: relative; flex: 1; min-width: 200px; max-width: 360px;
}
.toolbar .search-box .form-input { padding-left: var(--sp-huge); }
.toolbar .search-box .search-ic {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-3); pointer-events: none;
}
.toolbar .spacer { flex: 1; }

/* 按钮 */
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-sm); justify-content: center;
  padding: var(--sp-sm) var(--sp-lg); border-radius: var(--radius-md); border: 1px solid var(--border);
  background: var(--surface); color: var(--text-2); cursor: pointer;
  font-size: var(--fs-body); font-weight: var(--fw-medium); transition: all var(--dur-fast); white-space: nowrap;
  height: var(--ctl-web-lg);
}
.btn:hover { border-color: var(--primary-strong); color: var(--primary-text); background: var(--primary-bg); }
.btn-primary {
  background: var(--grad-brand); color: var(--on-brand, #FFFFFF); border-color: transparent;
  box-shadow: var(--elev-brand-sm);
}
.btn-primary:hover {
  background: var(--grad-brand); color: var(--on-brand, #FFFFFF);
  transform: translateY(-1px); box-shadow: var(--shadow-brand);
}
.btn-danger { background: var(--danger); color: var(--on-brand, #FFFFFF); border-color: transparent; }
/* 【W1-9 保留】悬停加深档（#B91C1C / 下方 .btn-warn #8F3919）与浅边框档（#FCA5A5 / #F5C6A5）
   在语义层无对应令牌：现有 --danger/--warning 只有「填充 / 文字」两档，没有「按下加深」和
   「浅描边」的刻度。新增令牌须先改 design-tokens 真值源并三端重生成，故本轮保留原值。 */
.btn-danger:hover { background: #B91C1C; color: var(--on-brand, #FFFFFF); }
/* 【UI测评P1】行内小号删除按钮弱化：表格里每行都挂红色实心按钮视觉噪音过大，
   统一降为「白底红字红描边」，悬停时才恢复实心。弹窗底部等大号危险按钮保持实心。 */
.btn-danger.btn-sm,
.btn.btn-sm.btn-danger {
  background: var(--surface); color: var(--danger-text); border-color: #FCA5A5;
}
.btn-danger.btn-sm:hover,
.btn.btn-sm.btn-danger:hover {
  background: var(--danger); color: var(--on-brand, #FFFFFF); border-color: transparent;
}
.btn-ghost { border: none; background: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--primary-text); border-color: transparent; }
.btn-sm { padding: var(--sp-xs) var(--sp-md); font-size: var(--fs-caption); height: var(--ctl-web-xs); border-radius: var(--radius-xs); }

/* 表单 */
.form-group { margin-bottom: var(--sp-lg); }
.form-label { display: block; font-size: var(--fs-caption); color: var(--text-2); margin-bottom: var(--sp-sm); font-weight: var(--fw-medium); }
.form-input, .form-select, .form-textarea {
  width: 100%; padding: var(--sp-sm) var(--sp-md); font-size: var(--fs-body);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text);
  transition: all var(--dur-fast); height: var(--ctl-web-lg);
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
  outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--a-green-weak);
}
.form-textarea { height: auto; min-height: 80px; padding: var(--sp-md) var(--sp-md); resize: vertical; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-lg); }
.form-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--sp-lg); }

/* ============ 【v2.9.43】库存核算 + 出库审核 ============ */
/* 表单字段下方补充说明（单价/总价联动、加权平均提示、审核后果告知等） */
.form-hint { font-size: var(--fs-caption); color: var(--text-2); margin-top: var(--sp-xs); line-height: var(--lh-tight); }
/* 警示按钮：免审直出等"能用但要谨慎"的动作，强度低于 danger */
.btn-warn { background: var(--warning); color: var(--on-brand, #FFFFFF); border-color: transparent; }
.btn-warn:hover { background: #8F3919; color: var(--on-brand, #FFFFFF); }
.btn-warn.btn-sm { background: var(--surface); color: var(--warning-text); border-color: #F5C6A5; }
.btn-warn.btn-sm:hover { background: var(--warning); color: var(--on-brand, #FFFFFF); border-color: transparent; }
/* 数字列右对齐 + 等宽数位（单价/数量/总价/金额，避免竖排参差） */
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* 库存汇总条：合计项数 / 总数量 / 库存货物总价 */
.stock-summary { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-lg); margin-bottom: var(--sp-lg);
  background: var(--surface-2); border-radius: var(--radius-md); font-size: var(--fs-body); color: var(--text-2); }
.stock-summary strong { color: var(--text); font-weight: var(--fw-semibold); }
.stock-summary-sep { width: 1px; height: 14px; background: var(--border); }
.stock-total-amount { color: var(--primary-text); font-size: var(--fs-title); }
/* 待审核数量角标（挂在工具栏「出库审核」按钮上） */
.stock-badge { display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; margin-left: 2px;
  background: var(--danger); color: var(--on-brand, #FFFFFF);
  border-radius: var(--radius-full); font-size: var(--fs-mini); font-weight: var(--fw-semibold); }
/* 【v3.4.26】列表数量列的「已出完」标记：物品出完不消失（名称/规格/单位留存便于再入库），只标红 */
.stock-out-badge { display: inline-block; padding: 1px 8px; border-radius: var(--radius-full);
  background: var(--danger-weak, rgba(229, 72, 77, 0.12)); color: var(--danger);
  font-size: var(--fs-mini); font-weight: var(--fw-semibold); }
/* 【v3.4.79】低库存预警：台账顶部预警条 + 行内徽标。
   口径＝数量 < 安全库存，0/未设走系统兜底线（真源 server/lib/inventory-alert.js）。
   🔑 配色一律走 --warning-* 令牌：深色主题自动跟随，**不写 dark 覆盖**
      （写死色会在深色档变瞎，且被 css-hardcode-color 闸判硬编码）。 */
.stock-low-alert { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-sm);
  padding: var(--sp-md) var(--sp-lg); margin-bottom: var(--sp-lg);
  background: var(--warning-bg); border: 1px solid var(--warning-border);
  border-left: 3px solid var(--warning); border-radius: var(--radius-sm);
  font-size: var(--fs-body); color: var(--warning-text); }
.stock-low-alert strong { font-weight: var(--fw-semibold); }
.stock-low-hint { color: var(--text-2); font-size: var(--fs-mini); }
.stock-low-badge { display: inline-block; margin-left: var(--sp-sm); padding: 1px 8px;
  border-radius: var(--radius-full); background: var(--warning-bg); color: var(--warning-text);
  border: 1px solid var(--warning-border);
  font-size: var(--fs-mini); font-weight: var(--fw-semibold); }
/* 预警「尚未配置」时的中性提示：没有缺货就不该用报警配色 ——
   报警样式只留给真缺货，否则红标天天在，仓库就不看了。 */
.stock-low-hint-block { padding: var(--sp-md) var(--sp-lg); margin-bottom: var(--sp-lg);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: var(--fs-body); color: var(--text-2); }

/* 出库申请弹窗：物品行 / 添加行 / 合计 */
.or-lines { margin-top: var(--sp-sm); }
.or-lines th, .or-lines td { padding: var(--sp-sm) var(--sp-md); }
.or-lines-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-md);
  margin-top: var(--sp-lg); font-size: var(--fs-caption); color: var(--text-2); font-weight: var(--fw-medium); }
/* 【v3.4.26】添加行四列：搜索框 / 下拉 / 数量 / 按钮。搜索框窄一点，把宽度让给下拉 */
/* 【v3.4.27】出库选品：搜索框 + 可见结果列表（替代藏起来的 select） */
.or-pick { margin-top: var(--sp-md); }
.or-pick-list { max-height: 200px; overflow-y: auto; margin-top: var(--sp-sm);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); }
.or-pick-row { display: flex; align-items: center; gap: var(--sp-sm); width: 100%;
  padding: var(--sp-sm) var(--sp-md); border: 0; border-bottom: 1px solid var(--border);
  background: transparent; color: var(--text); font-size: var(--fs-body);
  text-align: left; cursor: pointer; }
.or-pick-row:last-child { border-bottom: 0; }
.or-pick-row:hover { background: var(--surface-2); }
.or-pick-row.active { background: color-mix(in srgb, var(--primary) 12%, transparent);
  box-shadow: inset 3px 0 0 var(--primary); }
.or-pick-row .or-pick-name { flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; font-weight: var(--fw-semibold); }
.or-pick-row .or-pick-name em { font-style: normal; font-weight: var(--fw-regular);
  color: var(--text-2); margin-left: var(--sp-sm); font-size: var(--fs-caption); }
.or-pick-row .or-pick-qty { flex: 0 0 auto; color: var(--text-2); font-size: var(--fs-caption); }
.or-pick-row.or-pick-out { opacity: 0.55; cursor: not-allowed; }
.or-pick-empty { padding: var(--sp-md); color: var(--text-2); font-size: var(--fs-caption); text-align: center; }
.or-picked { font-size: var(--fs-caption); color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.or-add-line { display: grid; grid-template-columns: 1fr 110px auto; gap: var(--sp-md); margin-top: var(--sp-md); align-items: center; }
.or-total { margin-top: var(--sp-md); padding: var(--sp-sm) var(--sp-md); background: var(--surface-2);
  border-radius: var(--radius-sm); font-size: var(--fs-body); color: var(--text-2); text-align: right; }
.or-total strong { color: var(--primary-text); font-size: var(--fs-title); }

/* ============ 【v2.9.80】库存实物照片 ============
   三处落点：① 列表缩略图（快速扫）② 新增/编辑弹窗的照片网格（录入）
   ③ 出库申请选中物品的照片（领料时确认拿对 —— 这是加照片的初衷）
   全部走 token 变量，深浅色自动跟随，不写死颜色。 */

/* ① 列表缩略图：固定小方块，多张时右下角挂 +N 角标 */
.stock-thumb-wrap { position: relative; display: inline-block; width: 40px; height: 40px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden;
  cursor: zoom-in; background: var(--surface-2); vertical-align: middle; }
.stock-thumb-wrap:hover { border-color: var(--primary); }
.stock-thumb { display: block; width: 100%; height: 100%; object-fit: cover; }
.stock-thumb-more { position: absolute; right: 0; bottom: 0; padding: 0 3px;
  background: var(--surface-inverse, rgba(0,0,0,.62)); color: var(--on-brand, #FFFFFF);
  font-size: var(--fs-mini); line-height: 12px; border-radius: var(--radius-sm) 0 0 0; }

/* ② 新增/编辑弹窗的照片网格：每项一个可删的方块 */
.stock-img-grid { display: flex; flex-wrap: wrap; gap: var(--sp-sm); margin-top: var(--sp-sm); }
.stock-img-grid:empty { display: none; }
.stock-img-cell { position: relative; width: 76px; height: 76px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); overflow: hidden; background: var(--surface-2); }
.stock-img-cell img { display: block; width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.stock-img-del { position: absolute; top: 2px; right: 2px; width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center; padding: 0;
  border: none; border-radius: var(--radius-full); cursor: pointer;
  background: var(--surface-inverse, rgba(0,0,0,.62)); color: var(--on-brand, #FFFFFF);
  font-size: 14px; line-height: 1; }
.stock-img-del:hover { background: var(--danger); }

/* ③ 出库申请：选中物品的照片条 */
.or-pick-img { display: flex; flex-wrap: wrap; gap: var(--sp-sm); align-items: center;
  margin-top: var(--sp-sm); font-size: var(--fs-caption); }
.or-pick-img:empty { display: none; }
.or-pick-thumb { width: 64px; height: 64px; object-fit: cover; cursor: zoom-in;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); }
.or-pick-thumb:hover { border-color: var(--primary); }

/* 大图预览：左右翻页 + 底部页码 */
.stock-imgview { display: flex; align-items: center; gap: var(--sp-md); justify-content: center; }
.stock-imgview img { max-width: 100%; max-height: 62vh; object-fit: contain;
  border-radius: var(--radius-md); background: var(--surface-2); }
.stock-imgview-nav { flex: 0 0 auto; width: 36px; height: 36px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text-2); border-radius: var(--radius-full);
  font-size: 22px; line-height: 1; cursor: pointer; }
.stock-imgview-nav:hover { color: var(--primary-text); border-color: var(--primary); }
.stock-imgview-foot { margin-top: var(--sp-md); text-align: center; font-size: var(--fs-caption); color: var(--text-2); }

/* 审核面板：状态筛选 tabs */
.or-tabs { display: flex; gap: var(--sp-xs); margin-bottom: var(--sp-lg); flex-wrap: wrap; }
.or-tab { padding: var(--sp-xs) var(--sp-md); border: 1px solid var(--border); background: var(--surface);
  color: var(--text-2); border-radius: var(--radius-full); font-size: var(--fs-caption); cursor: pointer;
  transition: all var(--dur-fast); }
.or-tab:hover { color: var(--primary-text); border-color: var(--primary); }
/* 【2026-10-18】激活态原为 `background: var(--primary)` = brand.deco #06B981 —— 该令牌自述
   「装饰档，禁止承载任何文字或图标」（白字仅 2.54:1）。改挂 --primary-strong #05835C：白字 4.76 ✅，
   与 .btn-primary / .tag-active 同口径（实心档承载白字，装饰档只做纯色块）。 */
.or-tab.active { background: var(--primary-strong); color: var(--on-brand, #FFFFFF); border-color: transparent; }
/* 明细展开区 */
.or-detail { margin-top: var(--sp-lg); }
.or-detail-head { display: flex; flex-wrap: wrap; gap: var(--sp-md); padding: var(--sp-md);
  background: var(--surface-2); border-radius: var(--radius-sm); font-size: var(--fs-caption); margin-bottom: var(--sp-sm); }
.or-detail-head strong { color: var(--text); font-size: var(--fs-body); }
/* 通过 / 驳回确认弹窗摘要 */
.or-act-summary { padding: var(--sp-md); background: var(--surface-2); border-radius: var(--radius-sm);
  font-size: var(--fs-body); margin-bottom: var(--sp-lg); }
.or-act-summary strong { color: var(--primary-text); }
.or-act-summary .form-hint { margin-top: var(--sp-sm); }

/* 表格：仅横向滚动 + 圆角（纵向滚动交给具体容器，避免弹窗内嵌套滚动条）。
   【P3】原 1080 行重复定义 `.table-wrap { max-height:70vh; overflow:auto }` 已删除：
   它强制所有 .table-wrap（含弹窗内）限高 70vh 并纵向滚动，是嵌套滚动条根因。 */
.table-wrap { overflow-x: auto; border-radius: var(--radius-sm); }
.table-wrap table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-2);
  box-shadow: inset 0 -1px 0 var(--border);
}
table { width: 100%; border-collapse: collapse; }
table thead th {
  background: var(--surface-2); color: var(--text-2);
  font-weight: var(--fw-semibold); font-size: var(--fs-caption); padding: var(--sp-md) var(--sp-lg);
  text-align: left; border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table tbody td {
  padding: var(--sp-md) var(--sp-lg); border-bottom: 1px solid var(--border-2);
  color: var(--text); font-size: var(--fs-body);
  word-break: keep-all;          /* 中文按词换行，避免逐字竖排 */
  overflow-wrap: anywhere;       /* 英文/长串可断行 */
}
table tbody td.cell-name,
table tbody td.cell-customer {
  min-width: 140px;
  max-width: 280px;
  white-space: normal;
}
table tbody td.cell-name { font-weight: var(--fw-semibold); }
table tbody td.cell-actions { white-space: nowrap; }
table tbody tr:hover { background: var(--surface-2); }
table tbody tr:last-child td { border-bottom: none; }
.font-600 { font-weight: var(--fw-semibold); }
.text-2 { color: var(--text-2); }
.text-3 { color: var(--text-3); }
.text-danger { color: var(--danger-text); }
.text-success { color: var(--success-text); }
.text-sm { font-size: var(--fs-caption); }
.empty { text-align: center; color: var(--text-3); padding: var(--sp-huge) 0 !important; }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-8 { gap: var(--sp-sm); }
.gap-12 { gap: var(--sp-md); }
.gap-16 { gap: var(--sp-lg); }
.mt-16 { margin-top: var(--sp-lg); }

/* 徽章 */
.badge {
  display: inline-block; padding: var(--sp-xs) var(--sp-md); border-radius: var(--radius-lg);
  font-size: var(--fs-mini); font-weight: var(--fw-medium); line-height: var(--lh-normal);
  white-space: nowrap;  /* 【UI测评P0】「已上架」等徽章在窄容器内折行 */
}
/* 【W1-9 保留】徽章文字色（#1E40AF/#92400E/#B91C1C/#475569/#6D28D9）是「浅底深字」的
   第三档语义（比 --*-text 更深一档），设计系统未定义 badge-text 令牌；底色已全部走 --tint-*。
   新增需同步 design-tokens，故保留原值。 */
.badge-blue { background: var(--tint-info-badge, #DBEAFE); color: #1E40AF; }
.badge-green { background: var(--primary-bg); color: var(--primary-text); }
.badge-orange { background: var(--tint-warning-badge, #FEF3C7); color: #92400E; }
.badge-red { background: var(--tint-danger-badge, #FEE2E2); color: #B91C1C; }
.badge-gray { background: var(--tint-neutral-badge, #F1F5F9); color: #475569; }
.badge-purple { background: var(--tint-purple-badge, #EDE9FE); color: #6D28D9; }
.badge-yellow { background: var(--tint-warning-badge, #FEF3C7); color: #92400E; }  /* 【UI检查】补缺失类：投标/采购「待审批/已开标/流标」状态徽章引用未定义（与 orange 同色系，保持视觉统一） */

/* ===== 报价模块移动端筛选（抽屉式，20260916）=====
   桌面端 .q-m-bar 隐藏；≤768px 或 force-mobile 时 .q-desktop-toolbar 隐藏、移动条接管。
   搜索框 + 状态 chips（唯一适合横滚的同质筛选）留在首屏，其余进底部抽屉。 */
.q-m-bar { display: none; }
.q-m-row { display: flex; gap: var(--sp-sm); align-items: center; }
.q-m-search { flex: 1; position: relative; }
.q-m-search .form-input { width: 100%; padding-left: var(--sp-huge); }
.q-m-filter-btn { flex-shrink: 0; white-space: nowrap; }
.q-m-badge {
  display: inline-block; min-width: 18px; height: 18px; line-height: 18px;
  padding: 0 var(--sp-xs); margin-left: var(--sp-xs); border-radius: var(--radius-sm);
  background: var(--danger); color: var(--on-brand, #FFFFFF); font-size: var(--fs-mini); font-weight: var(--fw-semibold);
  text-align: center; vertical-align: 1px;
}
.q-m-chips {
  display: flex; gap: var(--sp-sm); overflow-x: auto; padding: var(--sp-md) var(--sp-xxs) var(--sp-xs);
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.q-m-chips::-webkit-scrollbar { display: none; }
/* 【⑥-1 归一 · v2.4.68】原 .q-chip 自成一派（radius-xl 圆胶囊 + 1px 描边 + surface 底 + padding-x sp-lg），
   与 .fm-tab（radius-sm 方角填充 + surface-2 底 + padding-x sp-xl）**同语义却两套皮**。
   已并入下方 .fm-tab 规则组：共享同一条规则 = 改一处两端同步，而非各存一份拷贝。
   DOM 仍用 .q-chip（移动端报价筛选条 .q-m-chips），**零结构改动**。 */
.q-filter-drawer { align-items: flex-end; padding: 0; }  /* 底部弹层：贴底 */
.q-drawer {
  width: 100%; max-width: 92vw; max-height: 80vh; overflow-y: auto;
  border-radius: 16px 16px 0 0;
  animation: qDrawerUp var(--dur-base) var(--ease-standard);
}
@keyframes qDrawerUp { from { transform: translateY(24px); opacity: 0.6; } to { transform: translateY(0); opacity: 1; } }
@media (max-width: 768px) {
  .q-m-bar { display: block; }
  .q-desktop-toolbar { display: none; }
}
html.force-mobile .q-m-bar { display: block; }
html.force-mobile .q-desktop-toolbar { display: none; }

/* 模态框：不用 backdrop-filter（headless/Edge 都会模糊），用温和的深色半透明遮罩 */
/* 默认隐藏，只有 .show 才显示，避免模块渲染时弹窗全部弹出 */
.modal-mask {
  position: fixed; inset: 0; background: var(--scrim-modal);
  z-index: 1000;
  display: none; align-items: center; justify-content: center;
  padding: var(--sp-xxl);
}
.modal-mask.show {
  display: flex;
  animation: fadeIn var(--dur-base);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
.modal {
  background: var(--surface, #FFFFFF) !important; border-radius: var(--radius-xl);
  width: 520px; max-width: 92vw; max-height: 90vh; overflow-y: auto;
  box-shadow: var(--elev-ink-xxl); animation: slideUp var(--dur-base);
  display: flex; flex-direction: column;
  position: relative; z-index: 1;
}
.modal-lg { width: 720px; }
/* 弹窗内的次级分区（上方一条分隔线）：用于「在同一张单里做另一种动作」的区块，
   例如到账流水下方的「按银行流水核实」。禁区：不要在调用处写内联 style，③-4 闸会红。 */
.modal-sec { border-top: 1px solid var(--border-2); margin-top: var(--sp-lg); padding-top: var(--sp-md); }
/* 内容区（兼容没写 modal-body 的弹窗）：直接子元素排除 head/foot 后给内边距 */
.modal > .modal-head { padding: var(--sp-xl) var(--sp-xxl); border-bottom: 1px solid var(--border-2); flex-shrink: 0; }
.modal > .modal-foot { padding: var(--sp-lg) var(--sp-xxl); border-top: 1px solid var(--border-2); background: var(--surface-2); flex-shrink: 0; }
.modal > .modal-body { padding: var(--sp-xxl); flex: 1; overflow-y: auto; }
/* 关键兜底：如果弹窗没有 modal-body，所有非 head/foot 直接子元素都加内边距 */
.modal > *:not(.modal-head):not(.modal-foot):not(.modal-body):not([class*="modal-head"]):not([class*="modal-foot"]) {
  padding-left: var(--sp-xxl); padding-right: var(--sp-xxl);
}
.modal > *:not(.modal-head):not(.modal-foot):not(.modal-body):not([class*="modal-head"]):not([class*="modal-foot"]):first-of-type { padding-top: var(--sp-xxl); }
.modal > *:not(.modal-head):not(.modal-foot):not(.modal-body):not([class*="modal-head"]):not([class*="modal-foot"]):last-of-type:not(.modal-foot) { padding-bottom: var(--sp-xxl); }
/* 兜底规则覆盖到的元素如果是 input/button/hr/textarea，给它们正常 box-sizing 并 reset 边距 */
.modal > input, .modal > button, .modal > hr {
  display: block; width: auto; padding: 0; margin: 0; border: none; background: none;
}
.modal > input[type="hidden"] { display: none !important; }
.modal-head {
  padding: var(--sp-xl) var(--sp-xxl); border-bottom: 1px solid var(--border-2);
  display: flex; align-items: center; justify-content: space-between;
  flex-shrink: 0;
}
.modal-title { font-size: var(--fs-title); font-weight: var(--fw-semibold); }
.modal-close { background: none; border: none; cursor: pointer; font-size: var(--fs-display); color: var(--text-3); padding: 0; line-height: 1;
  /* 【三端UI评审 P0-3】桌面端原命中区仅字形大小（约 22×12），低于 WCAG 2.2 AA 的 24×24；补最小命中区，字形不变 */
  min-width: 32px; min-height: var(--ctl-web-md); display: inline-flex; align-items: center; justify-content: center; }
.modal-close:hover { color: var(--text); }
.modal-body { padding: var(--sp-xxl); overflow-y: auto; flex: 1; }
.modal-foot {
  padding: var(--sp-lg) var(--sp-xxl); border-top: 1px solid var(--border-2);
  display: flex; justify-content: flex-end; gap: var(--sp-md);
  background: var(--surface-2); flex-shrink: 0;
}

/* 个人资料弹窗专用样式 */
.profile-header {
  display: flex; align-items: center; gap: var(--sp-lg);
  padding-bottom: var(--sp-xl); border-bottom: 1px solid var(--border-2);
  margin-bottom: var(--sp-xl);
}
.profile-header .avatar-lg {
  width: 64px; height: 64px; border-radius: var(--radius-xl);
  background: var(--grad-brand); color: var(--on-brand, #FFFFFF);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-hero); font-weight: var(--fw-bold);
  box-shadow: var(--elev-brand-md);
  flex-shrink: 0;
}
.profile-header .profile-name { font-size: var(--fs-titleLg); font-weight: var(--fw-bold); color: var(--text); }
.profile-header .profile-role {
  font-size: var(--fs-caption); color: var(--primary-text); margin-top: var(--sp-xs);
  background: var(--primary-bg); padding: var(--sp-xxs) var(--sp-md); border-radius: var(--radius-md);
  display: inline-block;
}
.profile-info { margin-bottom: var(--sp-sm); }
.profile-info .info-row {
  display: flex; align-items: center; padding: var(--sp-md) 0;
  border-bottom: 1px solid var(--border-2); font-size: var(--fs-body);
}
.profile-info .info-row:last-child { border-bottom: none; }
.profile-info .info-label {
  width: 90px; color: var(--text-3); flex-shrink: 0;
}
.profile-info .info-value { color: var(--text); font-weight: var(--fw-medium); }
.profile-divider {
  font-size: var(--fs-body); font-weight: var(--fw-semibold); color: var(--text-2);
  margin: var(--sp-xl) 0 var(--sp-lg); padding-left: var(--sp-md);
  border-left: 3px solid var(--primary);
}

/* Toast */
/* 【v2.4.76 · 缺陷修复】原 `.toast` 既无 position 规则、又是 `<body>` 的最后一个子元素
   → 实际渲染在**视口之外**（实测 rect.y=900 / 视口高 900，`在视口内=false`），
   全站 520 处 toast() 调用**用户一条也看不到**（截图实证：触发后页面毫无变化）。
   同时 `.toast.show` 自始至终**没有任何 CSS 规则** → toast() 里那个「4 秒后 remove('show')」是空操作。
   处置：① 给 `.toast` 补浮动定位；② 用 opacity/visibility + .show 建立真正的显隐机制；
   ③ 原 `animation: slideUp` 会覆盖 opacity（动画优先级高于普通声明）→ 必须改成 transition。
   `.toast-area` 是同一套设计的容器形态但**从未被使用**，保留原样不动。 */
.toast-area { position: fixed; top: 24px; right: 24px; z-index: 2000; display: flex; flex-direction: column; gap: var(--sp-sm); }
.toast {
  position: fixed; top: 24px; right: 24px; z-index: 2000;
  padding: var(--sp-md) var(--sp-xl); border-radius: var(--radius-sm);
  background: var(--surface); box-shadow: var(--shadow-lg);
  border-left: 3px solid var(--primary);
  min-width: 240px; max-width: 380px;
  opacity: 0; visibility: hidden; transform: translateY(-8px); pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard), visibility var(--dur-base);
}
.toast.show { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
.toast.error { border-left-color: var(--danger); }
.toast.success { border-left-color: var(--success); }
/* 【v2.4.104】中性档：状态 / 进行中 / 无数据（如「暂无产品数据」「正在跳转…完成绑定」）。
   此前 toast 组件只有「是 success 与否」这一个二值出口 ⇒ 状态类提示被迫挂 .error，
   渲染成失败红条。色值取自真值源 --c-info-solid #2563EB（白字 5.17:1），非新增值。 */
.toast.info { border-left-color: var(--info, #2563EB); }

/* 视觉隐藏但屏幕阅读器可读（toast 的「成功/错误」语义用它承载，替代会被念出来的 ✓/✕） */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---- 跳到主内容（skip link）· v2.4.106 · 无障碍 A11Y ----
   WCAG 2.4.1 Bypass Blocks：为**键盘用户**提供绕过重复导航区块的机制。
   🔴 为什么需要它：<main> 地标只帮得到**读屏用户**（landmark 快速导航）；
   纯键盘用户没有捷径 —— 实测 Tab 序第 0–30 位全是侧栏 nav-item（31 项）。
   默认移出视口（对读屏与键盘仍可达），获得焦点时落回左上角。
   用 :focus 而非 :focus-visible —— 跳过链接是「被 Tab 到就该现身」的经典控件。 */
.skip-link {
  position: fixed; left: 8px; top: -64px; z-index: 9999;
  padding: var(--sp-sm) var(--sp-lg);
  background: var(--surface); color: var(--primary-text);
  border: 1px solid var(--primary); border-radius: var(--radius-md);
  box-shadow: var(--elev-ink-lg);
  font-size: var(--fs-body); font-weight: var(--fw-medium);
  text-decoration: none; white-space: nowrap;
  transition: top var(--dur-fast) var(--ease-standard);
}
.skip-link:focus { top: 8px; }

/* ===================================================
 * 响应式
 * =================================================== */
@media (max-width: 768px) {
  .login-brand { display: none; }
  .login-box { width: 100%; max-width: 420px; }
  .sidebar { transform: translateX(-100%); }
  .sidebar.open { transform: translateX(0); }
  .main { margin-left: 0; }
  .topbar .menu-toggle { display: inline-flex; }
  /* 【v2.4.92 修正】原来整块 `display:none` —— 面包屑接入后 <nav> 里装着**页面标题**，
     沿用会把移动端顶栏标题一起藏掉（标题只剩在 .content 里，位置感丢失）。
     改为只藏父级段与分隔符：移动端 = 原来的单标题外观，桌面端 = 分组 › 标题。 */
  .topbar .crumbs .crumb-parent,
  .topbar .crumbs .sep { display: none; }
  .content { padding: var(--sp-lg); }
  .form-row, .form-row-3 { grid-template-columns: 1fr; }
}

/* 滚动条
   【W1-9 保留】#D6E0EE / #B8C5D6 是浏览器原生 UI 专用灰（不属于品牌/语义色族），
   令牌化后仍要单独维护一份同值，反而多一层间接；故保留原值并在报告登记。 */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: #D6E0EE; border-radius: var(--radius-xxs); }
::-webkit-scrollbar-thumb:hover { background: #B8C5D6; }
::-webkit-scrollbar-track { background: transparent; }

/* ===================================================
 * 大厂精华组件：全局搜索 / 通知中心 / 欢迎横幅 / 快捷入口
 * =================================================== */

/* 顶部栏：搜索框居中 */
.topbar { position: sticky; top: 0; z-index: 90; }
.topbar-center { flex: 1; display: flex; justify-content: center; padding: 0 var(--sp-lg); }

.global-search { position: relative; width: 100%; max-width: 440px; }
.global-search .gs-ic { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); opacity: 0.5; font-size: var(--fs-bodyLg); }
.gs-input {
  width: 100%; height: var(--ctl-web-xl); padding: 0 44px 0 var(--sp-huge);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2); font-size: var(--fs-body); color: var(--text);
  transition: all var(--dur-base); outline: none;
}
.gs-input:focus { border-color: var(--primary); background: var(--surface, #FFFFFF); box-shadow: 0 0 0 3px var(--a-green-weak); }
.gs-kbd {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font-size: var(--fs-mini); color: var(--text-3); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-xxs); padding: var(--sp-xxs) var(--sp-sm);
}
.gs-dropdown {
  position: absolute; top: 46px; left: 0; width: 100%;
  background: var(--surface, #FFFFFF); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  border: 1px solid var(--border-2); max-height: 420px; overflow-y: auto; z-index: 200;
  padding: var(--sp-sm); animation: slideUp var(--dur-fast);
}
/* ── 全局搜索 · 类型筛选 chips（v2.4.103 · 批次 2.1「高级筛选」） ──
   命中类目通常 2–6 个，但跨类关键词可能上两位数。**不换行、横向滚动**：
   换行会把结果整体推下去，横滚则永远只占一行。
   sticky 吸顶：结果列表本身可滚（.gs-dropdown 有 max-height），
   滚到第 5 组时筛选条若已滚走，用户就得先滚回去才能换类目。 */
.gs-chips {
  display: flex; gap: var(--sp-xs); align-items: center;
  padding: var(--sp-xs) 0 var(--sp-sm);
  overflow-x: auto; scrollbar-width: none;
  position: sticky; top: calc(var(--sp-sm) * -1); z-index: 2;
  background: var(--surface, #FFFFFF);
}
.gs-chips::-webkit-scrollbar { display: none; }
.gs-chip {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  padding: 5px var(--sp-sm); border: 1px solid var(--border-2); border-radius: var(--radius-full);
  background: transparent; color: var(--text-2); font-family: inherit; font-size: var(--fs-mini);
  line-height: 1.4; cursor: pointer;
  transition: background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);
}
.gs-chip:hover { border-color: var(--primary); color: var(--primary-text); }
/* 选中态与 .btn-primary 同源（--grad-brand + --on-brand）：图标是 currentColor，自动跟随变白 */
.gs-chip.active { background: var(--grad-brand); border-color: transparent; color: var(--on-brand, #FFFFFF); }
.gs-chip b { font-weight: var(--fw-semibold); opacity: 0.85; margin-left: 1px; }
.gs-chip-ic { display: inline-flex; align-items: center; }
.gs-chip-ic svg { display: block; width: 12px; height: 12px; }
.gs-group-title { font-size: var(--fs-mini); color: var(--text-3); padding: var(--sp-sm) var(--sp-md) var(--sp-xs); font-weight: var(--fw-semibold); letter-spacing: 0.5px; }
.gs-item {
  display: flex; align-items: center; gap: var(--sp-md); padding: var(--sp-sm) var(--sp-md);
  border-radius: var(--radius-sm); cursor: pointer; transition: background var(--dur-fast);
}
.gs-item:hover { background: var(--primary-50); }
.gs-item-title { font-size: var(--fs-body); color: var(--text); font-weight: var(--fw-medium); flex-shrink: 0; max-width: 55%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-item-sub { font-size: var(--fs-caption); color: var(--text-3); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-item-extra { font-size: var(--fs-caption); color: var(--primary-text); font-weight: var(--fw-semibold); flex-shrink: 0; }
.gs-empty { padding: var(--sp-xxl); text-align: center; color: var(--text-3); font-size: var(--fs-body); }

/* 【v2.4.90】全局搜索：键盘高亮 / 命中高亮 / 分组信息 / 最近搜索。
   全部走既有令牌（含深色档覆盖），不引入任何硬编码色值。 */
.gs-item.active { background: var(--primary-50); box-shadow: inset 2px 0 0 var(--primary); }
.gs-item.active .gs-item-title { color: var(--primary-text); }
.gs-hl { background: transparent; color: var(--primary-text); font-weight: var(--fw-semibold); }
.gs-group-ic { display: inline-flex; align-items: center; vertical-align: -2px; margin-right: var(--sp-xs); opacity: 0.75; }
.gs-group-ic svg { display: block; }
.gs-group-more { float: right; color: var(--text-3); font-weight: var(--fw-regular); letter-spacing: 0; }
.gs-clear { float: right; border: 0; background: transparent; color: var(--text-3); font-size: var(--fs-mini); cursor: pointer; padding: 0 var(--sp-xs); font-family: inherit; }
.gs-clear:hover { color: var(--danger-text); }
.gs-foot { display: flex; justify-content: space-between; gap: var(--sp-md); padding: var(--sp-sm) var(--sp-md) var(--sp-xs); margin-top: var(--sp-xs); border-top: 1px solid var(--border-2); font-size: var(--fs-mini); color: var(--text-3); }
.gs-foot-key { white-space: nowrap; }
.gs-empty-hint { margin-top: var(--sp-sm); font-size: var(--fs-caption); color: var(--text-3); }
.gs-empty-hint b { color: var(--primary-text); font-weight: var(--fw-semibold); }
/* 移动端无物理键盘，隐藏快捷键提示 */
@media (max-width: 820px) { .gs-foot-key { display: none; } }

/* 移动端全局搜索：桌面端顶栏搜索框在 force-mobile 下被隐藏，故加独立搜索按钮 + 全屏搜索层 */
.mobile-search-btn { display: none; }
.mobile-search-layer { display: none; }

/* 通知中心 */
.notify-wrap { position: relative; }
.icon-btn { position: relative; width: 38px; height: var(--ctl-web-xl); padding: 0; font-size: var(--fs-title); }
.notify-badge {
  position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px;
  background: var(--danger); color: var(--on-brand, #FFFFFF); font-size: var(--fs-mini); font-weight: var(--fw-bold);
  border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;
  padding: 0 var(--sp-xs); border: 2px solid var(--on-brand, #FFFFFF);
}
.notify-dropdown {
  position: absolute; top: 44px; right: 0; width: 300px; max-height: 70vh; overflow-y: auto;
  background: var(--surface, #FFFFFF); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  border: 1px solid var(--border-2); z-index: 200; padding: var(--sp-sm); animation: slideUp var(--dur-fast);
}
/* 【v2.9.42】通知中心 tab（待办 / 通知）+ 通知项 */
.notify-tabs { display: flex; gap: var(--sp-xs); padding: 0 var(--sp-xs) var(--sp-sm); border-bottom: 1px solid var(--border-2); margin-bottom: var(--sp-xs); }
.notify-tab { flex: 1; padding: var(--sp-xs) 0; border: none; background: transparent; color: var(--text-3); font-size: var(--fs-body); font-weight: var(--fw-medium); cursor: pointer; border-radius: var(--radius-md); }
.notify-tab:hover { background: var(--primary-50); }
.notify-tab.active { color: var(--primary-text); background: var(--primary-50); font-weight: var(--fw-semibold); }
.notify-bar { padding: var(--sp-xs) var(--sp-sm); text-align: right; }
.notify-item { cursor: pointer; }
.notify-item.unread { box-shadow: inset 3px 0 0 var(--primary); }
.notify-item.unread .gs-item-title { font-weight: var(--fw-semibold); }

/* 【v3.4.21】通知直达待办：聚焦条 + 命中行高亮。
   作用：铃铛/通知点进来时，用户一眼看到"就是这条"，不用在长列表里自己找。
   颜色一律走语义变量，深色模式自动跟随，不写死色值。 */
.todo-focus-bar { background: var(--primary-50); border-left: 3px solid var(--primary); }
.todo-row-focus { background: var(--primary-50); }
.todo-row-focus > td { box-shadow: inset 0 0 0 9999px var(--primary-50); }

/* 提醒中心深链定位（v3.4.23）：#/reminders?bucket=x 落到对应档位卡片 */
.rm-focus-card { background: var(--primary-50); border-left: 3px solid var(--primary); }

/* 欢迎横幅 */
.welcome-banner {
  display: flex; align-items: center; justify-content: space-between;
  background: var(--grad-brand); color: var(--on-brand, #FFFFFF); border-radius: var(--radius-xl);
  padding: var(--sp-xxl) var(--sp-huge); margin-bottom: var(--sp-lg); position: relative; overflow: hidden;
}
.welcome-banner::after {
  content: ''; position: absolute; right: -40px; top: -40px; width: 200px; height: 200px;
  background: radial-gradient(circle, var(--a-white-mid) 0%, transparent 70%); border-radius: 50%;
}
.wb-greet { font-size: var(--fs-display); font-weight: var(--fw-bold); letter-spacing: 0.3px; display: flex; align-items: center; gap: var(--sp-sm); }
.wb-greet .wb-emoji { display: inline-flex; color: var(--tint-warning-badge); }
.wb-sub { font-size: var(--fs-body); opacity: 0.85; margin-top: var(--sp-xs); }
.wb-actions { position: relative; z-index: 1; }
.wb-actions .btn-primary { background: var(--surface, #FFFFFF); color: var(--brand-blue); font-weight: var(--fw-semibold); }

/* 工作台横幅内嵌搜索框 */
.wb-search {
  position: relative; z-index: 1; display: flex; align-items: center; gap: var(--sp-sm);
  background: var(--surface, #FFFFFF); border-radius: 24px; padding: var(--sp-sm) var(--sp-lg); width: 340px;
  box-shadow: var(--elev-ink-lg);
}
.wb-search .gs-ic { color: var(--text-3); font-size: var(--fs-bodyLg); }
.wb-search-input { border: none; outline: none; flex: 1; font-size: var(--fs-bodyLg); color: var(--text); background: transparent; }
.wb-search-input::placeholder { color: var(--text-3); }
.wb-search .gs-kbd {
  background: var(--surface-2); color: var(--text-3); font-size: var(--fs-mini);
  padding: var(--sp-xxs) var(--sp-sm); border-radius: var(--radius-xxs); border: 1px solid var(--border);
}
.wb-search-result {
  margin: -4px 0 var(--sp-lg); background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--elev-ink-xl); overflow: hidden;
  max-height: 420px; overflow-y: auto; padding: var(--sp-sm) 0;
}
.wb-search-result .gs-item { padding: var(--sp-md) var(--sp-lg); }
.wb-search-result .gs-item:hover { background: var(--primary-bg); }

@media (max-width: 768px) {
  .wb-search { width: 100%; }
}

/* 快捷入口 */
.quick-grid {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--sp-md); margin-bottom: var(--sp-lg);
}
.quick-card {
  background: var(--surface, #FFFFFF); border: 1px solid var(--border-2); border-radius: var(--radius);
  padding: var(--sp-lg) var(--sp-sm); text-align: center; cursor: pointer; transition: all var(--dur-base);
}
.quick-card:hover { transform: translateY(-2px); box-shadow: var(--elev-ink-md); border-color: var(--primary-200); }
.qc-ic {
  width: 44px; height: 44px; border-radius: var(--radius-lg);
  display: flex; align-items: center; justify-content: center;
  /* 【P1-2】底色由 dashboard.ts 的 QUICK_ENTRIES 内联注入（--c-tile-cyan/emerald/orange…），
     那一族全是**亮色**：白字实测 cyan 2.43 / emerald 2.54 / orange 2.15，连 3:1 都不到，
     等于瓷砖上的字糊成一块。深墨档全部 ≥3.6（大号字门槛 3:1）。
     判据同 --on-accent 注释：底恒亮 ⇒ 前景恒深墨。
     这条规则没有声明 background（底来自 JS），CSS 静态门禁算不到 —— 由
     tests/unit/tile-contrast.test.js 用「瓷砖色 × 前景」矩阵兜住。 */
  color: var(--on-accent, #0D1B2A); font-size: var(--fs-title); font-weight: var(--fw-semibold);
  margin: 0 auto var(--sp-sm);
  box-shadow: var(--elev-ink-md);
  letter-spacing: -0.5px;
}
.qc-name { font-size: var(--fs-body); color: var(--text-2); font-weight: var(--fw-medium); }

/* 待办提醒条
   【W1-9 保留】#FFFBEB / #FDE68A / #92400E 是「浅黄底 + 黄描边 + 深黄字」的提醒条专用三档，
   语义层只有 --tint-warning-badge(#FEF3C7) 与 --warning-text(#A8441F)，换过去会改变视觉层级；
   新增需走 design-tokens，故保留原值。 */
.pending-strip {
  display: flex; align-items: center; gap: var(--sp-md); flex-wrap: wrap;
  background: #FFFBEB; border: 1px solid #FDE68A; border-radius: var(--radius);
  padding: var(--sp-md) var(--sp-lg); margin-bottom: var(--sp-lg);
}
.pending-strip .ps-title { font-size: var(--fs-body); font-weight: var(--fw-semibold); color: #92400E; }

/* 常用功能 */
.quick-func { display: flex; flex-direction: column; }
.qf-item {
  display: flex; align-items: center; gap: var(--sp-md); padding: var(--sp-md) var(--sp-sm);
  border-bottom: 1px solid var(--border-2); cursor: pointer; transition: background var(--dur-fast);
}
.qf-item:last-child { border-bottom: none; }
.qf-item:hover { background: var(--primary-50); border-radius: var(--radius-sm); }
.qf-ic {
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--on-brand, #FFFFFF); font-size: var(--fs-body); font-weight: var(--fw-semibold);
  flex-shrink: 0;
}
.qf-item span:nth-child(2) { font-size: var(--fs-body); color: var(--text); flex: 1; }
.qf-arrow { color: var(--text-3); font-size: var(--fs-title); }

/* 回款缺口 Top 客户 */
.gap-list { display: flex; flex-direction: column; gap: var(--sp-lg); }
.gap-row { display: flex; flex-direction: column; gap: var(--sp-sm); }
.gap-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-md); }

/* 逾期标记（回款缺口卡） */
.overdue-tag {
  display: inline-block; margin-left: var(--sp-sm); padding: var(--sp-xxs) var(--sp-sm); font-size: var(--fs-mini); font-weight: var(--fw-semibold);
  color: var(--on-brand, #FFFFFF); background: var(--danger); border-radius: var(--radius-md); vertical-align: middle;
}

/* 应收账龄分析 */
.aging-row { display: flex; flex-direction: column; gap: var(--sp-sm); }
.aging-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-md); }

/* 页脚版本信息 */
.app-footer {
  padding: var(--sp-md) var(--sp-xxl); text-align: center; font-size: var(--fs-mini); color: var(--text-3);
  border-top: 1px solid var(--border-2); margin-top: auto;
}
.app-footer a { color: var(--primary-text); cursor: pointer; }

/* 版本信息卡片 */
.version-info { display: flex; align-items: center; gap: var(--sp-huge); flex-wrap: wrap; }
.vi-main { display: flex; align-items: baseline; gap: var(--sp-md); }
.vi-ver { font-size: var(--glyph-badge); font-weight: var(--fw-bold); background: var(--grad-brand); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.vi-name { font-size: var(--fs-bodyLg); color: var(--text-2); font-weight: var(--fw-medium); }
.vi-meta { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-md); flex: 1; }
.vi-item { display: flex; flex-direction: column; gap: var(--sp-xxs); padding: var(--sp-md) var(--sp-lg); background: var(--surface-2); border-radius: var(--radius-md); }
.vi-item span { font-size: var(--fs-mini); color: var(--text-3); }
.vi-item b { font-size: var(--fs-title); color: var(--text); }

/* 更新日志 */
.changelog-item { padding: var(--sp-lg) 0; border-bottom: 1px solid var(--border-2); }
.changelog-item:last-child { border-bottom: none; }
.cl-head { display: flex; align-items: center; gap: var(--sp-md); margin-bottom: var(--sp-sm); }
.cl-list { margin: 0; padding-left: var(--sp-xl); }
.cl-list li { font-size: var(--fs-body); color: var(--text-2); line-height: var(--lh-loose); }

@media (max-width: 768px) {
  .topbar-center { display: none; }
  .quick-grid { grid-template-columns: repeat(3, 1fr); }
  .welcome-banner { flex-direction: column; align-items: flex-start; gap: var(--sp-md); }
}
/* ===== 财务模块 ===== */
.fm-tabs { display: flex; gap: var(--sp-sm); flex-wrap: wrap; border-bottom: 2px solid var(--border); padding-bottom: var(--sp-md); }
/* 【⑥-1 归一 · v2.4.68】.q-chip 并入本组（原独立规则见上方注释处）。
   吸收 .q-chip 的 flex-shrink:0（等价 .fm-tab 的 flex:none）与 white-space:nowrap（移动端横滚必需）。 */
.fm-tab, .q-chip { flex: none; padding: var(--sp-sm) var(--sp-xl); border: none; background: var(--surface-2); color: var(--text-2); border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-body); font-weight: var(--fw-medium); font-family: inherit; transition: all var(--dur-fast); white-space: nowrap; }
.fm-tab:hover, .q-chip:hover { background: var(--primary-bg); color: var(--primary-text); }
.fm-tab.active, .q-chip.active { background: var(--grad-brand); color: var(--on-brand, #FFFFFF); font-weight: var(--fw-semibold); }
/* 【v3.4.6】页签内的二级视图切换（「采购付款」的 欠款台账 / 付款单据）。
   与 .fm-tabs 的区别：不留底部分隔线（它不是区块边界，只是同一块内容换个看法），
   且字号收一档 —— 视觉上必须明显弱于一级页签，否则会被误认成又多了一个模块。 */
.fm-tabs-sub { border-bottom: none; padding-bottom: 0; gap: var(--sp-xs); }
.fm-tabs-sub .fm-tab { font-size: var(--fs-caption); padding: var(--sp-xs) var(--sp-lg); }
/* 【v3.4.6】对账差异块 —— 背景 + 描边 + 内边距三件事绑成一个语义类，
   而不是写成行内 style：行内样式过不了 ③-4 门禁（且深色模式下会被静默吞掉）。 */
.fm-recon-warn { background: var(--warning-bg); border: 1px solid var(--warning-border); border-radius: var(--radius-sm); padding: var(--sp-md); }
/* 弹窗里的只读信息块（供应商 / 金额 / 日期那一格）。原先 inline background，同样过不了门禁。 */
.fm-info-block { background: var(--surface-2); border-radius: var(--radius-sm); padding: var(--sp-md); }

/* 财务工具类 */
.mb-16 { margin-bottom: var(--sp-lg); }
.mb-12 { margin-bottom: var(--sp-md); }
.gap-6 { gap: var(--sp-sm); }
.gap-32 { gap: var(--sp-huge); }

/* ===== OA 模块（钉钉级布局） ===== */
.oa-layout { display: flex; gap: var(--sp-xl); align-items: flex-start; }
.oa-side {
  width: 200px; flex-shrink: 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--sp-md) 0; position: sticky; top: 76px;
}
.oa-group {
  padding: var(--sp-md) var(--sp-lg) var(--sp-xs); font-size: var(--fs-caption); color: var(--text-3);
  font-weight: var(--fw-semibold); letter-spacing: 1px;
}
.oa-nav {
  display: flex; align-items: center; gap: var(--sp-sm);
  padding: var(--sp-md) var(--sp-lg); cursor: pointer; font-size: var(--fs-bodyLg);
  color: var(--text-2); transition: all var(--dur-fast); position: relative;
}
.oa-nav:hover { background: var(--surface-2); color: var(--text); }
.oa-nav.active { background: var(--primary-bg); color: var(--primary-text); font-weight: var(--fw-semibold); border-right: 3px solid var(--primary-500); }
.oa-nav .oa-dot {
  display: none; min-width: 18px; height: 18px; line-height: 18px; text-align: center;
  background: var(--danger); color: var(--on-brand, #FFFFFF); font-size: var(--fs-mini); font-weight: var(--fw-semibold);
  border-radius: var(--radius-sm); padding: 0 var(--sp-xs); margin-left: auto;
}
.oa-main { flex: 1; min-width: 0; }
@media (max-width: 768px) {
  .oa-layout { flex-direction: column; }
  .oa-side { width: 100%; position: static; display: flex; flex-wrap: wrap; gap: var(--sp-xs); padding: var(--sp-sm); }
  .oa-group { width: 100%; padding: var(--sp-sm) var(--sp-sm) var(--sp-xxs); }
  .oa-nav { padding: var(--sp-sm) var(--sp-md); border-radius: var(--radius-sm); }
  .oa-nav.active { border-right: none; }
}

/* ============ 权限管理 ============ */
.perm-layout { display: flex; gap: var(--sp-xl); align-items: flex-start; }
.perm-side {
  width: 240px; flex-shrink: 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--sp-md); position: sticky; top: 76px; max-height: calc(100vh - 96px); overflow-y: auto;
}
.perm-side-title { font-size: var(--fs-body); font-weight: var(--fw-semibold); color: var(--text-2); padding: var(--sp-xs) var(--sp-sm) var(--sp-md); }
.perm-user {
  padding: var(--sp-md) var(--sp-md); border-radius: var(--radius-sm); cursor: pointer; margin-bottom: var(--sp-xs);
  transition: all var(--dur-fast); border: 1px solid transparent;
}
.perm-user:hover { background: var(--surface-2); }
.perm-user.active { background: var(--primary-bg); border-color: var(--primary-300); }
.pu-name { font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); color: var(--text); display: flex; align-items: center; gap: var(--sp-sm); }
.pu-role { font-size: var(--fs-caption); color: var(--text-3); margin-top: var(--sp-xxs); }
.perm-main { flex: 1; min-width: 0; }
.perm-tip {
  background: var(--primary-bg); color: var(--primary-text); font-size: var(--fs-body);
  padding: var(--sp-md) var(--sp-lg); border-radius: var(--radius-sm); margin: var(--sp-lg) 0; line-height: var(--lh-relaxed);
}
.perm-group { margin-bottom: var(--sp-lg); }
.perm-mod { padding: var(--sp-sm) 0; border-bottom: 1px solid var(--border-2); }
.perm-mod:last-child { border-bottom: none; }
.perm-mod-head { display: flex; align-items: center; gap: var(--sp-sm); font-size: var(--fs-bodyLg); font-weight: var(--fw-medium); color: var(--text); cursor: pointer; }
.perm-mod-head input { width: 16px; height: 16px; cursor: pointer; }
.perm-funcs { display: flex; flex-wrap: wrap; gap: var(--sp-sm); margin: var(--sp-sm) 0 var(--sp-xs) var(--sp-xxl); }
.perm-func {
  display: inline-flex; align-items: center; gap: var(--sp-xs); font-size: var(--fs-body);
  color: var(--text-2); padding: var(--sp-xs) var(--sp-md); background: var(--surface-2);
  border-radius: var(--radius-xs); cursor: pointer; transition: all var(--dur-fast);
}
.perm-func input { width: 14px; height: 14px; cursor: pointer; }
.perm-func.dim { opacity: 0.4; cursor: not-allowed; }
@media (max-width: 768px) {
  .perm-layout { flex-direction: column; }
  .perm-side { width: 100%; position: static; max-height: none; }
}

/* ============ 快捷新建（独立下拉框） ============ */
#qc-dropdown { min-width: 240px; max-height: 60vh; overflow-y: auto; padding: var(--sp-sm) 0; }
#qc-dropdown .qc-search { padding: var(--sp-xs) var(--sp-md) var(--sp-sm); }
#qc-dropdown .qc-search .form-input { padding: var(--sp-sm) var(--sp-md); font-size: var(--fs-body); height: var(--ctl-web-md); }
#qc-dropdown .qc-group-label {
  font-size: var(--fs-mini); font-weight: var(--fw-semibold); color: var(--text-3);
  padding: var(--sp-sm) var(--sp-lg) var(--sp-xs); letter-spacing: 0.5px;
}
#qc-dropdown .gs-item { padding: var(--sp-sm) var(--sp-lg); }
#qc-dropdown .gs-item:hover { background: var(--primary-bg); color: var(--primary-text); }

/* ============ 报价管理模块样式 ============ */
.modal-xl { width: 1100px; max-width: 96vw; }
.modal-2xl { width: 1280px; max-width: 96vw; }

/* 报价数据看板 */
.quote-kpi { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-lg); margin-bottom: var(--sp-xl); }
.quote-kpi-card {
  background: linear-gradient(135deg, var(--surface, #FFFFFF) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border-2); border-radius: var(--radius-lg);
  padding: var(--sp-xl) var(--sp-xl); position: relative; overflow: hidden;
  transition: all var(--dur-base);
}
.quote-kpi-card:hover { transform: translateY(-2px); box-shadow: var(--elev-ink-md); }
.quote-kpi-card::before {
  content: ''; position: absolute; top: 0; right: 0; width: 64px; height: 64px;
  background: var(--grad-brand); opacity: 0.06; border-radius: 0 0 0 64px;
}
.quote-kpi-label { font-size: var(--fs-body); color: var(--text-3); margin-bottom: var(--sp-sm); }
.quote-kpi-val { font-size: var(--fs-hero); font-weight: var(--fw-bold); color: var(--text); line-height: var(--lh-tight); }
.quote-kpi-sub { font-size: var(--fs-caption); color: var(--text-3); margin-top: var(--sp-sm); }
.quote-profit { color: var(--primary-text); }

/* 报价弹窗分组 */
.qm-section { background: var(--surface-2); border-radius: var(--radius-md); padding: var(--sp-lg) var(--sp-xl); margin-bottom: var(--sp-lg); }
.qm-section-title {
  font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); color: var(--text); margin-bottom: var(--sp-lg);
  padding-left: var(--sp-md); border-left: 3px solid var(--primary);
}

/* 报价明细表 */
.qm-items-table { font-size: var(--fs-body); }
.qm-items-table th { background: var(--surface-2); font-weight: var(--fw-semibold); }
.qm-items-table td { padding: var(--sp-sm) var(--sp-sm); vertical-align: middle; }
.qm-items-table .form-input { padding: var(--sp-sm) var(--sp-sm); font-size: var(--fs-body); height: var(--ctl-web-md); }
.qm-items-table .qm-idx { color: var(--text-3); text-align: center; font-size: var(--fs-caption); }

/* 报价合计
   【v3.4.75 · P0】原规则只设 background 未设 color，深色档下文字继承 --text(#E6EBF3 近白)
   压在这块硬编码浅灰渐变上 ⇒ 对比度 1.06~1.14:1，等于看不见（三端体检 UI 组 P0）。
   两档都显式钉住 color —— 既修复缺陷，也让 css-contrast-rules 的「同规则内底+字」判据能覆盖到。 */
.qm-summary {
  /* 【W1-9 保留】报价合计卡衬底：双灰阶渐变，语义层无「表面浅灰」令牌（--surface-2 单色，换去会丢层次） */
  background: linear-gradient(135deg, #f8fafc 0%, #eef2f7 100%);
  color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius-md);
  padding: var(--sp-lg) var(--sp-xl); margin-top: var(--sp-lg);
  display: flex; flex-direction: column; gap: var(--sp-sm);
}
.qm-summary-row { display: flex; justify-content: space-between; align-items: center; font-size: var(--fs-body); }
.qm-summary-total { border-top: 1px solid var(--border-2); padding-top: var(--sp-md); margin-top: var(--sp-xs); font-size: var(--fs-title); }
.qm-summary-total .font-600 { color: var(--primary-text); font-size: var(--fs-display); }

/* 模板卡片 */
.qm-tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--sp-lg); }
.qm-tpl-card {
  border: 1px solid var(--border-2); border-radius: var(--radius-md); padding: var(--sp-lg) var(--sp-lg);
  background: var(--surface, #FFFFFF); transition: all var(--dur-base);
}
.qm-tpl-card:hover { border-color: var(--primary); box-shadow: var(--elev-brand-md); }
.qm-tpl-name { font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); margin-bottom: var(--sp-sm); color: var(--text); }

/* ===== 【Task#305】报价弹窗：填写报价 / 预览报单价 页签 ===== */
.qm-tabs {
  display: flex; align-items: center; gap: var(--sp-xs);
  padding: 0 var(--sp-xxl); border-bottom: 1px solid var(--border-2);
  background: var(--surface-2);
}
.qm-tab {
  position: relative;
  padding: var(--sp-md) var(--sp-xl); border: none; background: transparent;
  font-size: var(--fs-bodyLg); font-weight: var(--fw-medium); color: var(--text-3); cursor: pointer;
  border-bottom: 2px solid transparent; transition: color var(--dur-fast), border-color var(--dur-fast);
}
.qm-tab:hover { color: var(--text); }
.qm-tab.active { color: var(--primary-text); font-weight: var(--fw-semibold); border-bottom-color: var(--primary); }
.qm-tab-hint { margin-left: auto; font-size: var(--fs-caption); color: var(--text-3); }

/* 预览面板 */
.qm-preview-pane { padding: 0 !important; }
.qm-preview-toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-xxl); border-bottom: 1px solid var(--border-2);
  background: var(--surface, #FFFFFF); position: sticky; top: 0; z-index: 3;
}
.qm-preview-tip { font-size: var(--fs-caption); color: var(--text-3); }
.qm-preview-zoom { display: inline-flex; align-items: center; gap: var(--sp-sm); font-size: var(--fs-caption); color: var(--text-3); }
.qm-preview-zoom .btn { min-width: 30px; padding: var(--sp-xxs) var(--sp-sm); }

/* A4 纸张舞台：灰底衬托白纸 */
.qm-paper-stage {
  /* 无对应令牌：A4 舞台衬底灰（比 --bg 深一档，专为衬托白纸），语义层无此档 */
  padding: var(--sp-xl); background: #eef1f5;
  max-height: 66vh; overflow: auto;
  display: flex; justify-content: center; align-items: flex-start;
}
.qm-paper-frame { position: relative; overflow: hidden; box-shadow: var(--elev-ink-lg); background: var(--surface); }
.qm-paper-iframe { border: 0; display: block; background: var(--surface); }

/* 暗色主题适配（页面主题，不影响 iframe 内 A4 内容） */
[data-theme="dark"] .qm-tabs { background: var(--surface-2); }
[data-theme="dark"] .qm-preview-toolbar { background: var(--surface); }
[data-theme="dark"] .qm-paper-stage { background: #12161c; }

/* 对比表 */
.cmp-table th, .cmp-table td { padding: var(--sp-md) var(--sp-md); }
.cmp-table th { background: var(--surface-2); }
.cmp-head { border: 1px solid var(--border-2); border-radius: var(--radius-md); overflow: hidden; margin-bottom: var(--sp-lg); }

/* 规格列宽（在产品库选品弹窗中使用） */
.cell-spec { max-width: 240px; word-break: break-all; line-height: var(--lh-normal); }

/* 版本历史 */
.quote-ver-row { padding: var(--sp-md) 0; border-bottom: 1px solid var(--border-2); }
.quote-ver-row:last-child { border-bottom: none; }

/* 分页 */
.pager { display: inline-flex; align-items: center; gap: var(--sp-xs); }
.pager button:disabled { opacity: 0.4; cursor: not-allowed; }

/* 工具栏按钮内的 svg 对齐 */
.toolbar .btn svg { flex-shrink: 0; }
.toolbar .btn { display: inline-flex; align-items: center; }

@media (max-width: 1100px) {
  .quote-kpi { grid-template-columns: repeat(2, 1fr); }
}


/* ============ OA 规则配置 ============ */
.rule-tabs {
  display: flex; gap: var(--sp-sm); flex-wrap: wrap;
  margin-bottom: var(--sp-lg); border-bottom: 1px solid var(--border); padding-bottom: var(--sp-md);
}
.rule-tab {
  padding: var(--sp-sm) var(--sp-xl); border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-bodyLg);
  color: var(--text-2); background: var(--surface-2); transition: all var(--dur-fast);
}
.rule-tab:hover { color: var(--text); }
.rule-tab.active { background: var(--grad-brand); color: var(--on-brand, #FFFFFF); font-weight: var(--fw-semibold); }  /* 【UI测评P1】原 solid primary-500，与 fm-tab/calc-nav 激活态统一为品牌渐变 */
.rule-panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-xl); }
.rule-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-lg) var(--sp-xxl);
}
.rule-row {
  display: flex; align-items: center; gap: var(--sp-md);
}
.rule-label {
  min-width: 150px; font-size: var(--fs-bodyLg); color: var(--text-2); flex-shrink: 0;
}
.rule-check { width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-500); }
@media (max-width: 768px) {
  .rule-grid { grid-template-columns: 1fr; }
  .rule-row { flex-direction: column; align-items: flex-start; gap: var(--sp-sm); }
  .rule-label { min-width: auto; }
}

/* ============ 实验室计算中心 ============ */
.calc-layout {
  display: flex; gap: var(--sp-xl); align-items: flex-start;
}
.calc-side {
  width: 200px; flex-shrink: 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-md);
  position: sticky; top: 16px;
}
.calc-group-title {
  font-size: var(--fs-caption); color: var(--text-3); font-weight: var(--fw-semibold);
  padding: var(--sp-md) var(--sp-sm) var(--sp-sm); letter-spacing: .5px;
}
.calc-nav {
  display: flex; align-items: center; gap: var(--sp-md); padding: var(--sp-md) var(--sp-md);
  border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-bodyLg); color: var(--text-2);
  transition: all var(--dur-fast);
}
.calc-nav .ic { display: flex; align-items: center; }
.calc-nav:hover { background: var(--surface-2); color: var(--text); }
.calc-nav.active {
  background: var(--grad-brand);
  color: var(--on-brand, #FFFFFF); font-weight: var(--fw-semibold); box-shadow: var(--elev-brand-md);
}
.calc-main { flex: 1; min-width: 0; }

.calc-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--sp-xxl);
}
.calc-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: var(--sp-lg);
}
.calc-head h3 { font-size: var(--fs-titleLg); font-weight: var(--fw-bold); color: var(--text); }

.calc-tabs-mini {
  display: flex; gap: var(--sp-sm); flex-wrap: wrap; margin-bottom: var(--sp-xl);
  border-bottom: 1px solid var(--border-2); padding-bottom: var(--sp-lg);
}
.ctm {
  padding: var(--sp-sm) var(--sp-lg); border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-body);
  color: var(--text-2); background: var(--surface-2); border: 1px solid var(--border);
  transition: all var(--dur-fast);
}
.ctm:hover { color: var(--text); border-color: var(--primary-strong); }
.ctm.active {
  background: var(--primary-strong); color: var(--on-brand, #FFFFFF); font-weight: var(--fw-semibold); border-color: var(--primary-strong);
}

.calc-unit-hint { color: var(--text-3); font-weight: var(--fw-regular); font-size: var(--fs-caption); }

.calc-results {
  display: flex; flex-wrap: wrap; gap: var(--sp-lg); margin-top: var(--sp-xl);
}
.calc-result {
  flex: 1; min-width: 200px; background: var(--surface-2);
  border: 1px solid var(--border-2); border-radius: var(--radius-md); padding: var(--sp-lg);
}
.calc-result.hl {
  background: linear-gradient(135deg, var(--primary-50), var(--surface));
  border-color: var(--primary-200);
}
.cr-label { font-size: var(--fs-caption); color: var(--text-3); margin-bottom: var(--sp-sm); }
.cr-value { font-size: var(--fs-hero); font-weight: var(--fw-bold); color: var(--text); line-height: var(--lh-tight); }
.cr-value .cr-unit { font-size: var(--fs-body); color: var(--text-3); font-weight: var(--fw-medium); margin-left: var(--sp-xs); }
.cr-note { font-size: var(--fs-caption); color: var(--text-3); margin-top: var(--sp-sm); line-height: var(--lh-normal); }

.calc-table-wrap { margin-top: var(--sp-xl); overflow-x: auto; }
.calc-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
.calc-table th {
  background: var(--surface-2); text-align: left; padding: var(--sp-md) var(--sp-md);
  color: var(--text-2); font-weight: var(--fw-semibold); border-bottom: 1px solid var(--border);
}
.calc-table td { padding: var(--sp-md) var(--sp-md); border-bottom: 1px solid var(--border-2); }

.calc-select-list { display: flex; flex-direction: column; gap: var(--sp-sm); margin-bottom: var(--sp-lg); }
.csl-item {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-md) var(--sp-lg); border-radius: var(--radius-md); border: 1px solid var(--border);
  background: var(--surface); cursor: pointer; font-size: var(--fs-bodyLg); color: var(--text);
  transition: all var(--dur-fast);
}
.csl-item:hover { border-color: var(--primary-strong); background: var(--primary-50); }

.convert-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-lg);
}
.convert-box {
  border: 1px solid var(--border-2); border-radius: var(--radius-md); padding: var(--sp-lg);
  background: var(--surface-2);
}
.cb-title { font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); color: var(--text); margin-bottom: var(--sp-md); }
.cb-row { display: flex; flex-wrap: wrap; gap: var(--sp-sm); margin: var(--sp-md) 0; }

@media (max-width: 900px) {
  .calc-layout { flex-direction: column; }
  .calc-side { width: 100%; position: static; display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
  .calc-group-title { width: 100%; }
  .convert-grid { grid-template-columns: 1fr; }
}


/* ============ 合作门户管理 ============ */
.form-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-md); margin-bottom: var(--sp-md); }
@media (max-width:640px){ .form-row-2{ grid-template-columns:1fr; } }
.code-chip { background: var(--tint-code-badge, #F0F4FF); color: #3b5bdb; padding: var(--sp-xxs) var(--sp-sm); border-radius: var(--radius-xs); font-size: var(--fs-caption); font-family: Consolas,monospace; border:1px solid #dbe4ff; }  /* 【W1-9 保留】代码片段专用靛蓝字/边，无同族令牌 */
/* 【P3】.modal-footer 与 .modal-foot 是历史遗留的重复类（同一语义：弹窗底部按钮栏）。
   全站仅 module_partner.js 使用 .modal-footer。此处让它复用 .modal-foot 的完整样式，
   避免两套定义视觉不一致；后续新代码统一用 .modal-foot。 */
.modal-footer { padding: var(--sp-lg) var(--sp-xxl); border-top: 1px solid var(--border-2); display: flex; justify-content: flex-end; gap: var(--sp-md); background: var(--surface-2); flex-shrink: 0; }
#pm-bound-list .pp-item:last-child { border-bottom: none !important; }

/* ===================================================
 * 无障碍与交互细节（统一焦点态 / 二次确认弹层）
 * =================================================== */
:root {
  --focus-ring: var(--a-green-strong);
}

/* 统一键盘焦点可见（WCAG AA：可聚焦元素需有清晰焦点指示） */
.btn:focus-visible,
.form-input:focus-visible,
.form-select:focus-visible,
.form-textarea:focus-visible,
.nav-item:focus-visible,
.qf-item:focus-visible,
.quick-card:focus-visible,
.oa-nav:focus-visible,
.calc-nav:focus-visible,
.gs-item:focus-visible,
a.crumb-parent:focus-visible {
  outline: 2px solid var(--primary-text);
  outline-offset: 2px;
}
/* 图标按钮（无文本）焦点环收紧，避免突兀 */
.icon-btn:focus-visible,
.modal-close:focus-visible {
  outline: 2px solid var(--primary-text);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}
.menu-toggle:focus-visible { outline: 2px solid var(--primary-text); outline-offset: 2px; border-radius: var(--radius-xs); }

/* 尊重用户「减少动态效果」偏好 */
@media (prefers-reduced-motion: reduce) {
  /* 【CSP 行内属性清零 2026-10-05】选择器由 `*, *::before, *::after` 改为 `html:root …` 前缀。
     覆盖面**完全不变**（html 自身 + 全部后代 + 后代的两类伪元素），只是把特异性从
     (0,0,0)/(0,0,1) 抬到 (0,1,1)/(0,1,2)。
     🔴 为什么非抬不可：这三条带 !important，原状压得过行内 style（行内是普通作者声明）；
     清零行内后，对手变成 `.u-transition-opacity-15s { transition: … !important }`（(0,1,0)）——
     二者**属性名不同、却落在同一个长属性 transition-duration 上**，`*` 会输，
     于是"尊重系统减弱动效"在那处失效。单纯把 attr-utils.css 提前（定序）救不了这一处：
     特异性已经不同，平局规则用不上。 */
  html:root, html:root *, html:root *::before, html:root *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===================================================
 * 移动端（PWA）适配增强 —— 全功能移动办公
 * =================================================== */

/* 侧边栏抽屉遮罩（仅移动端显示） */
.sidebar-mask {
  display: none;
  position: fixed; inset: 0;
  background: var(--scrim-sidebar);
  z-index: 95; /* 低于侧边栏 z-index:100，高于内容 */
}

/* 移动端：让点击目标更大、间距更舒适 */
@media (max-width: 768px) {
  /* 遮罩显示 */
  .sidebar-mask.show { display: block; animation: fadeIn var(--dur-base); }

  /* 侧边栏抽屉：加阴影，滑入更顺滑 */
  .sidebar { box-shadow: var(--elev-ink-side); }

  /* 内容区：收紧边距 + 预留底部安全区（全面屏手势条） */
  .content {
    padding: var(--sp-lg) var(--sp-lg) calc(var(--sp-lg) + env(safe-area-inset-bottom));
  }
  .topbar {
    padding: 0 var(--sp-md);
    gap: var(--sp-sm);
  }
  .topbar .topbar-center { padding: 0 var(--sp-sm); }

  /* 顶栏精简：移动端隐藏全局搜索框（避免与标题/按钮挤作一团），
     搜索能力由各模块列表页自带搜索框承担 */
  .topbar-center { display: none; }

  /* 隐藏用户名文字，仅保留头像（节省横向空间） */
  .topbar .user > div:last-child { display: none; }
  .topbar .user { padding: var(--sp-xs); }

  /* 退出按钮文字保留（触控目标足够） */

  /* 触控目标 ≥48px（对齐 Material 48dp；同时满足 WCAG 2.5.5 AAA ≥44）—— 值取 --ctl-touch */
  .btn { min-height: var(--ctl-inline); padding: var(--sp-sm) var(--sp-lg); }
  .btn-sm { min-height: var(--ctl-web-lg); }
  .icon-btn, .menu-toggle { width: var(--ctl-touch); height: var(--ctl-touch); }
  .nav-item { min-height: var(--ctl-secondary); max-height: var(--ctl-secondary); padding: var(--sp-md) var(--sp-lg); }
  .nav-group-title { padding: var(--sp-lg) var(--sp-md) var(--sp-sm); }
  .modal-close { width: var(--ctl-touch); height: var(--ctl-touch); font-size: var(--glyph-arrow); }

  /* 表单控件：移动端放大到 16px 防止 iOS 聚焦缩放，高度 ≥44px */
  .form-input, .form-select { height: var(--ctl-secondary); font-size: var(--fs-title); }
  .form-textarea { font-size: var(--fs-title); }
  .form-label { font-size: var(--fs-body); }

  /* 弹窗：移动端改为近全屏 + 底部上滑，替代居中卡片，
     便于在手机上操作报价/合同等大表单 */
  .modal-mask { padding: 0; align-items: flex-end; }
  .modal {
    width: 100%; max-width: 100%;
    max-height: 92vh;
    border-radius: 16px 16px 0 0;
    animation: slideUp var(--dur-base);
  }
  .modal-lg { width: 100%; max-width: 100%; }
  /* 小弹窗（确认/提示框）：保持居中卡片，不底部抽屉化 */
  .modal-center {
    align-self: center;
    border-radius: var(--radius-xl);
    width: 92vw !important;
    max-width: 400px !important;
    max-height: 80vh;
  }
  .modal > .modal-head { padding: var(--sp-lg) var(--sp-lg); }
  .modal > .modal-body { padding: var(--sp-lg); }
  .modal > .modal-foot {
    padding: var(--sp-md) var(--sp-lg) calc(var(--sp-md) + env(safe-area-inset-bottom));
    flex-wrap: wrap;
  }
  .modal > .modal-foot .btn { flex: 1 1 auto; min-height: var(--ctl-secondary); }

  /* 工具栏：移动端按钮换行、搜索框占满整行 */
  .toolbar { gap: var(--sp-sm); }
  .toolbar .search-box { min-width: 100%; max-width: 100%; flex-basis: 100%; }
  .toolbar .btn { flex: 1 1 auto; }

  /* 表格：移动端横滑时给出「可左右滑动」的视觉暗示（右侧渐隐） */
  .table-wrap {
    -webkit-overflow-scrolling: touch;
    position: relative;
  }
  .table-wrap::after {
    content: '';
    position: sticky; right: 0; top: 0;
    display: block; width: 0; height: 0;
  }
  table { font-size: var(--fs-caption); }
  table thead th, table tbody td { padding: var(--sp-md) var(--sp-md); }

  /* 卡片：移动端内边距收紧 */
  .card { padding: var(--sp-lg); }

  /* 统计卡片：单列（已有 auto-fill minmax 220px，这里显式收紧到双列更省空间） */
  .stat-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-md); }
  .stat-card { padding: var(--sp-md) var(--sp-lg); }

  /* Toast 提示：移动端顶部居中，避免右下角被手势区遮挡 */
  /* 【v2.4.76】`.toast` 自身已是 fixed 定位 → 移动端规则必须同时作用到它（否则仍停在右上角）；
     left/right 已确定宽度，故去掉 width:100%（会与 left+right 冲突而溢出） */
  .toast-area, .toast { top: auto; bottom: calc(16px + env(safe-area-inset-bottom)); right: 16px; left: 16px; }
  .toast { min-width: 0; max-width: none; width: auto; }

  /* 欢迎横幅：移动端纵向堆叠 */
  .welcome-banner { flex-direction: column; align-items: flex-start; gap: var(--sp-lg); padding: var(--sp-xl); }
  .wb-greet { font-size: var(--fs-titleLg); }
  .wb-actions { width: 100%; }
  .wb-actions .btn-primary { width: 100%; }

  /* 分页：移动端按钮适当放大 */
  .pager button { min-height: var(--ctl-web-lg); padding: var(--sp-sm) var(--sp-md); }
}

/* 超小屏（<400px）：进一步收紧 */
@media (max-width: 400px) {
  .stat-grid { grid-template-columns: 1fr; }
  .page-title { font-size: var(--fs-titleLg); }
}


/* 二次确认弹层图标 */
.ui-confirm-ic {
  width: 26px; height: 26px; border-radius: var(--radius-sm); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-bodyLg); font-weight: var(--fw-bold); line-height: 1;
}

/* ===================================================
 * 模块页面骨架模板（新模块直接套用，保证全站一致）
 * 标准结构：toolbar → tabs → card(table) → 分页
 * =================================================== */
.module-page { display: flex; flex-direction: column; gap: var(--sp-lg); }
.module-toolbar {
  display: flex; align-items: center; gap: var(--sp-md); flex-wrap: wrap;
}
.module-toolbar .spacer { flex: 1; }
/* 让 .module-toolbar 与 .toolbar 完全等价：子元素（搜索框/图标/按钮）沿用同一套样式 */
.module-toolbar .search-box { position: relative; }
.module-toolbar .search-box .form-input { padding-left: var(--sp-huge); }
.module-toolbar .search-box .search-ic { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-3); pointer-events: none; }
.module-toolbar .btn { display: inline-flex; align-items: center; }
.module-toolbar .btn svg { flex-shrink: 0; }
.module-tabs {
  display: flex; gap: var(--sp-sm); flex-wrap: wrap;
  border-bottom: 2px solid var(--border); padding-bottom: var(--sp-md);
}
.module-tab {
  padding: var(--sp-sm) var(--sp-xl); border: none; background: var(--surface-2);
  color: var(--text-2); border-radius: var(--radius-sm); cursor: pointer;
  font-size: var(--fs-body); font-weight: var(--fw-medium); font-family: inherit; transition: all var(--dur-fast);
}
.module-tab:hover { background: var(--primary-bg); color: var(--primary-text); }
.module-tab.active { background: var(--grad-brand); color: var(--on-brand, #FFFFFF); font-weight: var(--fw-semibold); }
.module-card {
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--shadow); border: 1px solid var(--border-2);
  overflow: hidden;
}
.module-card-title {
  font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); color: var(--text);
  padding: var(--sp-xl) var(--sp-xl) 0;
}
.module-pager {
  display: flex; align-items: center; justify-content: flex-end;
  gap: var(--sp-md); padding: var(--sp-lg) var(--sp-xl); border-top: 1px solid var(--border-2);
  font-size: var(--fs-body); color: var(--text-2);
}
.module-pager .btn { height: var(--ctl-web-sm); }

/* ===================================================
 * 【P0 移动端兜底】force-mobile：JS 检测到设备物理宽度为 mobile
 * 但 WebView 渲染宽度被强制拉大（Capacitor WebView/微信 X5/部分国产浏览器
 * 会忽略 width=device-width viewport meta）时，给 <html> 加 force-mobile class。
 * 这里强制按 mobile 渲染，优先级高于普通 desktop 规则。
 * =================================================== */
.force-mobile .sidebar { transform: translateX(-100%); }
.force-mobile .sidebar.open { transform: translateX(0); }
.force-mobile .sidebar { box-shadow: var(--elev-ink-side); }
.force-mobile .main { margin-left: 0; }
.force-mobile .sidebar-mask.show { display: block; animation: fadeIn var(--dur-base); }
.force-mobile .topbar .menu-toggle { display: inline-flex !important; }
/* 同上（v2.4.92）：只藏父级段，保留页面标题 */
.force-mobile .topbar .crumbs .crumb-parent,
.force-mobile .topbar .crumbs .sep { display: none; }
.force-mobile .content { padding: var(--sp-lg); }
.force-mobile .form-row, .force-mobile .form-row-3 { grid-template-columns: 1fr; }
.force-mobile .stat-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-md); }
.force-mobile .stat-card { padding: var(--sp-md) var(--sp-lg); }
.force-mobile .login-brand { display: none; }
.force-mobile .login-box { width: 100%; max-width: 420px; }
.force-mobile .nav-item { min-height: var(--ctl-secondary); max-height: var(--ctl-secondary); padding: var(--sp-md) var(--sp-lg); }
.force-mobile .modal-mask { padding: 0; align-items: flex-end; }
.force-mobile .modal { width: 100% !important; max-width: 100% !important; max-height: 92vh; border-radius: 16px 16px 0 0; }
.force-mobile .modal-lg, .force-mobile .modal-xl, .force-mobile .modal-2xl { width: 100% !important; max-width: 100% !important; }
.force-mobile .modal-center { align-self: center; border-radius: var(--radius-xl); width: 92vw !important; max-width: 400px !important; max-height: 80vh; }
.force-mobile .form-input, .force-mobile .form-select { height: var(--ctl-secondary); font-size: var(--fs-title); }
.force-mobile .form-textarea { font-size: var(--fs-title); }
.force-mobile .btn { min-height: var(--ctl-inline); padding: var(--sp-sm) var(--sp-lg); }
.force-mobile .btn-sm { min-height: var(--ctl-web-lg); }
.force-mobile .icon-btn, .force-mobile .menu-toggle { width: var(--ctl-touch); height: var(--ctl-touch); }
.force-mobile .topbar { padding: 0 var(--sp-md); gap: var(--sp-sm); }
.force-mobile .topbar-center { display: none; }
.force-mobile .topbar .user > div:last-child { display: none; }
.force-mobile .toolbar { gap: var(--sp-sm); }
.force-mobile .toolbar .search-box { min-width: 100%; max-width: 100%; flex-basis: 100%; }
.force-mobile .toolbar .btn { flex: 1 1 auto; }
/* 【v2.4.76】同媒体查询：强制移动端时 `.toast` 也走底部通栏 */
.force-mobile .toast-area, .force-mobile .toast { top: auto; bottom: calc(16px + env(safe-area-inset-bottom)); right: 16px; left: 16px; }
.force-mobile .toast { min-width: 0; max-width: none; width: auto; }
.force-mobile .modal-close { width: var(--ctl-touch); height: var(--ctl-touch); font-size: var(--glyph-arrow); }
.force-mobile table { font-size: var(--fs-caption); }
.force-mobile table thead th, .force-mobile table tbody td { padding: var(--sp-md) var(--sp-md); }
.force-mobile .card { padding: var(--sp-lg); }
.force-mobile .pager button { min-height: var(--ctl-web-lg); padding: var(--sp-sm) var(--sp-md); }

/* 移动端全局搜索按钮（顶栏右侧，替换被隐藏的 .topbar-center 搜索框入口） */
.force-mobile .mobile-search-btn { display: inline-flex; }
/* 全屏搜索层：覆盖整屏，顶部搜索条 + 下方滚动结果 */
.force-mobile .mobile-search-layer {
  display: block;
  position: fixed; inset: 0; z-index: 300;
  background: var(--surface); overflow-y: auto;
  padding: var(--sp-md) var(--sp-lg) calc(var(--sp-xl) + env(safe-area-inset-bottom));
  animation: fadeIn var(--dur-fast);
}
.force-mobile .mobile-search-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: var(--sp-md);
  padding: var(--sp-xs) 0 var(--sp-md);
  background: var(--surface);
}
.force-mobile .mobile-search-head .gs-input { height: var(--ctl-secondary); font-size: var(--fs-title); }
.force-mobile .mobile-search-head .btn { white-space: nowrap; }
.force-mobile .mobile-search-result .gs-item { padding: var(--sp-md) var(--sp-md); border-radius: var(--radius-md); }
.force-mobile .mobile-search-result .gs-item-title { font-size: var(--fs-bodyLg); }
.force-mobile .mobile-search-result .gs-empty { padding: var(--sp-huge) var(--sp-lg); font-size: var(--fs-bodyLg); }

/* 售后工单：现场照片缩略图（小程序上传，工作台弹窗内展示） */
.as-imgs { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
.as-imgs .as-img { width: 84px; height: 84px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--border-2); cursor: zoom-in; }
.as-imgs .as-img:hover { transform: scale(1.04); border-color: var(--brand-blue, #1E6BB8); }  /* 【UI测评P1】原 var(--brand,#0B5FFF) 中 --brand 未定义，兜底为体系外蓝 */
.as-img-item { position: relative; width: 84px; height: 84px; }
.as-img-item .as-img { display: block; }
.as-img-del { position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; line-height: 20px; text-align: center; background: var(--danger, #E5484D); color: var(--on-brand, #FFFFFF); border: none; border-radius: 50%; cursor: pointer; font-size: var(--fs-bodyLg); padding: 0; }

/* ============ 企业知识库（V2.0 P1） ============ */
.kb-wrap { display: flex; gap: var(--sp-xl); align-items: flex-start; }
.kb-side {
  width: 220px; flex-shrink: 0; background: var(--surface, #fff);
  border: 1px solid var(--border-2); border-radius: var(--radius-xl);
  padding: var(--sp-lg) var(--sp-md); position: sticky; top: 76px;
}
.kb-side-head { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--sp-sm) var(--sp-md); border-bottom: 1px solid var(--border-2); margin-bottom: var(--sp-sm); }
.kb-cat-list { display: flex; flex-direction: column; gap: var(--sp-xxs); max-height: calc(100vh - 200px); overflow-y: auto; }
.kb-cat-item {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-sm) var(--sp-md); border-radius: var(--radius-sm); cursor: pointer;
  font-size: var(--fs-body); color: var(--text-2); transition: background var(--dur-fast);
}
.kb-cat-item:hover { background: var(--surface-2); color: var(--text); }
/* 【2026-10-18】激活底原为裸值 #E8F1FA（注释写「无对应令牌」——其实 --info-bg 就是同一档，浅色 #E6F1FB 差 2/255）；
   更关键的是深色主题**完全没跟**：底仍是浅蓝 #E8F1FA，字却是提亮档 --brand-blue #6BA3E0 ⇒ 2.32:1。
   现两主题同走 --info-bg（深色档是 rgba(59,130,246,.16) 叠色），配色见下行深色覆盖。 */
.kb-cat-item.active { background: var(--info-bg); color: var(--brand-blue, #1E6BB8); font-weight: var(--fw-semibold); }  /* 浅色 #1E6BB8 on #E6F1FB = 4.76 ✅ */
html[data-theme="dark"] .kb-cat-item.active { color: var(--info-text); }  /* #60A5FA 叠色后 5.37~6.06 ✅（沿用 --brand-blue 仅 4.3） */
.kb-cat-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-cat-count { font-size: var(--fs-mini); color: var(--text-3); margin-left: var(--sp-sm); }
.kb-main { flex: 1; min-width: 0; }
.kb-list { display: flex; flex-direction: column; gap: var(--sp-md); margin-top: var(--sp-lg); }
.kb-doc-card {
  background: var(--surface, #fff); border: 1px solid var(--border-2);
  border-radius: var(--radius-xl); padding: var(--sp-lg) var(--sp-lg); cursor: pointer;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.kb-doc-card:hover { border-color: var(--brand-blue, #1E6BB8); box-shadow: var(--elev-blue-lg); }
.kb-doc-title { display: flex; align-items: center; gap: var(--sp-sm); flex-wrap: wrap; }
.kb-doc-name { font-size: var(--fs-bodyLg); font-weight: var(--fw-semibold); color: var(--text); }
.kb-doc-summary { font-size: var(--fs-body); color: var(--text-2); margin-top: var(--sp-sm); line-height: var(--lh-relaxed); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.kb-doc-meta { display: flex; align-items: center; gap: var(--sp-lg); flex-wrap: wrap; margin-top: var(--sp-md); font-size: var(--fs-caption); color: var(--text-3); }
.kb-doc-meta span::before { content: ''; }
.kb-doc-meta span + span::before { content: '·'; margin-right: var(--sp-lg); }

/* 文档详情弹窗 */
.kb-view-meta { display: flex; flex-wrap: wrap; gap: var(--sp-sm) var(--sp-xl); font-size: var(--fs-caption); color: var(--text-2); padding-bottom: var(--sp-md); border-bottom: 1px solid var(--border-2); margin-bottom: var(--sp-md); }
.kb-view-tags { display: flex; flex-wrap: wrap; gap: var(--sp-sm); margin-bottom: var(--sp-md); }
.kb-view-summary { font-size: var(--fs-bodyLg); color: var(--text-2); background: var(--surface-2); border-left: 3px solid var(--brand-blue, #1E6BB8); padding: var(--sp-md) var(--sp-lg); border-radius: var(--radius-xs); margin-bottom: var(--sp-lg); line-height: var(--lh-relaxed); }  /* 【v2.4.107】补行高：正文摘要展示区（多行），与 .kb-doc-summary 同档 */
.kb-content-text { font-size: var(--fs-bodyLg); line-height: var(--lh-loose); color: var(--text); word-break: break-word; }
.kb-view-files { margin-top: var(--sp-xl); padding-top: var(--sp-lg); border-top: 1px solid var(--border-2); }
.kb-files-title { font-size: var(--fs-body); font-weight: var(--fw-semibold); margin-bottom: var(--sp-md); }
.kb-file-row { display: flex; align-items: center; gap: var(--sp-md); padding: var(--sp-sm) var(--sp-md); border-radius: var(--radius-sm); background: var(--surface-2); margin-bottom: var(--sp-sm); }
.kb-file-name { flex: 1; font-size: var(--fs-body); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 移动端：知识库侧栏折叠为顶部横向选择 */
@media (max-width: 768px) {
  .kb-wrap { flex-direction: column; }
  .kb-side { width: 100%; position: static; }
  .kb-cat-list { max-height: 180px; flex-direction: row; flex-wrap: wrap; gap: var(--sp-sm); }
  .kb-cat-item { padding: var(--sp-sm) var(--sp-md); }
}

/* ===================================================
 * 【P0 移动端深适配】通用表格卡片化（配合 app.js applyResponsiveTables）
 * force-mobile 下，被打上 rt-card 标记的"列表型表格"转成卡片视图：
 *   隐藏表头 → 每行变一张卡片 → 每个单元格纵向堆叠，左侧显示字段名。
 * 说明：
 *   - 仅 .force-mobile 作用域生效，桌面端零影响
 *   - 只作用于 rt-card（运行时由 JS 自动标记），编辑/明细/弹窗表格不受影响
 * =================================================== */
.force-mobile table.rt-card { display: block; border: none; }
.force-mobile table.rt-card thead { display: none; }
.force-mobile table.rt-card tbody { display: block; }
.force-mobile table.rt-card tr {
  display: block;
  border: 1px solid var(--border-2, #e5e7eb);
  border-radius: var(--radius-md, 12px);
  background: var(--surface, #fff);
  padding: var(--sp-md) var(--sp-lg);
  margin-bottom: var(--sp-md);
}
.force-mobile table.rt-card tr:last-child { margin-bottom: 0; }
.force-mobile table.rt-card td {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-md);
  padding: var(--sp-sm) 0;
  border: none;
  text-align: left;
  font-size: var(--fs-body);
  word-break: break-word;
}
/* 首列（通常是主标识，如报价单号/合同号）视觉加粗突出 */
.force-mobile table.rt-card td:first-child { font-weight: var(--fw-semibold); }
/* 字段名标签：占固定宽度左列 */
.force-mobile table.rt-card td::before {
  content: attr(data-label);
  flex: 0 0 78px;
  max-width: 78px;
  font-size: var(--fs-caption);
  color: var(--text-3, #9ca3af);
  padding-top: var(--sp-xxs);
  white-space: normal;
  word-break: break-word;
}
/* 操作列（JS 识别含 .btn 的单元格，打 rt-actions）：按钮横向铺开，不显示字段名 */
.force-mobile table.rt-card td.rt-actions::before { display: none; }
.force-mobile table.rt-card td.rt-actions {
  flex-wrap: wrap;
  padding-top: var(--sp-md);
  margin-top: var(--sp-xs);
  border-top: 1px dashed var(--border-2, #e5e7eb);
}
.force-mobile table.rt-card td.rt-actions .btn { flex: 1 1 auto; }
/* 空态行：占满宽度居中 */
.force-mobile table.rt-card td.empty { display: block; text-align: center; padding: var(--sp-xl) 0; }
.force-mobile table.rt-card td.empty::before { display: none; }

/* ===================================================
 * v2.1 视觉精修（2026-09-19 · 纯增量增强，不改结构/不动 JS）
 * 原则：只追加、只细化，不覆盖上方已有规则的颜色与布局语义；
 *       所有动效 ≤0.25s、仅 transform/opacity，不触发 reflow。
 * =================================================== */

/* —— 1. 按钮三态补齐：原只有 hover，补 active 下沉反馈 —— */
.btn { transition: all var(--dur-fast) var(--ease-standard), transform var(--dur-instant) var(--ease-standard); }
.btn:active { transform: translateY(1px) scale(0.98); }
.btn-primary:active { transform: translateY(0) scale(0.98); box-shadow: var(--elev-brand-sm); }
.btn-danger:active { filter: brightness(0.92); }

/* —— 2. 表格交互细化：行背景过渡 + 数字等宽（金额/数量列对齐跳动消除） —— */
table tbody tr { transition: background var(--dur-fast) var(--ease-standard); }
table tbody td { font-variant-numeric: tabular-nums; }

/* —— 3. 卡片层次：阴影/描边过渡（不位移，避免布局抖动） —— */
.card { transition: box-shadow var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard); }

/* —— 4. 页面切换淡入：route() 重渲染 #content 子树时自动触发 —— */
#content > * { animation: v21PageIn var(--dur-base) var(--ease-standard); }
@keyframes v21PageIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* —— 5. 骨架屏组件（.skeleton 系列，供后续加载占位直接取用） —— */
.skeleton {
  position: relative; overflow: hidden;
  background: var(--border-2); border-radius: var(--radius-xs); color: transparent !important;
}
.skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent 0%, var(--a-white-hi-weak) 50%, transparent 100%);
  animation: v21Shimmer 1.4s infinite;
}
@keyframes v21Shimmer { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
.skeleton-text { height: 13px; margin: var(--sp-sm) 0; }
.skeleton-row  { height: 44px; margin: var(--sp-sm) 0; border-radius: var(--radius-sm); }

/* —— 5b. 骨架屏结构化（v2.4.93 · 路线图 88 档 B5）——
 * 动机：以上 `.skeleton` 自 v2.1 建好却**零使用**，全站加载态一直是 `<div class="empty">加载中…</div>`
 * （纯文字）。文字态的问题不是"难看"，而是**不预示形状**：用户看到 5 行字也不知道等来的是
 * 表格还是卡片列表，内容落位时整页跳动。
 *
 * 设计约束（三条，缺一即失去意义）：
 *   ① 骨架必须**预示真实布局**（有没有前导缩略图、几列、几行）→ 故新增形状类而非只有一条灰条；
 *   ② **不新增视觉语言** —— `.sk` 与 `.skeleton` 共用同一套底色（--border-2）与同一道流光
 *      （v21Shimmer + --a-white-hi-weak），肉眼认得出是同一个组件；
 *   ③ 宽档用类（.sk-w40 等）而非内联 `style="width:62%"`，骨架同样受"无裸值"口径约束。
 *
 * 无障碍：骨架**不承载语义**，容器由 JS 加 `aria-busy="true"` + `.sr-only` 的「加载中」，
 * 读屏用户听到的仍是"加载中"，视觉用户看到的是形状 —— 两边都不丢信息。 */
.sk {
  position: relative; overflow: hidden;
  background: var(--border-2); border-radius: var(--radius-xxs); color: transparent !important;
}
.sk::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent 0%, var(--a-white-hi-weak) 50%, transparent 100%);
  animation: v21Shimmer 1.4s infinite;
}
/* 形状 */
.sk-line  { height: 12px; }
.sk-title { height: 15px; }
.sk-block { height: 56px; border-radius: var(--radius-md); }
.sk-thumb { width: 44px; height: 44px; border-radius: var(--radius-md); flex: none; }
.sk-avatar{ width: 40px; height: 40px; border-radius: var(--radius-full); flex: none; }
/* 宽档（10% 步进；只做到必要的粒度，避免变成通用工具类表） */
.sk-w20 { width: 20%; } .sk-w30 { width: 30%; } .sk-w40 { width: 40%; } .sk-w50 { width: 50%; }
.sk-w60 { width: 60%; } .sk-w70 { width: 70%; } .sk-w80 { width: 80%; } .sk-w90 { width: 90%; }
.sk-w100{ width: 100%; }
/* 容器与行（全 flex：列数由子元素个数表达，故**零内联样式**） */
.sk-wrap { padding: var(--sp-xs) 0; }
.sk-row  { display: flex; align-items: center; gap: var(--sp-md); padding: var(--sp-sm) 0; }
.sk-col  { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-sm); }
.sk-card { display: flex; gap: var(--sp-md); padding: var(--sp-md); margin-bottom: var(--sp-sm);
           background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); }
.sk-tr   { display: flex; align-items: center; gap: var(--sp-md); padding: var(--sp-sm) 0;
           border-bottom: 1px solid var(--border-2); }
.sk-cell { flex: 1 1 0; height: 12px; }
.sk-cell:first-child { flex: 1.5 1 0; }   /* 首列（名称/编号）天然更宽，避免"等宽表格"的塑料感 */

/* —— 6. 滚动条品牌化：滑块 hover 用品牌绿，轨道更窄更轻 —— */
/* 【W1-9 保留】滚动条 thumb 中性灰蓝(#CBD6E4)，浏览器 UI 专用，无对应令牌 */
::-webkit-scrollbar-thumb { background: #CBD6E4; border-radius: var(--radius-xxs); }
::-webkit-scrollbar-thumb:hover { background: var(--primary-400); }

/* —— 7. 文本选中文品牌色 —— */
::selection { background: var(--a-green-weak); }

/* —— 8. 焦点环统一强化（无障碍：键盘导航可见性） —— */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--primary-text); outline-offset: 1px;
}

/* —— 9. 尊重系统减弱动效偏好 ——
   【CSP 行内属性清零 2026-10-05】每条 alternative 加 `html:root ` 前缀，覆盖面不变，
   把最低特异性从 (0,0,3)（`table tbody tr`）抬到 ≥ (0,1,1)，从而恒胜工具类
   `.u-transition-*` / `.u-animation-*`（(0,1,0)）—— 同 `transition` 简写与
   `transition-duration` 长写的重叠见文件上方 8 号块注释。 */
@media (prefers-reduced-motion: reduce) {
  html:root #content > *, html:root .btn, html:root table tbody tr, html:root .card,
  html:root .skeleton::after, html:root .sk::after { animation: none !important; transition: none !important; }
}

/* ===================================================
 * v2.2 深色模式（2026-09-19 · html[data-theme="dark"] 全量覆盖）
 * 机制：index.html 在 CSS 加载前按 localStorage[acme-theme]/系统偏好写入
 *       data-theme 属性（防 FOUC）；core.ts toggleTheme() 切换并持久化。
 * 覆盖范围：① :root 变量全套（326 处 var() 引用自动适配）
 *           ② 8 处硬编码白底/语义徽标（.modal !important 同级后置覆盖）
 *           ③ v2.1 区块暗色适配（骨架屏/滚动条/登录页渐变）
 * =================================================== */

html[data-theme="dark"] {
  /* 中性色：深蓝灰 Slate 系（配合品牌绿蓝，不发紫不发黄） */
  --bg:        var(--c-bg, #F7F9FC);
  --surface:   var(--c-surface, #FFFFFF);
  --surface-2: #1D2735;
  --border:    var(--c-line, #E5EAF0);
  --border-2:  #222D3D;
  --text:      var(--c-ink-1, #1A2438);
  --text-2:    var(--c-ink-2, #4A5568);
  --text-3:    var(--c-ink-3, #5F6B7E);
  --text-4:    #5B6678;
  --text-secondary: var(--text-2);
  --bg-secondary:   var(--bg);

  /* 主色不变（品牌绿 #06B981 在深底上对比度 4.6:1 达标）；语义底色改透明叠加 */
  --primary-bg:  var(--a-green-weak);
  --primary-bg2: var(--a-green-mid);
  /* 【2026-10-13 审计修复 W-R2】浅绿交互族暗色适配（对标移动端 #16352A）：
     原 --primary-50/100/200 在暗色下仍是浅绿 #ECFDF5/#A7F3D0，约 15 处交互态浅底+浅字 1.06–2.4:1。
     改为半透明绿叠加，与移动端暗色观感一致，其上浅字对比度 ≥4.5:1。bg/bg2 已由上方 --a-green-weak/mid 覆盖。 */
  /* 【v2.9.65 · α 刻度门禁收口】原为三行裸写 `rgba(6,185,129,α)`，被门禁
     check-alpha-tiers.py ④ 残留层判 FAIL（「6 族 × 3 档，不许裸写 α」）。改走令牌：
     · --primary-50  ← --a-green-weak    （.12）**与 green.weak 逐字节同值 ⇒ 零像素变化**
     · --primary-200 ← --a-green-strong  （.35）**与 green.strong 逐字节同值 ⇒ 零像素变化**
     · --primary-100 原为 .18 —— 既不在本族刻度（.12/.25/.35）上，又**全库零消费点**
       （实测仅 common.css:9 与本节两处定义，无任何 `var(--primary-100)`）⇒ 归位本族 mid(.25)，
       因零引用，此 α 位移不产生任何视觉影响。 */
  --primary-50:  var(--a-green-weak);
  --primary-100: var(--a-green-mid);
  --primary-200: var(--a-green-strong);
  --success-50:  var(--a-green-weak);
  --info-bg:     var(--c-tint-info-bg, #E6F1FB);
  --info-border: var(--c-tint-info-border, #BAE0FF);
  --readonly-bg: #1A2330;
  --danger-bg:  var(--c-tint-danger-bg, #FEF2F2);
  --danger-bg2: var(--c-tint-danger-bg2, #FEE2E2);
  --warning-bg: var(--c-tint-warning-bg, #FDF6EC);
  --accent-blue: #6BA3E0;
  /* 【W1-9b】badge-purple 深色底：该令牌此前全仓未定义，浅色档现补在 semantic-aliases.css，
     深色档必须在此覆盖，否则深色徽章会拿到浅紫底 + 浅紫字（1.1:1）。 */
  --tint-purple-badge: #2E1065;

  /* 【三端UI评审 P0-1】深色下品牌绿文字档提亮：
     --primary-text #047857 在深底 #0D131B 上仅 3.40:1（不达 4.5:1），改用亮绿 #34D399（9.70:1）；
     --primary-strong 维持 #05835C（其上白字 4.76:1 达标，且与深底边界对比 3.92:1 可辨识）。 */
  --primary-text:   var(--c-brand-text, #047857);
  --primary-strong: var(--c-brand-solid, #05835C);

  /* 【三端UI评审 P0-1】深色下的语义色：
     *-text 档提亮（原色在白底合格、在深底不合格，需各按所处底色取档）；
     填充档 --danger 压深到 #C81E1E，保证其上白字 5.74:1 达标。 */
  --success-text: var(--c-success-text, #047857);
  --warning-text: var(--c-warning-text, #A8441F);
  /* 【v2.9.78 · 暗色警示填充】必须写**字面深值**，不得改成 var(--c-warning-solid)：
     该 token 在暗色块里是「提亮档 #E8815C」（给深底上的文字/图形用），拿它当填充色
     会让 .btn-warn 上的白字 --on-brand 对比度掉到 2.72:1 < 4.5 —— 实测由
     tests/unit/theme-contrast-regression.test.js U3 断言看守，改回去必红。
     填充档与文字档是**两个语义**，同族不同值，不能互相代理。 */
  --warning: #A8441F; /* 白字 5.02:1（对齐 --warning-text 的浅色档取值） */
  --danger-text:  var(--c-danger-text, #DC2626);
  --info-text:    var(--c-info-text, #2563EB);
  --info-500:     #60A5FA;
  --danger:       var(--c-danger-solid, #DC2626);

  /* 渐变与阴影 */
  --grad-brand-soft: linear-gradient(135deg, var(--a-green-weak) 0%, var(--a-blue-mid) 100%);
  /* --shadow* 深色档已上收真值源（v2.4.92）→ tokens.css 的 html[data-theme="dark"] --elev-* */
}

/* —— 硬编码白底覆盖（原规则 8 处；.modal 原是 !important，同级后置胜出） —— */
html[data-theme="dark"] .login-box { background: var(--surface); }
html[data-theme="dark"] .modal { background: var(--surface) !important; }
html[data-theme="dark"] .notify-dropdown { background: var(--surface); }
html[data-theme="dark"] .wb-search { background: var(--surface-2); }
html[data-theme="dark"] .qm-tpl-card { background: var(--surface); }
/* 报价合计：浅灰渐变是硬编码值、深色下不跟着变，必须显式换暗底并钉住浅色字 */
html[data-theme="dark"] .qm-summary {
  background: linear-gradient(135deg, var(--surface-2) 0%, #16202C 100%);
  color: var(--text);
}
html[data-theme="dark"] .code-chip { background: var(--tint-code-badge); color: #9DB4F5; }
/* 待办提醒条：#FFFBEB 浅黄底是硬编码、深色下不跟着变，继承来的近白文字压上去仅 1.15:1
   （v3.4.75 由新增的「只设底·文字继承」判据逮到 —— 体检报告没列出这条同类缺陷） */
html[data-theme="dark"] .pending-strip { background: #2A2416; border-color: #5B4A1F; }
html[data-theme="dark"] .pending-strip .ps-title { color: #FCD34D; }

/* —— 语义徽标：深色透明底 + 提亮文字（保持色相语义不变） —— */
html[data-theme="dark"] .badge-blue   { background: var(--tint-info-badge); color: #93C5FD; }
html[data-theme="dark"] .badge-orange { background: var(--tint-warning-badge); color: #FCD34D; }
html[data-theme="dark"] .badge-yellow { background: var(--tint-warning-badge); color: #FCD34D; }
html[data-theme="dark"] .badge-red    { background: var(--tint-danger-badge); color: #FCA5A5; }
html[data-theme="dark"] .badge-gray   { background: var(--tint-neutral-badge); color: #B6C2D2; }
html[data-theme="dark"] .badge-purple { background: var(--tint-purple-badge, #2E1065); color: #C4B5FD; }

/* —— v2.1 区块暗色适配 —— */
html[data-theme="dark"] .skeleton::after {
  background: linear-gradient(90deg, transparent 0%, var(--a-white-weak) 50%, transparent 100%);
}
/* 结构化骨架同规则：深色底上流光必须**减弱**（--a-white-hi-weak 在深底上是刺眼的白带） */
html[data-theme="dark"] .sk::after {
  background: linear-gradient(90deg, transparent 0%, var(--a-white-weak) 50%, transparent 100%);
}
html[data-theme="dark"] ::-webkit-scrollbar-thumb { background: #334054; }
html[data-theme="dark"] ::selection { background: var(--a-green-strong); }

/* —— 登录页：品牌渐变压暗（与移动端深色登录渐变对齐，三端同一观感） —— */
html[data-theme="dark"] .login-page {
  background: linear-gradient(135deg, var(--c-grad-login-from, #04382A) 0%, var(--c-grad-login-mid, #0A2740) 55%, var(--c-grad-login-to, #17538C) 100%);
}

/* —— 主题切换过渡（仅颜色属性，0.2s；切换瞬间柔和不闪） —— */
body, .topbar, .card, .modal, .login-box, .notify-dropdown {
  transition: background-color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}

/* ===================================================
 * v2.3 登录页品牌动画 + 空状态统一（2026-09-20 · 纯增量 CSS）
 * ① 登录页：入场交错淡入 / logo 呼吸浮动 / 背景柔光漂移（无限动效
 *    仅 transform/opacity，≤0.55s 入场 + 低频环境动效）
 * ② 空状态：.empty 单类全站收敛（86 处 td.empty/div.empty 零 JS 改动），
 *    图标用 mask + var(--text-4) 着色，明暗主题自适应
 * ③ 深色补丁：.login-box .form-input:focus 白底漏网补齐
 * =================================================== */

/* —— 1. 登录页入场动画：品牌区四子元素交错淡入 —— */
@keyframes v23FadeUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.login-brand > * { animation: v23FadeUp var(--dur-slower) var(--ease-standard) both; }
.login-brand > *:nth-child(2) { animation-delay: 0.08s; }
.login-brand > *:nth-child(3) { animation-delay: 0.16s; }
.login-brand > *:nth-child(4) { animation-delay: 0.24s; }

/* —— 2. logo 呼吸浮动：入场结束后接管 transform（双动画复合） —— */
@keyframes v23Float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
.login-brand .brand-logo-big { animation: v23FadeUp var(--dur-slower) var(--ease-standard) both, v23Float 4.5s ease-in-out 0.5s infinite; }

/* —— 3. 特性列表逐条浮现 —— */
.login-brand .feat { animation: v23FadeUp var(--dur-slow) var(--ease-standard) both; }
.login-brand .feat:nth-child(1) { animation-delay: 0.30s; }
.login-brand .feat:nth-child(2) { animation-delay: 0.38s; }
.login-brand .feat:nth-child(3) { animation-delay: 0.46s; }
.login-brand .feat:nth-child(4) { animation-delay: 0.54s; }
.login-brand .feat:nth-child(5) { animation-delay: 0.62s; }

/* —— 4. 登录卡片入场：稍晚于品牌区，带轻量回弹感 —— */
@keyframes v23CardIn { from { opacity: 0; transform: translateY(18px) scale(0.985); } to { opacity: 1; transform: none; } }
.login-box { animation: v23CardIn var(--dur-slower) var(--ease-standard) 0.12s both; }

/* —— 5. 背景柔光缓慢漂移（24s 环境动效，不干扰注意力） —— */
@keyframes v23Drift { from { transform: translate(0, 0) scale(1); } to { transform: translate(2.5%, -2.5%) scale(1.06); } }
.login-page::before { animation: v23Drift 24s ease-in-out infinite alternate; }

/* —— 6. 空状态统一：图标 + 文案层级（td.empty 与 div.empty 通吃） —— */
.empty { font-size: var(--fs-body); }
.empty::before {
  content: ''; display: block; width: 40px; height: 40px; margin: 0 auto var(--sp-md);
  background: var(--text-4); opacity: 0.55;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 12h-6l-2 3h-4l-2-3H2'/%3E%3Cpath d='M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 12h-6l-2 3h-4l-2-3H2'/%3E%3Cpath d='M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* —— 6b. 空状态行动出口（v2.4.92 · P1-A）——
   背景：第 6 条只解决了「长得难看」，没解决「出不去」——用户看到「暂无客户」却没有任何下一步。
   本块承载 `core/empty.ts::emptyBlock()` 的三件（图标仍由上面的 ::before 提供，避免双图标）：
     标题（结论）→ 指路文案（去哪里做什么）→ 内联主按钮（复用列表页已有 action 契约）。
   行动是**主按钮**而不是次级链接：零数据时刻这是页面唯一该做的事，不该跟其它控件抢注意力。 */
.empty-block .eb-title { font-weight: var(--fw-semibold); color: var(--text); margin-bottom: var(--sp-sm); }
.empty-block .eb-hint { color: var(--text-3); font-size: var(--fs-body); line-height: var(--lh-normal); }
.empty-block .eb-hint + .btn { margin-top: var(--sp-lg); }
/* 无指路文案时按钮自己撑开上间距（否则贴着标题） */
.empty-block .eb-title + .btn { margin-top: var(--sp-lg); }
/* 空态里的按钮宽度交给内容，别继承 .empty 的居中块级行为 */
.empty-block .btn { display: inline-flex; align-items: center; }
@media (max-width: 768px) {
  /* 移动端空态块内文字留白收窄，避免 40px 图标 + 长指路文案把行宽压到 6 字以内 */
  .empty-block .eb-hint { padding: 0 var(--sp-lg); }
}

/* —— 6c. 空态插画（v2.4.105 · 路线图批 3.2）——
   动机：上面第 6 条的图标是 40px 单色灰 mask（--text-4，opacity .55），全站**同一个图标、
   同一种灰**，86 处空态长得完全一样，既无辨识度也不像「一幅图」。本批升级为插画形态：
   尺寸 40 → 64px｜着色 单色灰 → 品牌装饰渐变（--grad-brand-deco，绿→蓝）｜底层加一层弥散柔光。

   🔴 **必须分档，不能全站铺开**（这是本条的关键判断）：
   `.empty` 实际承担四种语义 —— ① 真空态（暂无客户）② 填写提示（calc 的 18 处「请输入 X」）
   ③ 加载中（「正在分析合同条款…」）④ 错误与权限（「经营数据加载失败」「无权限查看」）。
   只有 ① 值得一幅插画；②③④ 套上 64px 大图标 + --sp-huge 留白反而是缺陷（尤其 calc
   表单下那 18 处，会让一个输入提示占掉半屏）。

   判据 = **有没有行动出口**：`core/empty.ts::emptyBlock()` 渲染出的 `.empty-block`
   只出现在「真空态 + 给得出下一步」的场景，故用 `:has(> .empty-block)` 精确命中。
   不支持 :has() 的浏览器整块失效 → 优雅降级为原来的轻量形态，无破坏性。
   🔴 契约不变（判据 166）：图标的**唯一提供者仍是 `.empty::before`**，emptyBlock 依旧只出
   标题/文案/按钮三件；本批只是让这个 ::before 在真空态下换个形态。 */
.empty:has(> .empty-block) { position: relative; }
.empty:has(> .empty-block)::before {
  width: 64px; height: 64px;
  margin-bottom: var(--sp-lg);
  background: var(--grad-brand-deco);
  opacity: 0.5;
  position: relative; z-index: 1;
}
.empty:has(> .empty-block)::after {
  content: ''; position: absolute; left: 50%; top: 0;
  width: 168px; height: 168px; transform: translateX(-50%);
  border-radius: 50%; pointer-events: none; z-index: 0;
  background: radial-gradient(circle, var(--a-green-weak) 0%, transparent 68%);
}

/* —— 6d. 非空态语义的留白收紧（v2.4.105）——
   上面第 6 条给 `.empty` 定的 `padding: var(--sp-huge) 0`（上下 32px）是为「整页空状态」
   准备的。但 `.empty` 同时被大量用作**嵌在表单/区块里的一句反馈**（calc 表单下 18 处
   「请输入房间体积和换气次数」、ai 的「正在分析合同条款…」、bi 的「暂无月度数据」）——
   这些没有行动出口，也不是空页面，撑 32px 上下留白会让一句提示占掉半屏。
   收紧到 --sp-xl(20)。图标尺寸不动（仍是全站统一的 40px 轻量形态）。 */
.empty:not(:has(> .empty-block)) { padding: var(--sp-xl) 0 !important; }

/* —— 6e. 图表入场与悬停（v2.4.105 · 路线图批 3.3）——
   背景：bi.ts 的三个图表（月度收支 / 报价利润排行 / 进销项环）此前是**纯静态 SVG** ——
   数据到达时图形直接「跳」到终态，用户看不出哪儿变了；也没有任何悬停反馈。
   本批加入场生长 + 悬停提亮，并把时长与缓动接到 motion 令牌（--dur-* / --ease-*）。

   🔴 一律用 transform / opacity 这类**合成器属性**，不动几何属性（x/y/width/height）——
       改几何会让浏览器对整个 SVG 重排，长列表里直接掉帧。
   🔴 入场动画只在挂载时跑一次（非 infinite）。数据刷新时靠 innerHTML 重建子树重放。
   🔴 prefers-reduced-motion 下由文末统一块关停（该块用 animation: none 覆盖本组）。 */
.bi-grow { transform-box: fill-box; transform-origin: bottom; animation: biGrow var(--dur-slower) var(--ease-decelerate) both; }
@keyframes biGrow { from { transform: scaleY(0); } to { transform: scaleY(1); } }

/* delay 复用 instant 档（80ms），不另立刻度 —— 只为让折线略晚于柱子起步 */
.bi-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: biLine var(--dur-slower) var(--ease-decelerate) var(--dur-instant) both; }
@keyframes biLine { to { stroke-dashoffset: 0; } }

.bi-donut { transform-box: view-box; transform-origin: center; animation: biDonut var(--dur-slower) var(--ease-spring) both; }
@keyframes biDonut { from { opacity: 0; transform: rotate(-90deg) scale(0.88); } to { opacity: 1; transform: rotate(0deg) scale(1); } }

.bi-hbar { transform-box: fill-box; transform-origin: left center; animation: biHBar var(--dur-slower) var(--ease-decelerate) both; }
@keyframes biHBar { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }

/* 水平网格线：给柱状图一个可读的基线参照（此前只有 3 个轴标签，柱子悬空） */
.bi-grid { stroke: var(--border-2); stroke-width: 1; stroke-dasharray: 3 4; }

/* 悬停提亮：filter 同样走合成层，不触发重排 */
.bi-grow:hover, .bi-hbar:hover { filter: brightness(1.14) saturate(1.06); }

/* —— 7. 深色补丁：登录输入框聚焦白底漏网 —— */
html[data-theme="dark"] .login-box .form-input:focus { background: var(--surface); }

/* —— 10. 尊重系统减弱动效偏好（登录页全部动效关停） —— */
@media (prefers-reduced-motion: reduce) {
  .login-brand > *, .login-brand .feat, .login-brand .brand-logo-big,
  .login-box, .login-page::before { animation: none !important; }
}

/* ===================================================
 * 安装验收 / 设备固码模块（2026-09-20 · 外包现场安装协同）
 * 进度看板 / 房间设备 / 固码二维码打印
 * =================================================== */

/* 项目进度条 */
.ins-progress-item { padding: var(--sp-md) 0; border-bottom: 1px solid var(--border-2); }
.ins-progress-item:last-child { border-bottom: none; }
.ins-progress-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-sm); }
.ins-progress-bar { height: 8px; background: var(--surface-2); border-radius: var(--radius-full); overflow: hidden; }
.ins-progress-bar > div { height: 100%; border-radius: var(--radius-full); transition: width var(--dur-slow) var(--ease-standard); }

/* 房间 + 设备两栏布局 */
.ins-rooms-layout { display: flex; gap: var(--sp-lg); align-items: flex-start; margin-top: var(--sp-lg); }
.ins-rooms-list { width: 220px; flex-shrink: 0; display: flex; flex-direction: column; gap: var(--sp-sm); }
.ins-room-card {
  padding: var(--sp-md) var(--sp-lg); border: 1px solid var(--border-2); border-radius: var(--radius);
  cursor: pointer; transition: border-color var(--dur-fast), background var(--dur-fast); background: var(--surface);
}
.ins-room-card:hover { border-color: var(--primary-2); }
.ins-room-card.active { border-color: var(--primary); background: var(--primary-bg); }
.ins-room-devices { flex: 1; min-width: 0; }

/* 固码二维码网格 */
.ins-qr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: var(--sp-lg); padding: var(--sp-lg) 0; }
.ins-qr-item {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-sm);
  border: 1px dashed var(--border); border-radius: var(--radius); padding: var(--sp-lg) var(--sp-md);
  background: var(--surface);
}
.ins-qr-img { width: 120px; height: 120px; }
.ins-qr-null { width: 120px; height: 120px; display: flex; align-items: center; justify-content: center; color: var(--text-3); }
.ins-qr-preview { display: flex; justify-content: center; margin: var(--sp-sm) 0; }

/* 现场照片缩略图 */
.ins-photos { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
.ins-photo { width: 84px; height: 84px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--border-2); cursor: zoom-in; }
.ins-photo:hover { transform: scale(1.04); border-color: var(--brand-blue); }

/* 审核记录 */
.ins-review-item { padding: var(--sp-sm) 0; border-bottom: 1px solid var(--border-2); display: flex; flex-direction: column; gap: var(--sp-xs); }
.ins-review-item:last-child { border-bottom: none; }

/* 项目共享 */
.ins-share-item { display: flex; justify-content: space-between; align-items: center; padding: var(--sp-sm) 0; border-bottom: 1px solid var(--border-2); }
.ins-share-item:last-child { border-bottom: none; }

/* 等宽编码 */
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* 移动端：房间列表横向排布 */
@media (max-width: 768px) {
  .ins-rooms-layout { flex-direction: column; }
  .ins-rooms-list { width: 100%; flex-direction: row; flex-wrap: wrap; }
  .ins-room-card { flex: 1 1 auto; min-width: 90px; }
}

/* ==========================================================================
   外来报价单导入向导（报价模块：导入别人格式不一样的报价单）
   ========================================================================== */
.qimp-steps { display: flex; align-items: center; gap: var(--sp-sm); margin-bottom: var(--sp-xl); }
.qimp-step {
  flex: 1; text-align: center; padding: var(--sp-sm) var(--sp-md); border-radius: var(--radius-sm);
  font-size: var(--fs-body); font-weight: var(--fw-medium); color: var(--text-3);
  background: var(--surface-2); border: 1px solid var(--border-2);
}
.qimp-step.active { color: var(--primary-text); font-weight: var(--fw-semibold); background: var(--primary-bg); border-color: var(--primary); }
.qimp-step.done { color: var(--text-2); background: var(--surface-2); }

.qimp-drop {
  margin-top: var(--sp-md); padding: 26px var(--sp-lg); text-align: center; line-height: var(--lh-loose);
  border: 1.5px dashed var(--border-2); border-radius: var(--radius-md);
  background: var(--surface-2); color: var(--text-3); font-size: var(--fs-body);
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
.qimp-drop.dragover { border-color: var(--primary); background: var(--primary-bg); }

.qimp-warn {
  margin-bottom: var(--sp-lg); padding: var(--sp-md) var(--sp-md); border-radius: var(--radius-sm); font-size: var(--fs-caption);
  background: var(--warning-bg, #fff7e6); border: 1px solid var(--warning-border, #ffd591);
  color: var(--text-2); max-height: 140px; overflow: auto; line-height: var(--lh-relaxed);
}

/* 原表网格（核对表头行用）—— 2026-10-13「导入多版本」批次新增
   为什么要它：光看「原表头 / 样本值」两张列，用户无法判断**自动选中的表头行对不对**
   （合并大标题、双层表头、封面页都会让自动判定出错）。给出原表网格 + 行号 + 点击设表头，
   用户一眼就能核对并就地纠正。 */
.qimp-gridbox {
  margin-bottom: var(--sp-lg); border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  background: var(--surface-2); overflow: hidden;
}
.qimp-gridcap {
  padding: var(--sp-sm) var(--sp-md); font-size: var(--fs-caption); color: var(--text-3);
  border-bottom: 1px solid var(--border-2); background: var(--surface);
}
.qimp-gridscroll { max-height: 220px; overflow: auto; }
.qimp-grid { border-collapse: collapse; font-size: var(--fs-caption); white-space: nowrap; }
.qimp-grid th, .qimp-grid td {
  border: 1px solid var(--border-2); padding: 3px var(--sp-sm); color: var(--text-2);
  max-width: 220px; overflow: hidden; text-overflow: ellipsis;
}
.qimp-grid th { background: var(--surface); color: var(--text-3); font-weight: var(--fw-medium); }
.qimp-grid td.rownum, .qimp-grid th.rownum {
  position: sticky; left: 0; z-index: 1; background: var(--surface); color: var(--text-3);
  text-align: right; cursor: pointer; user-select: none;
}
.qimp-grid tr.is-header td { background: var(--primary-bg); color: var(--primary-text); font-weight: var(--fw-semibold); }
.qimp-grid tr.is-header td.rownum { background: var(--primary-bg); color: var(--primary-text); }
.qimp-grid tr:not(.is-header) td.rownum:hover { background: var(--border-2); }
.qimp-grid td.is-empty { background: var(--surface-2); }
[data-theme="dark"] .qimp-step.active { background: var(--primary-bg); }
[data-theme="dark"] .qimp-warn { background: var(--tint-warning-subtle); }
[data-theme="dark"] .qimp-gridbox { background: var(--surface-2); }
[data-theme="dark"] .qimp-grid tr.is-header td { background: var(--primary-bg); color: var(--primary-500, #34D399); }

/* ==========================================================================
   命令面板（Command Palette）—— v2.4.97 · 90/95 档「UX 交互深化」批次 1.1
   与 #global-search（数据搜索）职责不同：本面板找「功能」，搜索框找「数据」（判据 144）。
   全部尺寸/颜色走设计令牌，深色模式由 [data-theme="dark"] 别名自动接管。
   ========================================================================== */
.cmd-mask {
  position: fixed; inset: 0; background: var(--scrim-modal);
  z-index: 1200;                 /* 高于业务弹窗（1000），命令面板永远在最上层 */
  display: none; align-items: flex-start; justify-content: center;
  padding: 12vh var(--sp-xl) var(--sp-xl);
}
.cmd-mask.show { display: flex; animation: fadeIn var(--dur-fast); }
.cmd-box {
  background: var(--surface, #FFFFFF); border-radius: var(--radius-xl);
  width: 620px; max-width: 94vw; max-height: 62vh;
  box-shadow: var(--elev-ink-xxl); animation: slideUp var(--dur-base);
  display: flex; flex-direction: column; overflow: hidden;
  border: 1px solid var(--border-2);
}
.cmd-head {
  display: flex; align-items: center; gap: var(--sp-sm);
  padding: 0 var(--sp-lg); height: var(--ctl-input);
  border-bottom: 1px solid var(--border-2); flex-shrink: 0;
}
.cmd-search-ic { display: inline-flex; align-items: center; color: var(--text-3); flex-shrink: 0; }
.cmd-search-ic svg { width: 16px; height: 16px; }
.cmd-input {
  flex: 1; border: none !important; outline: none !important; background: transparent !important;
  font-size: var(--fs-body); color: var(--text); height: 100%; padding: 0 !important;
  box-shadow: none !important;
}
.cmd-input::placeholder { color: var(--text-3); }
.cmd-esc {
  flex-shrink: 0; font-size: var(--fs-mini); color: var(--text-3);
  border: 1px solid var(--border-2); border-radius: var(--radius-xs);
  padding: 1px 5px; font-family: var(--font-mono, monospace); line-height: 1.5;
}
.cmd-list { overflow-y: auto; flex: 1; padding: var(--sp-xs) 0; }
.cmd-group {
  padding: var(--sp-xs) var(--sp-lg) 2px; font-size: var(--fs-mini);
  color: var(--text-3); font-weight: var(--fw-medium); letter-spacing: 0.02em;
}
.cmd-item {
  display: flex; align-items: center; gap: var(--sp-sm);
  padding: 7px var(--sp-lg); cursor: pointer; line-height: 1.4;
  border-left: 2px solid transparent;
}
.cmd-item.active { background: var(--surface-2, #F5F7FA); border-left-color: var(--brand-solid); }
.cmd-item.active .cmd-title { color: var(--brand-text); }
.cmd-ic { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex-shrink: 0; color: var(--text-2); }
.cmd-ic svg { width: 16px; height: 16px; }
.cmd-title { font-size: var(--fs-body); color: var(--text); flex-shrink: 0; }
.cmd-hit { background: transparent; color: var(--brand-text); font-weight: var(--fw-semibold); }
.cmd-sub {
  font-size: var(--fs-caption); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.cmd-hint {
  margin-left: auto; font-size: var(--fs-mini); color: var(--text-3);
  font-family: var(--font-mono, monospace); flex-shrink: 0;
}
.cmd-empty { padding: var(--sp-xxl); text-align: center; color: var(--text-3); font-size: var(--fs-body); }
.cmd-foot {
  display: flex; gap: var(--sp-lg); padding: var(--sp-xs) var(--sp-lg);
  border-top: 1px solid var(--border-2); background: var(--surface-2, #F5F7FA);
  font-size: var(--fs-mini); color: var(--text-3); flex-shrink: 0;
}
.cmd-foot span { display: inline-flex; align-items: center; gap: 4px; }
.cmd-foot kbd {
  border: 1px solid var(--border-2); border-radius: var(--radius-xxs);
  padding: 0 4px; font-family: var(--font-mono, monospace); font-size: var(--fs-mini);
  background: var(--surface, #fff); line-height: 1.6;
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* 移动端：面板占满宽度、贴近顶部 */
@media (max-width: 640px) {
  .cmd-mask { padding: 6vh var(--sp-sm) var(--sp-sm); }
  .cmd-box { max-height: 72vh; }
}
.force-mobile .cmd-mask { padding: 6vh var(--sp-sm) var(--sp-sm); }

/* ==========================================================================
   右键上下文菜单（Context Menu）—— v2.4.97 · 90/95 档「UX 交互深化」批次 1.3
   与命令面板分工（判据 144 延伸）：命令面板 = 全局找「功能」；右键菜单 = 就地管「这一条」。
   层级 1150：低于命令面板（1200），高于业务弹窗（1000）—— 菜单常从弹窗内列表触发，须盖住弹窗。
   定位由 JS 计算 left/top（先量尺寸再翻转 + 夹取视口内），CSS 只负责外观。
   ========================================================================== */
.ctx-menu {
  position: fixed; left: 0; top: 0; display: none;
  z-index: 1150;
  min-width: 176px; max-width: 300px;
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-ink-xl);
  padding: var(--sp-xs) 0;
  outline: none;
}
.ctx-menu.show { display: block; animation: ctxIn var(--dur-fast) ease-out; }
@keyframes ctxIn {
  from { opacity: 0; transform: scale(0.97) translateY(-2px); }
  to   { opacity: 1; transform: none; }
}
.ctx-list { max-height: 60vh; overflow-y: auto; }
.ctx-item {
  display: flex; align-items: center; gap: var(--sp-sm);
  padding: 7px var(--sp-lg); cursor: pointer;
  font-size: var(--fs-body); color: var(--text); line-height: 1.4;
  border-left: 2px solid transparent; user-select: none;
}
.ctx-item:hover:not([aria-disabled="true"]) { background: var(--surface-2, #F5F7FA); }
.ctx-item.active:not([aria-disabled="true"]) {
  background: var(--primary-bg); border-left-color: var(--brand-solid);
}
.ctx-item[aria-disabled="true"] { color: var(--text-3); cursor: not-allowed; }
.ctx-item.danger:not([aria-disabled="true"]) { color: var(--danger-text, #DC2626); }
.ctx-item.danger.active { background: var(--danger-bg); border-left-color: var(--danger-text, #DC2626); }
.ctx-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex-shrink: 0; color: var(--text-3);
}
.ctx-ic svg { width: 15px; height: 15px; }
.ctx-item.danger .ctx-ic { color: inherit; }
.ctx-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctx-hint {
  margin-left: auto; font-size: var(--fs-mini); color: var(--text-3);
  font-family: var(--font-mono, monospace); flex-shrink: 0; padding-left: var(--sp-md);
}
.ctx-sep { border: none; border-top: 1px solid var(--border-2); margin: var(--sp-xs) 0; height: 0; }

/* 带 data-ctx 的行：给出「可右键」的可发现性提示（不改变布局，仅光标语义） */
[data-ctx] { cursor: context-menu; }
/* 移动端：菜单贴合手指、加大命中区（触屏无右键，但长按经 contextmenu 事件同样触发） */
@media (max-width: 640px) {
  .ctx-menu { min-width: 200px; }
  .ctx-item { padding: 10px var(--sp-lg); }
}
.force-mobile .ctx-menu { min-width: 200px; }
.force-mobile .ctx-item { padding: 10px var(--sp-lg); }

/* ==========================================================================
   快捷键帮助面板（Keyboard Help）—— v2.4.98 · 95 分路线「UX 交互深化」批次 1.2
   补上「可发现性」这最后一块：此前全站 12 处快捷键各自 addEventListener，
   用户无从得知有哪些键位（等于不存在）。本面板是 `core/keys.ts` 注册表的**直接视图** ——
   注册即出现，不存在「文档漏更新」的可能（含只登记不绑定的文档项）。
   层级 1250：高于命令面板（1200）与右键菜单（1150），保证任意状态下都能置顶呼出。
   ========================================================================== */
.keys-mask {
  position: fixed; inset: 0; background: var(--scrim-modal);
  z-index: 1250; display: none;
  align-items: center; justify-content: center;
  padding: var(--sp-xl);
}
.keys-mask.show { display: flex; animation: fadeIn var(--dur-fast); }
.keys-box {
  width: min(720px, 100%); max-height: 84vh;
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-xl);
  box-shadow: var(--elev-ink-xxl);
  display: flex; flex-direction: column; overflow: hidden;
  outline: none;
}
.keys-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-lg) var(--sp-xl);
  border-bottom: 1px solid var(--border-2);
}
.keys-title {
  font-size: var(--fs-bodyLg); font-weight: var(--fw-bold);
  color: var(--text); margin: 0; letter-spacing: 0.2px;
}
.keys-body {
  flex: 1; overflow-y: auto;
  padding: var(--sp-lg) var(--sp-xl);
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--sp-lg) var(--sp-xxl);
  align-content: start;
}
.keys-group-title {
  font-size: var(--fs-mini); font-weight: var(--fw-semibold); color: var(--text-3);
  letter-spacing: 1px; margin-bottom: var(--sp-sm); text-transform: none;
}
.keys-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-md); padding: 6px 0;
  font-size: var(--fs-body); color: var(--text-2);
  border-bottom: 1px dashed var(--border-2);
}
.keys-row:last-child { border-bottom: none; }
.keys-desc { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.keys-kbd {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-mini); color: var(--text-2);
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-xs); padding: 2px 7px;
  white-space: nowrap; flex-shrink: 0;
}
.keys-foot {
  padding: var(--sp-md) var(--sp-xl);
  border-top: 1px solid var(--border-2);
  font-size: var(--fs-mini); color: var(--text-3); text-align: center;
}
@media (max-width: 640px) {
  .keys-body { grid-template-columns: 1fr; }
}
.force-mobile .keys-body { grid-template-columns: 1fr; }

/* ==========================================================================
   桌面端侧栏折叠（`[` 快捷键 —— v2.4.98 · 批次 1.2）
   与移动端抽屉（`.sidebar.open`）是两回事：折叠是「常驻但收窄为图标条」，
   用于宽表格场景给内容区让位。实现只覆盖 `--sidebar-w` 一个变量 ——
   侧栏宽度与主区左边距都读它，改一处即全局同步（无需碰任何布局代码）。
   ⚠️ 限定 min-width:769px：移动端侧栏走抽屉，不受折叠类影响。
   ========================================================================== */
@media (min-width: 769px) {
  html.sidebar-collapsed { --sidebar-w: 72px; }
  html.sidebar-collapsed .sidebar .brand { justify-content: center; padding-left: 0; padding-right: 0; }
  html.sidebar-collapsed .sidebar .brand > div:not(.brand-logo) { display: none; }
  html.sidebar-collapsed .sidebar .nav-item {
    justify-content: center; gap: 0; padding-left: 0; padding-right: 0;
  }
  html.sidebar-collapsed .sidebar .nav-item .nav-label { display: none; }
  html.sidebar-collapsed .sidebar .nav-group-title {
    overflow: hidden; text-overflow: clip; white-space: nowrap; text-align: center;
  }
  /* 让位动效：侧栏宽度与主区左边距同步过渡，避免「跳一下」 */
  html.sidebar-collapsed .sidebar,
  html.sidebar-collapsed .main { transition: width var(--dur-base) var(--ease-standard), margin-left var(--dur-base) var(--ease-standard); }
}

/* ==========================================================================
   列表批量操作（Batch Operations）—— v2.4.99 · 95 分路线「UX 交互深化」批次 1.4
   复选框列与操作条由 core/batch.ts 自动注入（零模块改动，与卡片化同范式）。
   层级 950：**低于**弹窗(1000) / 右键菜单(1150) / 命令面板(1200) —— 弹层打开时
   操作条被自然遮住，无需 JS 联动。
   四段分工：① 复选框列  ② 选中行高亮  ③ 浮动操作条  ④ 移动端卡片模式适配。
   ========================================================================== */

/* --- ① 复选框列 --------------------------------------------------------- */
th.bt-col,
td.bt-col {
  width: 40px; min-width: 40px; max-width: 40px;
  text-align: center; vertical-align: middle;
  padding-left: var(--sp-md); padding-right: 0;
}
th.bt-col input[type="checkbox"],
td.bt-col input[type="checkbox"] {
  width: 15px; height: 15px; margin: 0; vertical-align: middle;
  cursor: pointer; accent-color: var(--brand-solid);
}
th.bt-col input[type="checkbox"] { cursor: pointer; }

/* --- ② 选中行高亮（类名由 JS 维护，不用 :has()，兼容老 WebView） --------- */
tbody tr.bt-sel { background: var(--primary-bg); }
tbody tr.bt-sel > td { color: var(--primary-text); }

/* --- ③ 浮动操作条 ------------------------------------------------------- */
.bt-bar {
  position: fixed;
  left: 50%; bottom: calc(var(--sp-xxl) + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%) translateY(12px);
  z-index: 950;
  display: none; align-items: center; gap: var(--sp-lg);
  max-width: calc(100vw - var(--sp-xxl) * 2);
  padding: var(--sp-sm) var(--sp-md) var(--sp-sm) var(--sp-lg);
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-lg, 14px);
  box-shadow: var(--elev-ink-xl);
  opacity: 0;
}
.bt-bar.show {
  display: flex;
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  transition: opacity var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.bt-count {
  font-size: var(--fs-body); font-weight: var(--fw-semibold);
  color: var(--text); white-space: nowrap;
}
.bt-acts { display: flex; align-items: center; gap: var(--sp-sm); flex-wrap: wrap; }
.bt-acts .btn svg { width: 14px; height: 14px; }

/* --- ④ 移动端卡片模式适配 --------------------------------------------- */
/* 卡片模式表头隐藏 → 全选无处安放，故在卡片视图隐去表头复选框列 */
.force-mobile table.rt-card thead th.bt-col { display: none; }
.force-mobile table.rt-card td.bt-col {
  display: block; padding: 0 0 var(--sp-sm); text-align: left;
}
.force-mobile table.rt-card td.bt-col::before { display: none; }
/* 复选框列被前置后，真正的首个**数据**列要接过「加粗」语义（原本是 :first-child） */
.force-mobile table.rt-card td.bt-col ~ td:nth-of-type(2) { font-weight: var(--fw-semibold); }
.force-mobile table.rt-card tr.bt-sel { border-color: var(--brand-solid); background: var(--primary-bg); }

/* 窄屏操作条：占满可用宽度、按钮均分 */
@media (max-width: 640px) {
  .bt-bar { left: var(--sp-md); right: var(--sp-md); transform: none; max-width: none; }
  .bt-bar.show { transform: none; }
  .bt-acts { flex: 1; }
  .bt-acts .btn { flex: 1 1 auto; justify-content: center; }
}
.force-mobile .bt-bar:not(.show) { display: none; }
.force-mobile .bt-bar.show { left: var(--sp-md); right: var(--sp-md); transform: none; max-width: none; }

/* 动效降级：尊重系统「减少动态效果」（三端一致原则，勿回退） */
@media (prefers-reduced-motion: reduce) {
  .bt-bar.show { transition: none; }
}


/* ===================================================
 * 列表拖拽排序（Drag Sort）—— v2.4.100 · 批次 1.5
 * 两处消费者：① 侧栏导航项（组内重排）② 表行（data-row-sort opt-in）
 *
 * 🔴 手柄为什么是 CSS 点阵而不是图标（判据 157）：
 *   跨端图标体系规格是「24×24 viewBox + 线性描边 stroke 1.8 + fill:none」，
 *   而拖拽手柄在交互语言里天然是**实心点阵**（与线性描边风格不兼容）。
 *   强行入库会产生一个"规格外"图标，且三端都没有该交互（移动端不启用行拖拽），
 *   不存在跨端同源诉求。故此处用 CSS 生成装饰纹理，**不写进 icons.json**。
 * =================================================== */

/* ---- 表头 / 单元格的手柄列 ---- */
th.ds-col, td.ds-col {
  width: 34px;
  min-width: 34px;
  padding-left: 0;
  padding-right: 0;
  text-align: center;
  vertical-align: middle;
}

/* ---- 拖拽手柄：CSS 点阵（3 行 × 2 列 = 6 点），尺寸随字号缩放 ---- */
.ds-grip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: var(--radius-xs, 4px);
  cursor: grab;
  color: var(--text-3, #94A3B8);
  touch-action: none;   /* 手柄自身不参与页面滚动，触屏可直接按住拖 */
}
.ds-grip::before {
  content: '';
  width: 10px;
  height: 14px;
  /* 六个圆点：两列等宽、三行等高 —— 用 radial-gradient 铺格，无图片、无图标资产 */
  background-image: radial-gradient(currentColor 1.1px, transparent 1.2px);
  background-size: 5px 5px;
  background-position: 0 1.5px;
  opacity: 0.75;
}
.ds-grip:hover { background: var(--surface-2, #F5F7FA); color: var(--text-2, #4B5563); }
.ds-grip:active { cursor: grabbing; }
.ds-grip:focus-visible { outline: 2px solid var(--brand-solid); outline-offset: 1px; }

/* ---- 拖拽中的行 / 导航项 ---- */
tr.ds-dragging,
.nav-item.ds-dragging {
  opacity: 0.9;
  background: var(--primary-bg) !important;
  box-shadow: var(--elev-ink-lg);
  border-radius: var(--radius-sm, 6px);
}
.nav-item.ds-dragging { cursor: grabbing; }
/* 拖拽期间全局禁选，避免拖动时把文字刷蓝 */
body.ds-dragging-active,
body.ds-dragging-active * { user-select: none !important; -webkit-user-select: none !important; }

/* ---- 侧栏「恢复默认排序」小药丸（仅在存在自定义顺序时出现） ---- */
.nav-order-reset {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  margin: var(--sp-md) var(--sp-md) var(--sp-md);
  padding: var(--sp-sm) var(--sp-md);
  min-height: 32px;
  border: 1px dashed var(--a-white-mid);
  background: transparent;
  color: var(--chrome-fg-dim);
  font-size: var(--fs-mini);
  border-radius: var(--radius-sm, 6px);
  cursor: pointer;
  flex-shrink: 0;
}
.nav-order-reset:hover { color: var(--on-brand, #FFFFFF); border-color: var(--a-white-strong); }
.nav-order-reset:focus-visible { outline: 2px solid var(--brand-solid); outline-offset: 2px; }
.nav-order-reset svg { width: 14px; height: 14px; }

/* ---- 移动端：行拖拽整列隐藏（卡片态没有"行"的概念） ----
 *   `applyResponsiveTables` 绑的 data-label 序号仍严格对齐 —— 手柄列与复选框列表头
 *   都是空文本，本就不写 data-label（判据 153 延伸）。
 *   侧栏在移动端是抽屉：触屏**需长按 380ms** 才进入拖拽（判据 159，不与抽屉滚动抢手势），
 *   鼠标/触控笔仍是即时拖拽。 */
.force-mobile th.ds-col,
.force-mobile td.ds-col { display: none; }
.force-mobile .nav-order-reset { margin-bottom: calc(var(--sp-md) + env(safe-area-inset-bottom, 0px)); }

/* 动效降级：尊重系统「减少动态效果」（三端一致原则，勿回退） */
@media (prefers-reduced-motion: reduce) {
  tr.ds-dragging, .nav-item.ds-dragging { box-shadow: none; }
}

/* ============================================================
 * 加载体验（Loading）—— v2.4.101 · 批次 1.6
 * ============================================================
 * 背景：全站 **225 处写操作**，此前只有 8 处给了按钮反馈，其余点下去**界面毫无反应** ——
 *       用户分不清「没生效」与「正在生效」，于是重复点击（同一张发票提交两次）。
 *       v2.4.101 由 `core/loading.ts` 在请求咽喉（`API.req`）自动挂载加载态（零侵入），
 *       本段是其视觉实现。两条硬要求：
 *         ① **宽度不跳** —— 手写版本用 `textContent = '保存中…'` 会把按钮撑宽、整行按钮位移。
 *            这里改为：内容整体隐藏（文字 `color:transparent` + 子元素 visibility:hidden），
 *            转圈用**绝对定位**叠在正中 —— 内容盒尺寸分毫不动，宽度天然不跳。
 *         ② **禁用必须有视觉** —— 全站此前**没有任何 `.btn:disabled` 规则**（只有 `.pager` 有），
 *            而加载态会主动禁用按钮；不补这条，加载中与正常态肉眼无差别。
 * ============================================================ */

/* —— 1. 禁用态（含无 disabled 属性元素的 aria-disabled 退化形态） —— */
.btn:disabled,
.btn[aria-disabled="true"] { opacity: .62; cursor: not-allowed; }
/* 悬停/按下反馈必须一并压掉：否则"禁用"按钮仍会抬起来，传递错误的可点暗示 */
.btn:disabled:hover,
.btn[aria-disabled="true"]:hover,
.btn:disabled:active,
.btn[aria-disabled="true"]:active {
  transform: none; box-shadow: none; filter: none;
}
.btn:disabled:hover {
  border-color: var(--border); color: var(--text-2); background: var(--surface);
}
.btn-primary:disabled:hover {
  background: var(--grad-brand); color: var(--on-brand, #FFFFFF); border-color: transparent;
}
.btn-danger:disabled:hover {
  background: var(--danger); color: var(--on-brand, #FFFFFF); border-color: transparent;
}

/* —— 2. 加载中 —— */
/* 转圈颜色按按钮变体给定：文字已被置透明，`currentColor` 不再可用 */
.btn { --btn-spin: var(--text-2); }
.btn-primary { --btn-spin: var(--on-brand, #FFFFFF); }
.btn-danger { --btn-spin: var(--on-brand, #FFFFFF); }

/* 🔴 三个选择器都要写：`.btn:disabled:hover`（0,2,1）会覆盖单写 `.btn.is-loading`（0,2,0）
 *   的 `color: transparent` —— 那会导致"悬停在加载中的按钮上，文字又冒出来"。
 *   `:disabled` 的 (0,3,0) 稳压 (0,2,1)。 */
.btn.is-loading,
.btn.is-loading:disabled,
.btn.is-loading[aria-disabled="true"] { color: transparent; cursor: progress; }
.btn.is-loading { position: relative; }
/* 子元素（图标 / 角标 / 计数）一律隐形 —— 防 SVG 自带显式 fill 时与转圈重叠 */
.btn.is-loading > * { visibility: hidden; }
.btn.is-loading::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 1em; height: 1em; margin: -0.5em 0 0 -0.5em;
  box-sizing: border-box;
  border: 2px solid var(--btn-spin);
  border-right-color: transparent;
  border-radius: var(--radius-full);
  animation: btnSpin .62s var(--ease-linear) infinite;
}
@keyframes btnSpin { to { transform: rotate(360deg); } }

/* 动效降级：不转，改用「半透明圆环」表达忙碌（勿回退成无指示） */
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::after { animation: none; opacity: .45; }
}

/* ==========================================================================
   表单体验（Form）—— v2.4.102 · 95 分路线图 批次 1.7
   对应内核：src/core/form.ts（字段校验 / 离开守卫 / 草稿自动保存）
   ========================================================================== */

/* ---- ① 字段级错误：红边框 + 字段下方的文案 ----
 * 🔴 为什么不用 `.form-input:invalid` 伪类：全站 **0 个 `<form>` 元素**，约束验证 API
 *   在元素级仍然可用，但 `:invalid` 的**呈现时机**不受控 —— 只要 `required` 且为空，
 *   浏览器就会把**用户还没开始填**的框全部标红（打开弹窗瞬间一片红）。
 *   这里只在 core/form.ts **确实判定失败**时才加 `.has-error` 类，时机由我们掌握。
 * 错误文案用 `.field-err` 贴在字段正下方 —— 与字段**有空间关联**，这是 toast 做不到的
 *   （toast 5 秒消失且不指向任何框，用户回头找不出是哪个字段）。 */
.form-input.has-error,
.form-select.has-error,
.form-textarea.has-error,
.form-group.has-error > .form-input,
.form-group.has-error > .form-select,
.form-group.has-error > .form-textarea {
  border-color: var(--danger-text, #DC2626);
}

/* 🔴 置零浏览器**原生**的无效态视觉。
 *   加了 `required` 之后，Firefox 会用 `:-moz-ui-invalid` 给「已交互但无效」的字段加默认红阴影
 *   （Chrome 走 `:user-invalid`，无默认样式）。那是**另一套时机**，与 `.has-error` 叠加会出现
 *   「我没说它错，它自己红了」。错误视觉**只**由 `.has-error` 驱动，时机由 core/form.ts 掌握。
 *   单条规则写，浏览器不认识该伪类时只丢弃这一条，不影响上面。 */
.form-input:-moz-ui-invalid { box-shadow: none; }
.form-select:-moz-ui-invalid { box-shadow: none; }
.form-textarea:-moz-ui-invalid { box-shadow: none; }
.form-input:user-invalid,
.form-select:user-invalid,
.form-textarea:user-invalid { box-shadow: none; }
/* 聚焦时把焦点环也换成红色，否则「已出错」与「正在编辑」的视觉会互相打架 */
.form-input.has-error:focus,
.form-select.has-error:focus,
.form-textarea.has-error:focus,
.form-group.has-error > .form-input:focus,
.form-group.has-error > .form-select:focus,
.form-group.has-error > .form-textarea:focus {
  border-color: var(--danger-text, #DC2626);
  box-shadow: 0 0 0 3px var(--danger-bg2, #FEE2E2);
}

.field-err {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-xs);
  margin-top: var(--sp-xs);
  font-size: var(--fs-caption);
  line-height: var(--lh-normal);
  color: var(--danger-text, #DC2626);
}
/* 小圆叹号：让错误文案在纯文字环境下也能被一眼认出（不依赖颜色 —— 色觉障碍可读） */
.field-err::before {
  content: '!';
  flex: 0 0 auto;
  width: 1.05em; height: 1.05em;
  margin-top: .09em;
  border-radius: var(--radius-full);
  background: var(--danger-text, #DC2626);
  color: var(--surface, #FFFFFF);
  font-size: .82em;
  font-weight: var(--fw-bold);
  line-height: 1.05em;
  text-align: center;
}

/* ---- ② 草稿恢复提示条（只在显式 opt-in `data-draft` 的弹窗里出现） ----
 * 不自动填 —— 报「发现了什么」，填不填由用户点。
 * 自动填会**静默覆盖**当前界面（可能是别人刚改过的服务端新值），那才是真的坑。 */
.draft-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  margin: 0 var(--sp-xxl) var(--sp-md);
  padding: var(--sp-sm) var(--sp-md);
  border-radius: var(--radius-md);
  background: var(--primary-bg, #ECFDF5);
  color: var(--text);
  font-size: var(--fs-caption);
  line-height: var(--lh-normal);
}
.draft-bar-txt { flex: 1 1 auto; min-width: 0; }
.draft-bar .btn { flex: 0 0 auto; }

/* 窄屏：错误文案与提示条都要能换行，不能把弹窗撑破 */
@media (max-width: 640px) {
  .draft-bar { margin: 0 var(--sp-lg) var(--sp-md); flex-wrap: wrap; }
  .draft-bar-txt { flex: 1 1 100%; }
}

/* ── 固码打印页（install.ts 的 insPrintQr 打开的独立窗口） ─────────────────────
   该窗口由 document.write 生成，只搬父页已指纹化的样式表链接，不额外加载样式。
   本节即原写在该窗口【内联 <style>】里的规则（2026-10-05 迁出：内联样式属 CSP
   style-src-elem: inline，是收紧 style-src 的阻塞点）。
   注：#ins-qr-grid 外壳不随 innerHTML 搬过去，故由 <body> 自身充当网格容器。 */
.print-qr-body { font-family: system-ui, sans-serif; padding: var(--sp-xl); display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-xl); align-items: start; }
.print-qr-body .ins-qr-item { text-align: center; }
@media print { .print-qr-body { grid-template-columns: repeat(3, 1fr); } }

/* ── 抄送待办卡片的「未读」左侧强调条 ─────────────────────────────────────────
   原为 todo.ts 模板里的行内 `style="… border-left:3px solid var(--primary)"`，
   2026-10-05 改由 data 属性驱动：标记已读时 JS 只翻 data-cc-read，视觉自动跟随
   （不再需要手工清 borderLeft，少一处状态同步面）。
   🔴 带 !important 才与行内等价：行内样式特异性 (1,0,0,0) 是作者样式里最高的，
      而 .card 基础规则用 `border: 1px solid var(--border-2)` 简写设了 border-left，
      不提权会被它按源序压掉。
   ⚠️ 作用域限定 `.card[...]`：data-cc-read 目前仅 todo.ts 产出。 */
.card[data-cc-read="0"] { border-left: 3px solid var(--primary) !important; }

/* ============ OA 任务协作增强（v2.9.39）：看板 / 标签 / 子任务 / 评论 ============ */
.seg { display: inline-flex; border: 1px solid var(--border-2, #E5E7EB); border-radius: var(--radius-sm, 8px); overflow: hidden; }
.seg-btn { border: 0; background: transparent; padding: 6px 14px; cursor: pointer; color: var(--text-2, #5F6B7E); font-size: var(--fs-body); line-height: 1; }
.seg-btn.active { background: var(--grad-brand, #06B981); color: var(--on-brand); }
.tag { display: inline-block; padding: 2px 8px; border-radius: 999px; background: var(--tint-brand, #E6F7F1); color: var(--brand-text, #047857); font-size: var(--fs-caption); max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kanban { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-md); margin-top: var(--sp-lg); align-items: start; }
.kanban-col { background: var(--surface-2, #F8FAFC); border: 1px solid var(--border-2, #E5E7EB); border-radius: var(--radius, 12px); padding: 10px; }
.kanban-col-head { display: flex; justify-content: space-between; align-items: center; font-weight: 600; padding: 4px 6px 10px; }
.kanban-count { background: var(--surface, #fff); border: 1px solid var(--border-2, #E5E7EB); border-radius: 999px; padding: 1px 8px; font-size: var(--fs-caption); color: var(--text-3, #94A3B8); }
.kanban-col-body { display: flex; flex-direction: column; gap: 8px; min-height: 40px; }
.kanban-card { background: var(--surface, #fff); border: 1px solid var(--border-2, #E5E7EB); border-radius: 10px; padding: 10px; }
.kanban-card-title { font-size: var(--fs-bodyLg); line-height: 1.4; word-break: break-word; }
.kanban-empty { color: var(--text-3, #94A3B8); font-size: var(--fs-caption); text-align: center; padding: 10px 0; }
.task-detail-head { padding: 0 var(--sp-lg) var(--sp-md); border-bottom: 1px solid var(--border-2, #E5E7EB); }
.task-detail-title { font-size: var(--fs-titleLg); font-weight: 600; width: 100%; }
.task-detail-title-static { font-size: var(--fs-titleLg); }
.task-detail-body-scroll { padding: var(--sp-md) var(--sp-lg); max-height: 62vh; overflow-y: auto; }
.td-section { margin-top: var(--sp-lg); }
.td-section-title { font-weight: 600; margin-bottom: var(--sp-sm); font-size: var(--fs-bodyLg); }
.subtask-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.subtask-done { text-decoration: line-through; color: var(--text-3, #94A3B8); }
.comment-item { padding: 8px 0; border-bottom: 1px dashed var(--border-2, #E5E7EB); }
.comment-head { display: flex; align-items: center; gap: var(--sp-sm); font-size: var(--fs-body); }
.comment-body { margin-top: 4px; white-space: pre-wrap; word-break: break-word; }
.form-static { display: block; padding: 6px 0; color: var(--text); }
/* 暗色主题适配 */
html[data-theme="dark"] .kanban-col { background: var(--surface-2, #1E293B); }
html[data-theme="dark"] .kanban-card { background: var(--surface, #0F172A); }
html[data-theme="dark"] .kanban-count { background: var(--surface-2, #1E293B); }
html[data-theme="dark"] .tag { background: var(--a-green-weak); color: #6EE7B7; }
html[data-theme="dark"] .comment-item { border-bottom-color: var(--border-2, #334155); }

/* ============ 公告通知（v2.9.39 · 对标钉钉）============ */
.notice-imgs { display: flex; flex-wrap: wrap; gap: 8px; }
.notice-thumb { width: 96px; height: 96px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border, #E5E7EB); background: var(--surface-2, #F3F4F6); }
.notice-img-item { position: relative; display: inline-block; }
.notice-img-del { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; line-height: 18px; text-align: center; border-radius: 50%; background: var(--danger); color: var(--on-brand); border: none; cursor: pointer; font-size: var(--fs-bodyLg); padding: 0; }
.notice-scope-item { display: inline-flex; align-items: center; gap: var(--sp-xs); margin: 0 var(--sp-md) var(--sp-sm) 0; font-size: var(--fs-body); }
.notice-progress { height: 10px; background: var(--surface-2, #E5E7EB); border-radius: 6px; overflow: hidden; }
.notice-progress-bar { height: 100%; background: linear-gradient(90deg, var(--primary), var(--brand-blue)); transition: width .25s ease; }
.notice-read-row { padding: 4px 0; border-bottom: 1px solid var(--border, #EEF0F3); font-size: var(--fs-body); }
/* 暗色主题适配 */
html[data-theme="dark"] .notice-thumb { border-color: var(--border-2, #334155); background: var(--surface-2, #1E293B); }
/* 【2026-10-18】原覆盖 `background: #F87171`（浅红）→ 白字只有 2.77:1（连非文本 3:1 都不到）。
   删掉即为正解：--danger 深色档 #C81E1E，白字 5.74:1，且不再多一个裸色值。 */
html[data-theme="dark"] .notice-progress { background: var(--surface-2, #1E293B); }
html[data-theme="dark"] .notice-read-row { border-bottom-color: var(--border-2, #334155); }
html[data-theme="dark"] .form-static { color: var(--text); }

/* ============ 日程月历（v2.9.39 对标钉钉） ============ */
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-weekday { text-align: center; font-size: var(--fs-caption); color: var(--text-2, #6B7280); padding: 6px 0; font-weight: 600; }
.cal-cell { min-height: 78px; border: 1px solid var(--border, #EEF0F3); border-radius: 8px; padding: 4px 6px; background: var(--surface, #fff); font-size: var(--fs-caption); overflow: hidden; }
html[data-theme="dark"] .cal-cell { border-color: var(--border-2, #334155); background: var(--surface, #0F172A); }
.cal-cell.cal-empty { background: transparent; border-color: transparent; }
.cal-daynum { font-weight: 600; margin-bottom: 2px; color: var(--text); }
html[data-theme="dark"] .cal-daynum { color: var(--text); }
.cal-today { border-color: var(--brand-blue); box-shadow: 0 0 0 2px rgba(30,107,184,.15) inset; }
.cal-today .cal-daynum { color: var(--brand-blue); }
.cal-item { margin-top: 2px; padding: 1px 4px; border-radius: 4px; background: var(--tint-blue, #EFF6FF); color: var(--text); line-height: 1.5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html[data-theme="dark"] .cal-item { background: #1E293B; color: #E5E7EB; }
.cal-item.cal-remind { background: var(--tint-warning-badge); color: #92400E; }  /* 文字色无对应令牌（--warning-text 更深），保留 */
html[data-theme="dark"] .cal-item.cal-remind { background: #422006; color: #FDE68A; }
.cal-item.cal-meeting { background: var(--success-50); color: #065F46; }  /* 文字色无对应令牌（--success-text 更深），保留 */
html[data-theme="dark"] .cal-item.cal-meeting { background: #064E3B; color: #A7F3D0; }
.remind-list { list-style: none; margin: 0; padding: 0; }
.remind-list li { padding: 6px 0; border-bottom: 1px solid var(--border, #EEF0F3); font-size: var(--fs-body); }
.remind-list li:last-child { border-bottom: none; }
html[data-theme="dark"] .remind-list li { border-bottom-color: var(--border-2, #334155); }

/* 【v3.4.77 · 硬编码色主题跟随】
   下面几处的**基色**走 var() 跟随主题，但 hover / 描边写死了浅色档的值 ⇒
   深色档下一悬停就跳回浅色（按钮变突兀、描边亮成一条线、滚动条忽然变白）。
   修法是补深色档覆盖，不去动浅色档 —— 与 .qm-paper-stage / .code-chip 同口径。 */
html[data-theme="dark"] .stat-card.stat-export {
  background: linear-gradient(135deg, var(--info, #3B82F6), #1E3A8A);
}
html[data-theme="dark"] .btn-danger:hover { background: #7F1D1D; }
html[data-theme="dark"] .btn-danger.btn-sm,
html[data-theme="dark"] .btn.btn-sm.btn-danger { border-color: #7F1D1D; }
html[data-theme="dark"] .btn-warn:hover { background: #7C2D12; }
html[data-theme="dark"] .btn-warn.btn-sm { border-color: #7C2D12; }
html[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: #3E4C60; }
.remind-on { font-weight: 600; }
.empty-li { color: var(--text-2, #9CA3AF); }

/* 【v3.4.30】已作废付款单：行内关键字段删除线 + 弱化（单据保留留痕，汇总/对账已剔除该行） */
.fm-voided-struck { text-decoration: line-through; opacity: .55; }

/* ===== 产品库联想浮层（v3.4.105）=====
   明细行「产品名称」与产品库勾选面板的搜索框共用（v3.4.106 起后者不再需要，浮层只服务明细行）。
   固定定位挂 body：明细表在 modal 的 .table-wrap（overflow:auto）里，挂在单元格内会被裁掉。 */
.qm-sug {
  position: fixed;
  z-index: 3000;
  overflow-y: auto;
  padding: var(--sp-xs, 4px);
  background: var(--c-surface, #fff);
  border: 1px solid var(--c-line, #e5eaf0);
  border-radius: var(--radius-sm, 8px);
  box-shadow: 0 10px 28px rgba(15, 42, 80, .18);
}
.qm-sug[hidden] { display: none; }
.qm-sug-item { padding: 6px 10px; border-radius: var(--radius-xs, 6px); cursor: pointer; }
.qm-sug-item.active,
.qm-sug-item:hover { background: var(--c-tint-info-bg, #e6f1fb); }
.qm-sug-row { display: flex; align-items: baseline; gap: var(--sp-sm, 8px); flex-wrap: wrap; }
.qm-sug-name { font-size: var(--fs-body, 13px); font-weight: 600; color: var(--c-ink-1, #1a2438); }
.qm-sug-model { font-size: var(--fs-caption, 12px); color: var(--c-brand-blue, #1e6bb8); }
.qm-sug-sub { margin-top: 2px; font-size: var(--fs-mini, 11px); color: var(--c-ink-3, #5f6b7e); }
html[data-theme="dark"] .qm-sug { border-color: #2B3648; box-shadow: 0 10px 28px rgba(0, 0, 0, .5); }
html[data-theme="dark"] .qm-sug-item.active,
html[data-theme="dark"] .qm-sug-item:hover { background: #1E2A3D; }

/* ===== 组合勾选面板（v3.4.106）=====
   左栏产品库（浏览 + 打勾），右栏已勾清单（改数量）。两栏等高、各自滚动：
   产品库 84+ 条全渲染，若让整个 body 滚动，右栏的「已勾选」会跟着滚出视野，
   勾到第 20 条时看不到自己勾了什么 —— 这正是逐行搜索方案最难用的地方。 */
.bom-pick { display: flex; gap: var(--sp-md, 12px); align-items: stretch; }
.bom-pick-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--border-2, #e5eaf0);
  border-radius: var(--radius-sm, 8px);
  overflow: hidden;
}
.bom-pick-lib { flex: 1 1 58%; }
.bom-pick-sel { flex: 1 1 42%; }
.bom-pick-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-sm, 8px);
  padding: var(--sp-sm, 8px) var(--sp-md, 12px);
  background: var(--surface-2, #f5f8fc);
  border-bottom: 1px solid var(--border-2, #e5eaf0);
  flex-shrink: 0;
}
.bom-pick-bar .form-input { flex: 1 1 auto; min-width: 0; }
.bom-pick-scroll { max-height: 340px; overflow: auto; }
.bom-pick-scroll table { margin: 0; }
.bom-pick-scroll thead th { position: sticky; top: 0; z-index: 1; background: var(--surface-2, #f5f8fc); }
.bom-lib-row { cursor: pointer; }
.bom-lib-row:hover { background: var(--c-tint-info-bg, #e6f1fb); }
.bom-lib-row.picked { background: var(--c-tint-info-bg, #e6f1fb); box-shadow: inset 3px 0 0 var(--c-brand-solid, #1e6bb8); }
.bom-lib-row.picked .bom-lib-name { font-weight: 600; color: var(--c-brand-text, #1657a0); }
/* 复选框只是状态指示：点击一律落在 tr 上（tr 带 data-act），
   否则勾选框自带 toggle 再叠加委托 toggle，一次点击会来回抵消成"没反应"。 */
.bom-lib-row input[type="checkbox"] { pointer-events: none; margin: 0; }
.bom-sel-name { line-height: 1.35; }
/* 【v3.4.110】兜底价标记：产品库没填售价、按「成本×(1+30%)」估出来的单价，必须一眼看出是估的 */
.bom-fb-dot { color: var(--c-warning-text, #b26a00); font-weight: 700; margin-left: 1px; }
.bom-fb-tag { color: var(--c-warning-text, #b26a00); font-weight: 600; }
.bom-pick-empty { padding: var(--sp-lg, 16px); text-align: center; color: var(--c-ink-3, #5f6b7e); font-size: var(--fs-caption, 12px); }
html[data-theme="dark"] .bom-pick-col { border-color: #2B3648; }
html[data-theme="dark"] .bom-pick-bar { background: #1B2434; border-bottom-color: #2B3648; }
html[data-theme="dark"] .bom-pick-scroll thead th { background: #1B2434; }
html[data-theme="dark"] .bom-lib-row:hover,
html[data-theme="dark"] .bom-lib-row.picked { background: #1E2A3D; }
html[data-theme="dark"] .bom-lib-row.picked .bom-lib-name { color: #7FB6EE; }

