/* ============================================================
   CRM 设计系统 v3 — macOS 风格
   统一浅色背景 · 轻量边界 · 精致细节
   ============================================================ */
/* ★ Inter 本地化（第105次修正）：上一轮我直接删掉 Google Fonts、
   以为"降级链兜得住、字形差别可忽略" —— **低估了**。现场一眼看出字变了：
   Windows 上 -apple-system 不生效，没有 Inter 就落到 Segoe UI，
   字形和字宽都不一样。所以把字体本身也搬到本地：
   样子跟原来完全一致，同时不依赖外网。文件在 static/vendor/fonts/。 */
/* ★★ 全站一种字体（2026-09-03 现场指令：\"系统需要默认一个字体\"）★★
   历史：第105次把 Inter 本地化、排字体栈最前 —— 但 Inter 只有拉丁字符：
   英文和数字用 Inter、汉字落到雅黑/苹方、.mono 的单号金额又是 Consolas，
   同一屏三种字形。数字多的模块（订单、PI、逾期表）和文字多的模块看起来
   就是\"字体不一样\"。现在反转：中文字体排最前（它们自带完整拉丁字形），
   中英文数字全走同一张脸，跟用户对照的那家 CRM 一个做法。
   Inter 的 @font-face 与升级清单条目一并移除。 */
/* ★ 2026-10-09「国家也要加上旗帜」：Windows 自带字体不画国旗 emoji（🇭🇰 显示成「HK」两个字母）。
   这个字体只含国旗（unicode-range 只覆盖国旗用到的区域指示符），别的字一个都不接管 —— Twemoji，CC-BY 4.0。 */
@font-face { font-family: 'Twemoji Country Flags'; font-display: swap;
  src: url('vendor/fonts/TwemojiCountryFlags.woff2') format('woff2');
  unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F; }
:root { --ui-font-family: 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC',
                          'Segoe UI', -apple-system, sans-serif; }

:root {
  --primary:        #10b981;
  --primary-dark:   #059669;
  --primary-soft:   #ecfdf5;
  --primary-muted:  #a7f3d0;

  --red:        #ef4444;
  --red-soft:   #fef2f2;
  --amber:      #f59e0b;
  --amber-soft: #fffbeb;
  --blue:       #3b82f6;
  --blue-soft:  #eff6ff;

  --ink:        #1c1c1e;
  --ink-soft:   #6e6e73;
  --ink-muted:  #aeaeb2;

  /* ★ 侧边栏和内容区统一用同一背景 */
  --bg:           #f2f2f7;   /* macOS 系统浅灰 */
  --surface:      #ffffff;
  --sidebar-bg:   #f2f2f7;   /* 与 --bg 相同 */
  --border:       #e5e5ea;
  --border-strong:#c7c7cc;

  --shadow-xs: 0 1px 2px rgba(0,0,0,0.04);
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
  --shadow:    0 4px 12px rgba(0,0,0,0.06);
  --shadow-md: 0 8px 24px rgba(0,0,0,0.08);

  --sidebar-text:        #3c3c43;
  --sidebar-muted:       #8e8e93;
  --sidebar-section:     #8e8e93;
  --sidebar-active-bg:   rgba(16,185,129,0.12);
  --sidebar-active-text: var(--primary-dark);
  --sidebar-hover-bg:    rgba(0,0,0,0.05);

  --sidebar-w: var(--ui-sidebar-w, 220px);
  /* 页面外观（2026-08-31 全员可调）：字号 / 内容宽度 / 疏密 / 侧边栏宽，
     由 base.html 按系统设置注入 --ui-* 变量；没设就是这些默认值 = 原样。 */
  --ui-font: 14px;
  --ui-cell-y: 10px;
  --ui-cell-x: 14px;
  --ui-content-max: none;
  --ui-content-pad: 24px;
  --radius:    8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
}

/* 夜间模式 */
body.dark-mode {
  --bg:         #1c1c1e;
  --surface:    #2c2c2e;
  --sidebar-bg: #1c1c1e;
  --border:     #3a3a3c;
  --border-strong: #48484a;
  --ink:        #f5f5f7;
  --ink-soft:   #98989d;
  --ink-muted:  #6e6e73;
  --primary:    #34d399;
  --primary-soft: #064e3b;
  --red-soft:   #2c1010;
  --amber-soft: #2c1f00;
  --blue-soft:  #0c1a2e;
  --sidebar-text: #e5e5ea;
  --sidebar-muted: #8e8e93;
  --sidebar-hover-bg: rgba(255,255,255,0.06);
  --sidebar-active-bg: rgba(52,211,153,0.15);
}

/* ---- 基础 ---- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  background: var(--bg);
  color: var(--ink);
  /* ★ 2026-09-03 全站一种字体：栈收进 --ui-font-family（中文字体最前，见文件头注释） */
  font-family: 'Twemoji Country Flags', var(--ui-font-family);
  font-size: var(--ui-font, 14px);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
.mono {
  /* ★ 去掉 font-size: 0.92em（第108次现场：同一行里公司名 14px、
     订单号/金额/日期却小一号，看着像没对齐）。
     .mono 的用途是**等宽对齐数字**，不该顺带把字号也改了 ——
     全站几十处在用，凡是等宽列都比旁边的字小一圈，这就是根源。
     等宽字体本身就比同字号的西文窄，视觉上已经不会显得大；
     真需要小字的地方另外加 .small，语义也更清楚。 */
  /* ★ 2026-09-03 全站一种字体：等宽对齐改用同一字体的表格数字（tnum），
     不再换 Consolas/SF Mono —— 那正是"数字列跟别处字体不一样"的第三张脸。
     tabular-nums 下每个数字等宽，金额/单号照样上下对齐。 */
  font-family: 'Twemoji Country Flags', var(--ui-font-family);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* ================================================================
   布局
   ================================================================ */
.app-shell { display: flex; min-height: 100vh; }

/* ================================================================
   侧边栏 — macOS 风格（与内容区同色）
   ================================================================ */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--sidebar-bg);
  /* 只用一条细分隔线，不用重边框 */
  border-right: 1px solid var(--border);
  position: fixed;
  top: 0; left: 0; bottom: 0;
  display: flex; flex-direction: column;
  z-index: 1000;
  transition: transform 0.22s ease;
}

/* 品牌区 */
.sidebar-brand {
  padding: 16px 14px 12px;
  border-bottom: 1px solid var(--border);
  min-height: 56px;
  display: flex; align-items: center;
}
.sidebar-brand img { max-height: 32px; max-width: 100%; object-fit: contain; }
.sidebar-brand .brand-placeholder { display: flex; align-items: center; gap: 9px; }
.sidebar-brand .brand-icon {
  width: 28px; height: 28px; border-radius: 7px;
  background: var(--primary);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 13px; flex-shrink: 0;
}
.sidebar-brand .brand-name {
  font-weight: 700; font-size: 14px;
  color: var(--ink);
  letter-spacing: -0.02em;
}

/* 导航 */
.sidebar-nav { flex: 1; padding: 8px 8px; overflow-y: auto; scrollbar-width: none; }
.sidebar-nav::-webkit-scrollbar { display: none; }
.sidebar-nav .nav-section {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--sidebar-section);
  padding: 16px 8px 5px;
  text-transform: uppercase;
}
.sidebar-link {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px;
  border-radius: 8px;
  color: var(--sidebar-text);
  text-decoration: none;
  font-size: 13px; font-weight: 500;
  margin-bottom: 1px;
  transition: background 0.1s, color 0.1s;
}
.sidebar-link i {
  font-size: 14px; width: 18px; text-align: center;
  color: var(--sidebar-muted); flex-shrink: 0;
  transition: color 0.1s;
}
.sidebar-link:hover { background: var(--sidebar-hover-bg); color: var(--ink); }
.sidebar-link:hover i { color: var(--ink-soft); }
.sidebar-link.active {
  background: var(--sidebar-active-bg);
  color: var(--sidebar-active-text);
  font-weight: 600;
}
.sidebar-link.active i { color: var(--sidebar-active-text); }

/* 底部用户区 */
.sidebar-foot {
  padding: 10px 10px 12px;
  border-top: 1px solid var(--border);
}
.sidebar-user { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.sidebar-user .avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--primary-soft); color: var(--primary-dark);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; flex-shrink: 0;
}
.sidebar-user .uname { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.sidebar-user .urole { font-size: 10.5px; color: var(--sidebar-muted); }
.sidebar-logout {
  display: block; text-align: center;
  font-size: 12px; color: var(--sidebar-muted);
  border: 1px solid var(--border); border-radius: 7px; padding: 5px 10px;
  text-decoration: none; transition: all 0.1s;
}
.sidebar-logout:hover { background: var(--red-soft); color: var(--red); border-color: #fca5a5; }

/* ================================================================
   主内容区 & 顶栏
   ================================================================ */
.main-area {
  margin-left: var(--sidebar-w);
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
}

.topbar {
  height: 52px; flex-shrink: 0;
  background: var(--bg);   /* ★ 与侧边栏同色，融为一体 */
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 20px;
  position: fixed; top: 0; left: var(--sidebar-w); right: 0; z-index: 900;
}
.topbar h1 {
  font-size: 14px; font-weight: 600; margin: 0; color: var(--ink);
  letter-spacing: -0.02em;
}

/* ---- 标签栏 ---- */
/* ★ 2026-09-21 现场："导航栏字体没有居中，而且很单调，应该跟别的系统一样颜色标识出来，跟着主题走"。
   原来是贴底边的"文件夹标签"（align-items:flex-end + 顶部留白 + 下挪 1px → 字偏下），
   当前页只有白底一条细下划线，几乎看不出来。改成胶囊：垂直居中；当前页整块填主题色白字
  （层次版主题用渐变，与侧栏选中项一致）；悬停淡主题色。栏高不变，正文位置不受影响。 */
.page-tabs-bar {
  position: fixed; top: 52px; left: var(--sidebar-w); right: 0; z-index: 890;
  display: flex; align-items: center; gap: 4px;
  padding: 0 16px; min-height: 34px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.page-tabs-bar::-webkit-scrollbar { display: none; }
.page-tabs-bar:empty { display: none; }
.page-tab {
  display: inline-flex; align-items: center; gap: 5px;
  height: 26px; padding: 0 10px 0 12px; white-space: nowrap; line-height: 1;
  border: 0; border-radius: 8px;
  background: transparent; color: var(--ink-soft); cursor: pointer;
  font-size: 12.5px; max-width: 190px; flex-shrink: 0;
  position: relative;
  transition: background 0.12s, color 0.12s, box-shadow 0.12s;
}
.page-tab:hover { background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--ink); }
.page-tab.active {
  background: var(--primary); color: #fff; font-weight: 600;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--primary) 35%, transparent);
  z-index: 1;
}
.page-tab .tab-title { overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
.page-tab .tab-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%; font-size: 10px; line-height: 1;
  color: var(--ink-muted); flex-shrink: 0;
}
.page-tab .tab-close:hover { background: rgba(0,0,0,0.1); color: var(--ink); }
.page-tab.active .tab-close { color: rgba(255,255,255,.85); }
.page-tab.active .tab-close:hover { background: rgba(255,255,255,.25); color: #fff; }

/* content区域 */
.content { padding: calc(52px + 34px + 8px) var(--ui-content-pad, 24px) 56px; }   /* 2026-09-22：+20 → +8，工具条贴近标签栏 */
/* 内容限宽（2026-08-31 v78 修正）：限宽作用在 .content 的直接子元素上，不动 .content 本身 ——
   .content 是 .main-area 的 flex 子项，给它 margin:auto 会收缩到内容宽度并居中（v71 现场：
   仪表盘悬空居中、宽表格撑破视口）。none 时下面这条等于没写。 */
.content > *:not(script):not(style) { max-width: var(--ui-content-max, none); margin-left: auto; margin-right: auto; }

/* ================================================================
   卡片 — 白色卡片浮在灰色背景上（macOS 风）
   ================================================================ */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}
.card-header {
  background: transparent;
  border-bottom: 1px solid var(--border);
  font-weight: 600; font-size: 13px;
  color: var(--ink); padding: 11px 16px;
}
.card-body { padding: 16px; }
.card-footer {
  background: var(--bg); border-top: 1px solid var(--border);
  padding: 10px 16px;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* KPI stat cards */
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  box-shadow: var(--shadow-xs);
  transition: box-shadow 0.15s, transform 0.15s;
}
.stat-card:hover { box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.stat-card .stat-icon {
  width: 34px; height: 34px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; margin-bottom: 10px;
}
.stat-card .stat-label { font-size: 11.5px; color: var(--ink-soft); margin-bottom: 4px; font-weight: 500; }
.stat-card .stat-value { font-size: 22px; font-weight: 700; letter-spacing: -0.03em; color: var(--ink); line-height: 1.1; }
/* 口径说明：成交率和转换率两个数字长得像、算法完全不同，
   不在卡片上写清分母，看的人一定会记混（也一定会来问）。 */
.stat-card .stat-hint { font-size: 10.5px; color: var(--ink-soft); margin-top: 5px; line-height: 1.3; opacity: .85; }
.stat-card.accent-primary .stat-icon { background: var(--primary-soft); color: var(--primary-dark); }
.stat-card.accent-red .stat-icon { background: var(--red-soft); color: var(--red); }
.stat-card.accent-amber .stat-icon { background: var(--amber-soft); color: var(--amber); }
.stat-card.accent-blue .stat-icon { background: var(--blue-soft); color: var(--blue); }

/* ================================================================
   表格
   ================================================================ */
.table { margin-bottom: 0; font-size: 13.5px; }
.table thead th {
  /* 2026-09-03 抬头醒目化：表头同"字段标签"一个待遇 —— 主墨色 + 加粗（字号走标尺，见 v146） */
  font-size: 11.5px; font-weight: 700;
  color: var(--ink);
  border-bottom: 1px solid var(--border);
  border-top: none;
  padding: 9px 14px;
  background: var(--bg);
  white-space: nowrap;
}
.table td {
  padding: var(--ui-cell-y, 10px) var(--ui-cell-x, 14px); vertical-align: middle;
  border-top: 1px solid var(--border); color: var(--ink);
}
.table-hover tbody tr:hover { background: var(--bg); }
.table-danger, tr.table-danger { background: var(--red-soft) !important; }
.table-warning, tr.table-warning { background: var(--amber-soft) !important; }
.table-light, thead.table-light tr { background: var(--bg) !important; }

/* ================================================================
   徽章
   ================================================================ */
.badge { font-weight: 600; font-size: 11px; padding: 2px 7px; border-radius: 5px; }
.badge.bg-success { background: var(--primary-soft) !important; color: var(--primary-dark) !important; }
.badge.bg-warning { background: var(--amber-soft) !important; color: #92400e !important; }
.badge.bg-danger { background: var(--red-soft) !important; color: #b91c1c !important; }
.badge.bg-secondary { background: rgba(0,0,0,0.06) !important; color: var(--ink-soft) !important; }
.badge.bg-primary { background: var(--primary-soft) !important; color: var(--primary-dark) !important; }
.badge.bg-info { background: var(--blue-soft) !important; color: #1d4ed8 !important; }
.badge.bg-success-subtle { background: var(--primary-soft); color: var(--primary-dark); }
.badge.bg-danger-subtle { background: var(--red-soft); color: #b91c1c; }
.badge.bg-secondary-subtle { background: rgba(0,0,0,0.06); color: var(--ink-soft); }

/* ================================================================
   按钮
   ================================================================ */
.btn { font-size: 13.5px; font-weight: 500; border-radius: 8px; padding: 6px 14px; transition: all 0.12s; line-height: 1.4; }
.btn-sm { font-size: 12px; padding: 4px 10px; border-radius: 6px; }
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; }
.btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
.btn-outline-primary { color: var(--primary-dark); border-color: var(--primary-muted); }
.btn-outline-primary:hover { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-outline-secondary { color: var(--ink-soft); border-color: var(--border-strong); background: transparent; }
.btn-outline-secondary:hover { background: var(--bg); color: var(--ink); border-color: var(--border-strong); }
.btn-outline-danger { color: var(--red); border-color: #fca5a5; }
.btn-outline-danger:hover { background: var(--red); border-color: var(--red); color: #fff; }
.btn-outline-success { color: var(--primary-dark); border-color: var(--primary-muted); }
.btn-outline-success:hover { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-outline-info { color: var(--blue); border-color: #93c5fd; }
.btn-outline-warning { color: var(--amber); border-color: #fcd34d; }
.btn-warning { background: var(--amber); border-color: var(--amber); color: #fff; font-weight: 600; }

/* ================================================================
   表单
   ================================================================ */
.form-control, .form-select {
  border: 1px solid var(--border-strong);
  border-radius: 8px; font-size: 13.5px;
  padding: 7px 11px;
  background: var(--surface); color: var(--ink);
  transition: border-color 0.12s, box-shadow 0.12s;
}
.form-control:focus, .form-select:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(16,185,129,0.15);
  background: var(--surface); color: var(--ink); outline: none;
}
.form-control-sm, .form-select-sm { font-size: 12.5px; padding: 5px 9px; border-radius: 6px; }
.form-label { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); margin-bottom: 5px; }  /* 2026-09-03 标签醒目化，与下方 v132 规则同口径 */
.form-text { font-size: 11.5px; color: var(--ink-muted); margin-top: 3px; }
.form-check-input:checked { background-color: var(--primary); border-color: var(--primary); }
.form-check-input:focus { box-shadow: 0 0 0 3px rgba(16,185,129,0.15); }

/* ================================================================
   侧边栏子菜单
   ================================================================ */
.orient-card:hover { background: var(--bg); }
.nav-group { margin: 1px 4px; }
.nav-group-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 8px; border-radius: 7px; cursor: pointer; font-size: 12.5px;
  color: var(--sidebar-text); font-weight: 500; user-select: none;
}
.nav-group-header:hover { background: var(--sidebar-hover-bg); }
.nav-group-header .chevron { transition: transform .2s; font-size: 10px; opacity: .5; }
.nav-group-header[aria-expanded="true"] .chevron,
.nav-group-header.expanded .chevron { transform: rotate(180deg); }
.sidebar-sublink {
  display: block; padding: 5px 10px 5px 32px; border-radius: 6px;
  color: var(--sidebar-muted); text-decoration: none;
  margin: 1px 0; font-size: 12.5px;
  transition: background 0.1s, color 0.1s;
}
.sidebar-sublink:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-text); }
.sidebar-sublink.active { background: var(--sidebar-active-bg); color: var(--sidebar-active-text); font-weight: 600; }

/* ================================================================
   进度 / 提醒
   ================================================================ */
.progress { border-radius: 6px; background: var(--border); height: 6px; }
.progress-bar { background: var(--primary); border-radius: 6px; transition: width 0.3s; }

.alert { border-radius: var(--radius); font-size: 13.5px; border: 1px solid transparent; padding: 10px 14px; }
.alert-success { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--primary-muted); }
.alert-danger { background: var(--red-soft); color: #b91c1c; border-color: #fca5a5; }
.alert-warning { background: var(--amber-soft); color: #92400e; border-color: #fcd34d; }
.alert-info { background: var(--blue-soft); color: #1d4ed8; border-color: #93c5fd; }

/* ================================================================
   登录页
   ================================================================ */
.login-shell {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: var(--bg);
}
.login-card {
  width: 400px; background: var(--surface);
  border-radius: var(--radius-xl); border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  padding: 36px 32px 28px;
}
.login-mark {
  width: 38px; height: 38px; border-radius: 9px;
  background: var(--primary);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 800; font-size: 17px;
  margin-bottom: 16px;
}

/* ================================================================
   空状态
   ================================================================ */
.empty-state { text-align: center; padding: 48px 20px; color: var(--ink-muted); }
.empty-state i { font-size: 26px; color: var(--border-strong); margin-bottom: 10px; display: block; }

/* ================================================================
   夜间切换
   ================================================================ */
.theme-btn {
  width: 30px; height: 30px; border-radius: 7px; border: 1px solid var(--border);
  background: transparent; color: var(--ink-soft);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all 0.1s; font-size: 14px; padding: 0;
}
.theme-btn:hover { background: rgba(0,0,0,0.05); color: var(--ink); }

/* ================================================================
   移动端
   ================================================================ */
.sidebar-toggle { display: none; }
@media (max-width: 900px) {
  .sidebar { transform: translateX(-100%); }
  .sidebar.open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.12); }
  .main-area { margin-left: 0; }
  .topbar { left: 0; }
  .page-tabs-bar { left: 0; }
  .sidebar-toggle { display: inline-flex !important; }
  .content { padding: calc(52px + 34px + 8px) 14px 36px; }
}

/* ================================================================
   工具类 & 组件覆盖
   ================================================================ */
hr { border-color: var(--border); margin: 12px 0; opacity: 1; }

.dropdown-menu {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); padding: 4px;
  background: var(--surface); font-size: 13.5px;
}
.dropdown-item { border-radius: 6px; padding: 7px 12px; color: var(--ink); }
.dropdown-item:hover { background: var(--bg); color: var(--ink); }
.dropdown-divider { border-color: var(--border); margin: 4px 0; }

.modal-content { border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-md); background: var(--surface); }
.modal-header { border-bottom: 1px solid var(--border); padding: 16px 20px; }
/* 2026-09-21 现场："弹窗中间灰色不好看"：正文用卡片色、底部却用页面背景色（var(--bg)），一深一浅 —— 统一卡片色 */
.modal-footer { border-top: 1px solid var(--border); padding: 12px 20px; background: var(--surface); border-radius: 0 0 var(--radius-xl) var(--radius-xl); }
.modal-body { background: var(--surface); }
.modal-header { cursor: move; user-select: none; }                 /* 按住标题栏可拖动弹窗 */
.modal-header .btn, .modal-header .btn-close, .modal-header a, .modal-header input { cursor: pointer; }
.modal-body { padding: 18px 20px; }
/* ★ 2026-10-18「检查系统的所有弹窗是否按标准化」：弹窗底部按钮全站统一小号（原来 45 个用 btn-sm、24 个默认大小，同一类区域高矮不一）；
   顺序标准：取消 / 关闭在左、主按钮在最右（辅助动作如「清空全部」「重新起草」加 me-auto 靠最左）；标题统一 .modal-title 16px 600 */
.modal-footer .btn:not(.btn-lg) { --bs-btn-padding-y: .25rem; --bs-btn-padding-x: .6rem; --bs-btn-font-size: .875rem; --bs-btn-border-radius: var(--bs-border-radius-sm); }
.modal-header .modal-title { font-size: 1rem; font-weight: 600; line-height: 1.5; }
/* otPanel：页面自拼的业务弹窗（路由器监控等）—— 外观同标准弹窗，遮罩自带（不走 bootstrap.Modal），宽度按内容 */
.ot-panel { background: rgba(15,23,42,.45); overflow: auto; z-index: 1060; }
.ot-panel .modal-dialog { max-width: none; width: auto; display: flex; justify-content: center; margin: 6vh auto; padding: 0 12px; pointer-events: auto; }
.ot-panel .modal-content { max-width: 96vw; }
.ot-panel .modal-body { max-height: calc(88vh - 120px); overflow: auto; }

.pagination .page-link { border-color: var(--border); color: var(--ink-soft); font-size: 13px; background: var(--surface); }
.pagination .page-item.active .page-link { background: var(--primary); border-color: var(--primary); }

/* 滚动条 */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-muted); }

.text-primary { color: var(--primary) !important; }
.text-secondary, .text-muted { color: var(--ink-soft) !important; }
.bg-light { background: var(--bg) !important; }
.border { border-color: var(--border) !important; }

/* ================================================================
   粘性表头 — 所有表格 thead 固定，滚动时不随页面移动
   ================================================================ */
.table thead th {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg) !important;
  box-shadow: 0 1px 0 var(--border-strong);
  white-space: nowrap;
}

/* DISPATCHED ORDERS 等大表格：容器高度限制+内部滚动 */
.table-scroll-container {
  overflow-y: auto;
  max-height: calc(100vh - 180px);
}
.table-scroll-container thead th {
  position: sticky;
  top: 0;
  z-index: 20;
}

/* ================================================================
   覆盖修正：粘性表头需偏移固定顶栏（topbar 52px + tabs 36px = 88px）
   ================================================================ */
.table thead th {
  top: var(--thead-top, 88px) !important;   /* 2026-09-21：按顶栏 + 标签栏实际底边算（static/layout_guard.js 设置） */
}
/* 表头固定的两种方式（static/layout_guard.js 按表格实际情况二选一） */
.table-responsive.thead-free { overflow-x: visible !important; }        /* 不宽：关掉横向滚动，表头固定在页面顶部 */
.table-responsive.thead-box { overflow: auto; max-height: calc(100vh - var(--thead-top, 88px) - 48px); }
.table-responsive.thead-box > .table > thead th { top: var(--tb-off, 0px) !important; }   /* 区域顶部被顶栏盖住时让到顶栏下沿 */  /* 很宽：表格区域自己滚，表头固定在区域顶部 */
/* 2026-09-21 现场截图："共 0 条…"贴着卡片边框：分页栏直接放在卡片里时没有内边距（17 个列表页共用） */
.card > .pager-bar { padding: 8px 16px 12px; margin-top: 0 !important; }
.lg-pad { padding-left: 16px !important; padding-right: 16px !important; }   /* 排版守护：文字贴边时补内边距 */
/* 在 table-scroll-container 内的表格：容器自己滚动，top=0 */
.table-scroll-container .table thead th {
  top: 0 !important;
}

/* 弹窗(modal)内的表格：弹窗里没有固定顶栏，若沿用 top:88px 会把表头
   整整下推 88px —— 表头跑到内容下面、中间留一条空白。这里必须重置。 */
.modal .table thead th,
.modal-body .table thead th {
  position: static !important;
  top: auto !important;
  z-index: auto !important;
  box-shadow: none;
}

/* card 内部的小表格（如收款规则、关联组织等）不需要粘性表头，重置 top */
.card .table-responsive thead th,
.card .table:not(.table-scroll-container .table) thead th {
  position: relative !important;
  top: auto !important;
  z-index: auto !important;
  box-shadow: none;
}

/* ===== 全局页面工具条：功能按钮从导航栏下移到列表上方，取代原来的搜索栏 ===== */
.page-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 8px 12px; margin-bottom: 12px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
.page-toolbar .ptb-title {
  font-weight: 700; font-size: 14px; color: var(--ink);
  white-space: nowrap; padding-right: 10px; margin-right: 2px;
  border-right: 1px solid var(--border);
}
.page-toolbar .ptb-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.page-toolbar .ptb-extra {
  display: flex; align-items: center; gap: 10px; margin-left: auto;
  font-size: 12px; color: var(--ink-soft, #6e6e73); white-space: nowrap;
}
.page-toolbar .btn { white-space: nowrap; }
#globalSearchModal .gs-field { min-width: 0; }
@media (max-width: 768px) {
  .page-toolbar { padding: 8px; }
  .page-toolbar .ptb-title { display: none; }
  .page-toolbar .ptb-extra { margin-left: 0; width: 100%; }
}

/* ===== Markdown 渲染（数字员工产出、AI 报价/选型/问答共用，见 static/md.js） ===== */
.md-h { font-weight: 600; line-height: 1.5; }
.md-h1 { font-size: 1.18rem; margin: 14px 0 8px; padding-bottom: 5px;
         border-bottom: 2px solid var(--bs-primary, #0d6efd); }
.md-h2 { font-size: 1.04rem; margin: 14px 0 6px; padding-left: 8px;
         border-left: 3px solid var(--bs-primary, #0d6efd); }
.md-h3 { font-size: .96rem; margin: 10px 0 5px; }
.md-h4 { font-size: .92rem; margin: 8px 0 4px; opacity: .85; }
.md-hr { margin: 12px 0; opacity: .25; }
.md-quote { border-left: 3px solid #d9d9d9; padding: 2px 0 2px 10px;
            margin: 6px 0; color: #6c757d; }
.md-list { margin: 6px 0 6px 0; padding-left: 22px; }
.md-list li { margin: 2px 0; }
.md-code { background: rgba(0,0,0,.06); padding: 1px 5px; border-radius: 3px;
           font-size: .92em; }
.md-table-wrap { overflow-x: auto; margin: 8px 0 14px; border-radius: 6px;
                 border: 1px solid rgba(0,0,0,.14); }
.md-table { border-collapse: collapse; font-size: 12.8px; width: 100%;
            background: var(--bs-body-bg, #fff); }
.md-table th, .md-table td { border-right: 1px solid rgba(0,0,0,.09);
                             border-bottom: 1px solid rgba(0,0,0,.09);
                             padding: 6px 10px; vertical-align: top;
                             /* 中文长句没有空格，不加这个会把整列撑爆 */
                             overflow-wrap: anywhere; }
.md-table th:last-child, .md-table td:last-child { border-right: 0; }
.md-table tbody tr:last-child td { border-bottom: 0; }
.md-table th { background: rgba(0,0,0,.05); font-weight: 600; white-space: nowrap;
               border-bottom: 1.5px solid rgba(0,0,0,.16); }
/* 第一列通常是公司名/客户名这类主键，不折行更好对照；表格容器本身可横向滚动 */
.md-table td:first-child { white-space: nowrap; font-weight: 500; }
/* 只有一两列的表不需要撑满整宽，撑满反而稀疏难读 */
.md-table-wrap.md-narrow { display: inline-block; width: auto; max-width: 100%; }
.md-table-wrap.md-narrow .md-table { width: auto; }
.md-table tbody tr:nth-child(even) td { background: rgba(0,0,0,.02); }
.md-table tbody tr:hover td { background: rgba(13,110,253,.07); }
/* 深色主题下的边框和底色 */
body.dark-mode .md-table th, body.dark-mode .md-table td { border-color: rgba(255,255,255,.16); }
body.dark-mode .md-table th { background: rgba(255,255,255,.08); }
body.dark-mode .md-table tbody tr:nth-child(even) td { background: rgba(255,255,255,.03); }
body.dark-mode .md-code { background: rgba(255,255,255,.12); }
body.dark-mode .md-quote { border-left-color: rgba(255,255,255,.2); }

/* ===== 设置页在超宽屏上的可读宽度 =====
   这类页面全是表单，宽屏下把每个输入框拉到一千多像素既难填也难扫读，
   卡片之间的空档还会显得整页很散。限个上限、居中显示，就是常规文档宽度。
   只影响设置页，列表/报表这类需要宽度的页面不受影响。 */
.settings-wrap { max-width: 1500px; }
/* 分组里的卡片统一等高：同一行的卡片撑满该行高度，
   矮卡下面不会留出白条（这是"右边空一块"的根源）。 */
.settings-sec .card { height: 100%; }

/* ================================================================
   列宽自适应：JS 量「内容需要多宽」时用的临时态
   ================================================================
   量的时候把表格切成 table-layout:auto + width:max-content，再强制不换行，
   量到的才是「这列一行放得下要多宽」。不加这条 nowrap 的话，量到的是
   **已经换过行的**宽度 —— 日期挤成两行时它只要 60px，于是越量越窄，
   截图里「2026-07-30 折行、编辑两个字竖排」就会一直修不好。
   这个 class 只在同一帧内存在，用户看不到。 */
.cw-measuring th,
.cw-measuring td { white-space: nowrap !important; }
.cw-measuring .text-truncate { overflow: visible !important; text-overflow: clip !important; }
.cw-measuring td .btn,
.cw-measuring td .badge { white-space: nowrap !important; }

/* 操作列里的按钮、日期、单号本来就不该断开。断开不是"省地方"，
   是把一个 60px 的按钮压成 24px 宽的两行字。 */
.table td .btn,
.table td .badge { white-space: nowrap; }
.table td.mono, .table td .mono { white-space: nowrap; }

/* 列宽引擎按「装不下就横向滚动、绝不压缩」来分配，所以容器必须能横着滚。
   少了这条，宽表只能靠压缩列来塞进容器 —— 又回到换行/竖排字。 */
.table-scroll-container { overflow-x: auto; }

/* 拖拽手柄：现在人人可拖（不再只有管理员），鼠标移上去给点提示 */
.col-rh:hover { background: var(--primary, #10b981) !important; opacity: .35; }

/* ============ 数字输入框：去掉上下箭头（全局） ============
   去掉的理由：这些框里填的是尺寸、重量、件数、数量，靠点箭头一格格调根本没人用；
   箭头反而占掉输入区、挤窄可视宽度，手滑还会误改数值。
   仍然可以用键盘上下键调整，功能没丢，只是不再占地方。

   ★ 放在 style.css 而不是 base.html：弹窗用的 layout_embed.html **不继承 base**，
     写在 base 里的话，就地编辑物料那种弹窗里的数字框仍然带箭头 ——
     而两个布局都引了这个文件，放这儿一处生效。
   ★ 三处都要写，缺一不可：
       -webkit-appearance / display:none  → Chrome、Edge、新版 Safari
       -moz-appearance: textfield         → Firefox（不认上面那两条伪元素）
       appearance: textfield              → 标准属性
   ★ 带 !important：Bootstrap 的 .form-control 会重设 appearance，
     不加的话在部分页面箭头又冒出来。 */
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button {
  -webkit-appearance: none !important;
  appearance: none !important;
  margin: 0 !important;
  display: none !important;
}
input[type=number],
input[type=number].form-control,
input[type=number].form-control-sm {
  -moz-appearance: textfield !important;
  appearance: textfield !important;
}


/* ================================================================
   说明文字统一为两级（第106次现场：字有的大有的很小）
   ----------------------------------------------------------------
   原来同一个页面上并存四五种大小：内联的 11/12/12.5/13px、
   Bootstrap 的 .small(0.875em≈12.25px)、.form-text(0.875em)、
   还有各处 text-secondary 自带的字号 —— 看着就像没对齐。
   归并成两级，全站一致：
     · 正文 14px（body 已定义）
     · 说明/辅助 12px（.small / .form-text / .text-secondary.small 一律 12px）
   只动字号，不动颜色和间距 —— 那些各处有各处的道理，一起改风险太大。
   ================================================================ */
.small, small, .form-text { font-size: 12px !important; }
.table-sm .small, .card-body .small { font-size: 12px !important; }
.form-text { margin-top: .2rem; }

/* 挂进宿主子菜单的其它模块入口（2026-08-31）：缩进成子项模样 */
.nav-attached .sidebar-link.top-link { padding-left: 34px; font-size: 13px; }

/* 表头列宽拖手（2026-08-31）：每列边界画一条看得见的竖线（"I"），悬停加粗变绿——用户才知道往哪拉 */
.th-resizer::after { content: ''; position: absolute; left: 4px; top: 18%; bottom: 18%; width: 1px; background: var(--border); }
.th-resizer:hover::after, .th-resizer.active::after { width: 3px; left: 3px; top: 8%; bottom: 8%; background: var(--primary); border-radius: 2px; }
.table thead th { position: relative; text-align: var(--ui-th-align, left); }
/* 正文对齐（2026-08-31）：带 .text-end/.text-center/.text-start 明确对齐的列（数字列）不受影响 */
.table td:not(.text-end):not(.text-center):not(.text-start) { text-align: var(--ui-td-align, left); }

/* 系统设置页双列（2026-08-31 v92 重做）：JS 按原顺序把卡片放到当前较矮的一列，
   顺序不乱、不留白（column-count 是先填满左列再填右列，阅读顺序上下跳）。 */
/* ★ 设置页排版重设计（2026-09-02 现场："卡片高低不一、大小不一样，手动调整太难用"）：
   原来是 JS 按高度把卡片分进两个 .sm-col（瀑布流）—— 两列各自往下长，天生高低参差，
   还得靠手工拖宽调高去对齐，越调越乱。改成 CSS Grid 硬网格：
   每格等宽（1fr），同一行的卡自动等高（stretch，矮卡拉到与同行高卡一致），
   顺序 = DOM 顺序（排版模式的 ↑↓/拖拽照常管顺序），不需要任何 JS 分列。
   同一分组里的卡从此天生一样宽、一行一样高 —— 统一是默认，不用手调。 */
/* ★ 2026-09-20 现场："并排的大小要一样，上下也要一样；插入的卡片更大，原先的就跟着变大"。
   · grid-template-columns: repeat(2,1fr) —— 两列**等宽**；
   · 等高按**行**、且**看差距**（2026-09-20 现场："高是按行，但如果相差不了多少，
     矮的就空一点"）：同一行两张高度接近时按高的撑齐（矮的留点空白，看着整齐）；
     差得太多就各归各高度，不硬撑 —— 否则小卡会被撑出大片空白。
     判定在 base.html 的 alignRows()（阈值：差距 ≤ 40% 且 ≤ 220px 才对齐）。
     这里只给 align-items:start 作为默认，具体由 JS 按实测高度决定。
   · align-items: stretch + .card{height:100%} —— 卡片撑满自己的格位，不留半截。 */
.settings-masonry { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px; align-items: start; }
/* JS 判定"该对齐"的行，会给这两张卡打上 .sm-even —— 由它撑到同高 */
.settings-masonry > .sm-even, .settings-masonry > .sm-even > .card { height: 100%; }
.settings-masonry > .sm-col { flex: 1 1 0; min-width: 0; }   /* 遗留：老 DOM 里若还有列容器，settingsMasonry 会先拆掉 */
.settings-masonry .col-lg-6, .settings-masonry [class*="col-"] { width: 100%; max-width: 100%; flex: none; padding: 0; margin-bottom: 0; }
.settings-masonry .card, .settings-masonry .card.h-100 { height: 100%; }
/* ★ 2026-10-13 设置页卡片里的表单列：上面那条 .settings-masonry [class*="col-"] 把卡片**里面**的 col-md-6 / col-xl-3 也压成 100%，
   宽卡片（整行宽的）里一个输入框占一整行，又长又空。改成按**卡片自身宽度**（容器查询）套 Bootstrap 列宽：
   卡片 ≥420px 认 col-sm-*，≥560px 认 col-md-*，≥760px 认 col-lg-*，≥960px 认 col-xl-*；窄卡片照旧一列一行。 */
.settings-masonry > [class*="col-"] { container-type: inline-size; }
@container (min-width: 420px) {
  .settings-masonry .row > .col-sm-1 { flex: 0 0 auto; width: 8.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-2 { flex: 0 0 auto; width: 16.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-3 { flex: 0 0 auto; width: 25.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-4 { flex: 0 0 auto; width: 33.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-5 { flex: 0 0 auto; width: 41.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-6 { flex: 0 0 auto; width: 50.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-7 { flex: 0 0 auto; width: 58.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-8 { flex: 0 0 auto; width: 66.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-9 { flex: 0 0 auto; width: 75.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-10 { flex: 0 0 auto; width: 83.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-11 { flex: 0 0 auto; width: 91.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-sm-12 { flex: 0 0 auto; width: 100.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
}
@container (min-width: 560px) {
  .settings-masonry .row > .col-md-1 { flex: 0 0 auto; width: 8.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-2 { flex: 0 0 auto; width: 16.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-3 { flex: 0 0 auto; width: 25.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-4 { flex: 0 0 auto; width: 33.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-5 { flex: 0 0 auto; width: 41.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-6 { flex: 0 0 auto; width: 50.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-7 { flex: 0 0 auto; width: 58.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-8 { flex: 0 0 auto; width: 66.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-9 { flex: 0 0 auto; width: 75.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-10 { flex: 0 0 auto; width: 83.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-11 { flex: 0 0 auto; width: 91.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-md-12 { flex: 0 0 auto; width: 100.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
}
@container (min-width: 760px) {
  .settings-masonry .row > .col-lg-1 { flex: 0 0 auto; width: 8.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-2 { flex: 0 0 auto; width: 16.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-3 { flex: 0 0 auto; width: 25.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-4 { flex: 0 0 auto; width: 33.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-5 { flex: 0 0 auto; width: 41.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-6 { flex: 0 0 auto; width: 50.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-7 { flex: 0 0 auto; width: 58.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-8 { flex: 0 0 auto; width: 66.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-9 { flex: 0 0 auto; width: 75.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-10 { flex: 0 0 auto; width: 83.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-11 { flex: 0 0 auto; width: 91.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-lg-12 { flex: 0 0 auto; width: 100.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
}
@container (min-width: 960px) {
  .settings-masonry .row > .col-xl-1 { flex: 0 0 auto; width: 8.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-2 { flex: 0 0 auto; width: 16.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-3 { flex: 0 0 auto; width: 25.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-4 { flex: 0 0 auto; width: 33.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-5 { flex: 0 0 auto; width: 41.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-6 { flex: 0 0 auto; width: 50.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-7 { flex: 0 0 auto; width: 58.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-8 { flex: 0 0 auto; width: 66.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-9 { flex: 0 0 auto; width: 75.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-10 { flex: 0 0 auto; width: 83.3333%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-11 { flex: 0 0 auto; width: 91.6667%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
  .settings-masonry .row > .col-xl-12 { flex: 0 0 auto; width: 100.0000%; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); }
}
@media (max-width: 991px) { .settings-masonry { grid-template-columns: minmax(0, 1fr); } }


/* ============================================================
   设计规范 · 观感层（2026-09-01）
   四件事：① 字体层级 ② 间距标尺 ③ 少边框多层次 ④ 颜色克制。
   全部基于变量，主题切换后自动跟随；不改任何布局与功能。
   ============================================================ */
:root {
  /* ① 间距标尺：一律 4 的倍数，杜绝 11px/9px 这种随手值 */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px;
  /* ③ 层次：卡片改用极淡阴影 + 极淡边框，页面不再被一堆 1px 灰线切碎 */
  --card-border: rgba(0,0,0,.06);
  --card-shadow: 0 1px 2px rgba(16,24,40,.04), 0 1px 3px rgba(16,24,40,.06);
  --card-shadow-hover: 0 2px 4px rgba(16,24,40,.05), 0 4px 12px rgba(16,24,40,.08);
  --row-hover: rgba(0,0,0,.022);
}
body.dark-mode {
  --card-border: rgba(255,255,255,.08);
  --card-shadow: 0 1px 2px rgba(0,0,0,.4);
  --card-shadow-hover: 0 4px 14px rgba(0,0,0,.5);
  --row-hover: rgba(255,255,255,.04);
}

/* ① 字体层级：标题 600 / 正文 400 / 辅助 12px 灰；数字等宽对齐 */
h1, h2, h3, h4, h5, h6, .page-title { font-weight: 600; letter-spacing: -.01em; }
.card-header { font-weight: 700; color: var(--ink); letter-spacing: -.005em; }  /* 2026-09-03 抬头醒目化 */
.text-secondary, .form-text, small, .small { letter-spacing: 0; }
/* 等宽数字只给数字列（右对齐列 / .mono / 数字输入框）：给整表每个格子都设字体特性没必要 */
td.text-end, th.text-end, .mono, input[type="number"], .num {
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
/* 金额/数量列用等宽数字后，同一列的个位天然对齐，不再靠肉眼 */

/* ② 间距标尺 */
.card { margin-bottom: var(--sp-4); }
.card-header { padding: var(--sp-3) var(--sp-4); }
.card-body { padding: var(--sp-4); }
.card-footer { padding: var(--sp-3) var(--sp-4); }

/* ③ 少边框、多层次 */
/* 卡片只保留静态的淡阴影：hover 阴影动画在设置页那种几十张卡的页面上不划算 */
.card { border: 1px solid var(--card-border); box-shadow: var(--card-shadow); }
.card-header { border-bottom: 1px solid var(--card-border); background: transparent; }
.table thead th { border-bottom: 1px solid var(--card-border); background: var(--bg); }
.table td { border-top: 1px solid var(--card-border); }
/* ★ 2026-09-01 性能回退：行级 transition 在几百上千行的表上会明显拖慢滚动与悬停
   （每行都要维护动画状态）。悬停变色本来就是瞬时的，去掉过渡观感无差别。 */
.table-hover tbody tr:hover, .table tbody tr:hover { background: var(--row-hover); }
.table > :not(caption) > * > * { box-shadow: none; }   /* 去掉 bootstrap 的行内阴影线 */

/* ④ 颜色克制：主色只留给"主要动作"，次要动作一律中性 */
.btn-outline-secondary { color: var(--ink-soft); border-color: var(--border-strong); }
.btn-outline-secondary:hover { background: var(--bg); color: var(--ink); border-color: var(--border-strong); }
.badge { font-weight: 600; letter-spacing: .01em; }
/* 状态徽章保持语义色；纯计数徽章降为中性，避免满屏彩色 */
.badge.bg-secondary-subtle { color: var(--ink-soft) !important; }
a { text-underline-offset: 2px; }
.form-control, .form-select { border-color: var(--border); }
.form-control:focus, .form-select:focus {
  border-color: var(--primary-muted);
  box-shadow: 0 0 0 3px var(--primary-soft);
}
/* 空状态别太抢眼 */
.empty-state { color: var(--ink-muted); }
.empty-state i { font-size: 28px; opacity: .5; }


/* ============================================================
   排版与控件质感（2026-09-01 v132）
   换配色救不了观感 —— 真正的差距在：字重层级、留白节奏、控件长相、卡片分级。
   以下全部走变量，任何主题下都成立。
   ============================================================ */

/* — 层级：标题小而重、辅助文字更淡更小，拉开视觉重量 — */
.card-header { font-size: 13px; letter-spacing: .01em; }
.card-header .text-secondary, .card-header .small { font-weight: 400; }
/* ★ 字段标签与抬头醒目化（2026-09-03 现场指令："所有模块的字段、抬头都要黑色加粗，
   灰色第一眼看不到"）：.form-label 原来是 11.5px / 500 / 淡墨灰 —— 表单里满屏浅灰小字，
   用户找不到"这一格是填什么的"。统一改为主墨色 + 加粗；颜色走 --ink 变量而不是写死
   #000 —— 深色主题下 --ink 是近白，写死黑色在暗色模式里就消失了。
   同一道理套到卡片抬头与表头（见下方各自的规则）。 */
.form-label, label.form-label {
  font-size: var(--fs-sm); font-weight: 700; color: var(--ink);
  letter-spacing: .02em; margin-bottom: 4px; text-transform: none;
}
.form-check-label { color: var(--ink); font-weight: 600; }
.form-text { font-size: 11.5px; color: var(--ink-muted); margin-top: 4px; }

/* — 控件：统一高度与圆角，去掉 Bootstrap 的默认廉价感 — */
.form-control, .form-select {
  min-height: 34px; padding: 6px 10px; border-radius: 8px;
  font-size: 13px; background: var(--surface); color: var(--ink);
  border: 1px solid var(--border); box-shadow: none;
}
.form-control-sm, .form-select-sm { min-height: 30px; padding: 4px 9px; font-size: 12.5px; border-radius: 7px; }
.form-control:hover, .form-select:hover { border-color: var(--primary-muted); }
.form-control::placeholder { color: var(--ink-muted); opacity: .75; }
.input-group-text { border-radius: 8px; background: var(--bg); border-color: var(--border); font-size: 12.5px; }

/* — 按钮：主按钮有分量，次按钮退到中性；统一圆角与内距 — */
.btn { border-radius: 8px; font-size: 13px; padding: 6px 14px; font-weight: 500; letter-spacing: .01em; }
.btn-sm { border-radius: 7px; font-size: 12.5px; padding: 4px 11px; }
.btn-primary { border: none; box-shadow: 0 1px 2px rgba(16,24,40,.12); }
.btn-primary:hover { filter: brightness(1.06); box-shadow: 0 2px 6px rgba(16,24,40,.18); }
.btn-outline-secondary, .btn-outline-primary { border-width: 1px; }
.btn:focus, .btn:focus-visible { box-shadow: 0 0 0 3px var(--primary-soft); outline: none; }

/* — 卡片分级：主卡片正常，次要区域收成浅底块，不再一律平铺 — */
.card { border-radius: var(--radius-lg, 10px); }
.card-header { display: flex; align-items: center; gap: 8px; }
.card .card-header + .card-body { padding-top: var(--sp-4, 16px); }
.alert { border-radius: 10px; border-width: 1px; font-size: 12.5px; padding: 10px 12px; }

/* — 表格：更透气的表头、更轻的分隔、行首列留白 — */
.table thead th {
  /* 2026-09-03 抬头醒目化（同上一处）：这条在层叠里更靠后、才是生效的那条 —— 一起改 */
  font-size: 11.5px; font-weight: 700; letter-spacing: .03em;
  color: var(--ink); text-transform: none; padding-top: 9px; padding-bottom: 9px;
}
.table tbody td { font-size: 13px; }
.table tbody tr:last-child td { border-bottom: 0; }

/* — 徽章/标签：小而克制 — */
.badge { border-radius: 6px; padding: 3px 8px; font-size: 11px; font-weight: 600; }

/* — 指标卡：一眼看到数字，标签退居其次（仪表盘样板） — */
.stat-card {
  border-radius: 14px; padding: 16px 18px; background: var(--surface);
  border: 1px solid var(--card-border); box-shadow: var(--card-shadow);
  position: relative; overflow: hidden;
}
.stat-card::before {          /* 顶部一道极细的主色渐变，只在指标卡出现 */
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--primary), transparent 70%); opacity: .85;
}
.stat-card .stat-label { font-size: 11.5px; color: var(--ink-muted); letter-spacing: .02em; margin-bottom: 6px; }
.stat-card .stat-value {
  font-size: 26px; font-weight: 650; line-height: 1.15; letter-spacing: -.02em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.stat-card .stat-icon {
  position: absolute; right: 14px; top: 14px; font-size: 18px;
  color: var(--primary); opacity: .28;
}
.stat-card.accent-red::before { background: linear-gradient(90deg, var(--red), transparent 70%); }
.stat-card.accent-red .stat-icon { color: var(--red); }
.stat-card.accent-blue::before { background: linear-gradient(90deg, var(--blue), transparent 70%); }
.stat-card.accent-blue .stat-icon { color: var(--blue); }


/* ============================================================
   AI 驾驶舱视觉（2026-09-01 v134）
   ① 玻璃拟态只给弹窗/下拉/AI 卡片（小面积、底下有内容可透）——
      大面积毛玻璃在表格页会既糊又费性能，不做。
   ② AI 专属高亮色：主色沉稳，智能相关组件用 --ai-accent 系，一眼可辨。
   ③ 驾驶舱网格：非对称错落 + 充足留白。
   变量缺省值兜底，任何主题下都不会崩。
   ============================================================ */
:root {
  --ai-accent: var(--primary);
  --ai-accent-2: var(--blue);
  --ai-accent-3: #06b6d4;
  --glass-bg: rgba(255,255,255,.72);
  --glass-border: rgba(255,255,255,.55);
}

/* ① 玻璃拟态：弹窗、下拉、AI 卡片 */
.modal-content, .dropdown-menu, .offcanvas, .ai-card {
  background: var(--glass-bg);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--glass-border);
  box-shadow: 0 8px 32px rgba(11,18,32,.14);
}
@supports not (backdrop-filter: blur(1px)) {      /* 不支持就退回实色，不糊 */
  .modal-content, .dropdown-menu, .offcanvas, .ai-card { background: var(--surface); }
}

/* ② AI 高亮：给智能相关组件用 */
.ai-accent, .ai-badge, .ds-flow .dsf-agent .dsf-t i { color: var(--ai-accent); }
.ai-badge {
  display: inline-flex; align-items: center; gap: 4px;
  background: linear-gradient(135deg, var(--ai-accent), var(--ai-accent-2));
  color: #fff; border-radius: 999px; padding: 2px 9px; font-size: 11px; font-weight: 600;
}
.ai-card { border-radius: 16px; padding: 16px 18px; position: relative; overflow: hidden; }
.ai-card::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--ai-accent), var(--ai-accent-2) 45%, var(--ai-accent-3) 80%, transparent);
}
.ai-glow { box-shadow: 0 0 0 1px color-mix(in srgb, var(--ai-accent) 25%, transparent),
                       0 6px 24px color-mix(in srgb, var(--ai-accent) 14%, transparent); }
@supports not (color: color-mix(in srgb, red, blue)) {
  .ai-glow { box-shadow: 0 6px 24px rgba(124,58,237,.16); }
}

/* ③ 驾驶舱网格：非对称错落，留白充足 */
.cockpit-grid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(12, minmax(0, 1fr));
}
.cockpit-grid > .g-2 { grid-column: span 2; }
.cockpit-grid > .g-3 { grid-column: span 3; }
.cockpit-grid > .g-4 { grid-column: span 4; }
.cockpit-grid > .g-5 { grid-column: span 5; }
.cockpit-grid > .g-6 { grid-column: span 6; }
.cockpit-grid > .g-7 { grid-column: span 7; }
.cockpit-grid > .g-8 { grid-column: span 8; }
.cockpit-grid > .g-12 { grid-column: span 12; }
@media (max-width: 1200px) { .cockpit-grid > [class^="g-"] { grid-column: span 6; } }
@media (max-width: 1200px) { .cockpit-grid > .g-2 { grid-column: span 4; } }
@media (max-width: 768px)  { .cockpit-grid > [class^="g-"] { grid-column: span 12; } }
@media (max-width: 768px)  { .cockpit-grid > .g-2 { grid-column: span 6; } }
/* 驾驶舱里的卡片更松弛：留白即高级感 */
.cockpit-grid .card-body { padding: 18px 20px; }
.cockpit-grid .stat-card { padding: 18px 20px; }

/* 排版模式下双列被解散：卡片改为单列铺开，便于逐张排序与调宽（2026-09-01） */
.settings-masonry.masonry-off { display: block; }
.settings-masonry.masonry-off > * { width: 100%; margin-bottom: 16px; }

/* 排版模式：卡片可拖拽换位（2026-09-01） */
.layout-editing .layout-draggable { cursor: grab; }
.layout-editing .layout-draggable.dragging { opacity: .45; cursor: grabbing; }
.layout-editing .layout-draggable.drop-hint { outline: 2px dashed var(--primary); outline-offset: 2px; }

/* 调宽后自动回流（2026-09-01 v145）
   现场：把上一行的卡片调窄后，右边空出位置，下面那张却不会自动补上来 ——
   因为 cockpit-grid 是网格、每张卡固定占 N 列，改宽度不改格位。
   办法：容器里只要有卡片被手工调过宽（.w-tuned），整个容器切成 flex 自动换行，
   卡片按实际宽度依次排列，空间够就自动上移；没调过宽的页面完全不受影响。 */
.cockpit-grid.reflow, .settings-masonry.masonry-off.reflow, .reflow-container {
  display: flex !important; flex-wrap: wrap; align-items: flex-start; gap: 16px;
}
.cockpit-grid.reflow > *, .settings-masonry.masonry-off.reflow > *, .reflow-container > * {
  grid-column: auto !important; margin-bottom: 0;
}
/* 没被调过宽的卡片在回流容器里保持原比例，不至于挤成一条 */
.cockpit-grid.reflow > :not(.w-tuned) { flex: 1 1 260px; min-width: 240px; }
.reflow-container > :not(.w-tuned) { flex: 1 1 260px; min-width: 240px; }
.w-tuned { flex: 0 0 auto !important; }


/* ============================================================
   字号标尺与合计行（2026-09-01 v146）
   现场：模板里写死了 661 处 font-size，从 8px 到 18px 什么都有，
   于是"系统的字体不一样"。这里定标尺 + 统一归并：
   模板里的 font-size 一律让位给这套规则（!important 是必要的恶，
   否则 661 处内联样式各说各话，全局设置永远不生效）。
   ============================================================ */
:root {
  --fs-xs:   11px;   /* 徽章、极辅助 */
  --fs-sm:   12px;   /* 表格正文、辅助说明 */
  --fs-base: 13px;   /* 表单控件、正文 */
  --fs-md:   14px;   /* 卡片标题、强调正文 */
  --fs-lg:   16px;   /* 小标题 */
  --fs-xl:   20px;   /* 页面标题 */
  --fs-total: 14px;  /* （遗留）合计行曾比正文大一档；2026-09-02 起合计与正文同字号，此档不再被表格引用 */
}

/* 正文区里写死的小字号统一归并到标尺（保留 stat-value、图标等特例） */
.content [style*="font-size:9px"], .content [style*="font-size: 9px"],
.content [style*="font-size:8px"], .content [style*="font-size: 8px"],
.content [style*="font-size:10px"], .content [style*="font-size: 10px"],
.content [style*="font-size:10.5px"], .content [style*="font-size: 10.5px"] {
  font-size: var(--fs-xs) !important;
}
.content [style*="font-size:11px"], .content [style*="font-size: 11px"],
.content [style*="font-size:11.5px"], .content [style*="font-size: 11.5px"],
.content [style*="font-size:12px"], .content [style*="font-size: 12px"],
.content [style*="font-size:12.5px"], .content [style*="font-size: 12.5px"] {
  font-size: var(--fs-sm) !important;
}
.content [style*="font-size:13px"], .content [style*="font-size: 13px"],
.content [style*="font-size:13.5px"], .content [style*="font-size: 13.5px"] {
  font-size: var(--fs-base) !important;
}
.content [style*="font-size:14px"], .content [style*="font-size: 14px"] {
  font-size: var(--fs-md) !important;
}
.content [style*="font-size:15px"], .content [style*="font-size: 15px"],
.content [style*="font-size:16px"], .content [style*="font-size: 16px"] {
  font-size: var(--fs-lg) !important;
}
/* 指标卡数字、页面大标题（18px+）不参与归并 */
.content .stat-value, .content h1, .content h2, .content h3 { font-size: revert !important; }
.stat-card .stat-value { font-size: 26px !important; }

/* 表格：一个字号（2026-09-02 现场）：原来正文 12、表头 11、合计强制 14 加粗 ——
   同一张表里三种字号，加上金额列是等宽字体在 12px 下显得更小，
   用户对照别家 CRM 的原话是"求和字体大、数字很小"。
   现在表头、正文、数字、徽章全部走 --fs-base（随「外观-字号」整体缩放），
   层级改用字重和底色来拉：表头靠大写+加粗+灰底、合计靠加粗+色底，字号不再参与。 */
.table tbody td, .table tbody th { font-size: var(--fs-base); }
.table thead th { font-size: var(--fs-base); }
.table .badge { font-size: var(--fs-base); padding: 2px 8px; }

/* ★ 合计行：同字号、只靠加粗和底色识别（原来还要加大一档，是"字体不一样"的来源之一） */
.table tfoot td, .table tfoot th,
.table tr.total-row td, .table tr.total-row th,
.table tr.sum-row td, .table tr.sum-row th {
  font-size: var(--fs-base) !important;
  font-weight: 700 !important;
  background: var(--primary-soft) !important;
  color: var(--ink) !important;
  border-top: 2px solid var(--primary-muted) !important;
}
.table tfoot td.text-end, .table tr.total-row td.text-end {
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}

/* 卡片高度档位（2026-09-02）：选"统一 N"后，内容多的卡片内部滚动，页面不再高低不齐 */
.content .card.h-tunable, .cockpit-grid .card, .cockpit-grid .stat-card {
  min-height: var(--ui-card-h, auto);
}
.cockpit-grid .card, .content .card.h-tunable {
  display: flex; flex-direction: column;
}
.cockpit-grid .card > .card-body, .content .card.h-tunable > .card-body {
  flex: 1 1 auto; overflow: auto;
}
/* 排版模式下也能单独拖高度（见 base.html 的下边缘手柄） */
.layout-edge-b { position: absolute; left: 0; right: 0; bottom: -3px; height: 8px; cursor: ns-resize;
                 z-index: 20; background: linear-gradient(to bottom, transparent, rgba(74,139,245,.35)); }
.layout-edge-b:hover { background: rgba(74,139,245,.55); }

/* 手工调过高的卡片：压过主题/档位设的 min-height，并让内容区滚动（2026-09-02） */
.h-tuned { overflow: auto !important; }
.h-tuned > .card-body, .h-tuned .card-body { overflow: auto; }
.cockpit-grid .card.h-tuned, .content .card.h-tuned { min-height: 0 !important; }

/* 排版模式：表单字段可拖拽换位（2026-09-02） */
.layout-editing .field-draggable { cursor: grab; outline: 1px dashed transparent; }
.layout-editing .field-draggable:hover { outline-color: var(--primary-muted); outline-offset: 2px; }
.layout-editing .field-draggable.dragging { opacity: .45; }
.layout-editing .field-draggable.drop-hint { outline: 2px dashed var(--primary); outline-offset: 2px; }


/* ── 指标卡:外框与白卡尺寸对齐（2026-09-02 v172）────────────────────
   现场：排版模式拖外框（.g-3/.col-*）时，里面那张白卡不跟着变 —— 因为白卡是
   子元素、有自己的宽高。让白卡永远填满外框，拖外框就等于拖白卡。 */
.cockpit-grid > [class^="g-"], .cockpit-grid > [class*="col-"] { display: flex; }
.cockpit-grid > [class^="g-"] > .stat-card,
.cockpit-grid > [class^="g-"] > .card,
.cockpit-grid > [class*="col-"] > .stat-card,
.cockpit-grid > [class*="col-"] > .card { width: 100%; height: 100%; }
/* 手工调过高的外框：白卡跟着满高，内容超出时在白卡内滚动 */
.h-tuned > .stat-card, .h-tuned > .card { height: 100% !important; overflow: auto; }

/* 指标卡本身的造型统一：四张卡等高、数字对齐、标签不换行挤压 */
.stat-card {
  min-height: 104px;                    /* 四张卡等高，不会因文字长短高低不齐 */
  display: flex; flex-direction: column; justify-content: center;
}
.stat-card .stat-label {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding-right: 34px;                  /* 给右上角图标让位，长标签不压到图标下面 */
}
.stat-card .stat-value {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;   /* 金额等宽，四张卡的数字竖着看是齐的 */
}
.stat-card .form-text { margin-top: 2px; }
@media (max-width: 1400px) { .stat-card .stat-value { font-size: 22px; } }
@media (max-width: 1100px) { .stat-card .stat-value { font-size: 19px; } }

/* ★ 2026-09-04 现场：组「挂进」另一个组后，不再显示自己的组名 ——
   子项直接平铺在父组里（既然挂进了邮件收发，"邮箱服务器"这个名字就多余了）。
   通用规则：任何被挂进去的组，隐藏它自己的标题行、折叠层强制展开、去掉组外边距。 */
.nav-attached .nav-group { margin: 0; }
.nav-attached .nav-group > .nav-group-header { display: none; }
.nav-attached .nav-group > .collapse { display: block !important; height: auto !important; }
.nav-attached .nav-group > .collapsing { display: block !important; height: auto !important; }

/* 左下角版本号（2026-09-04）：跟着侧边栏滚动到底，收窄侧栏时也不遮内容 */
.sidebar-build { padding: 8px 14px 14px; font-size: 11px; opacity: .75; letter-spacing: .2px; }
.sidebar-build i { font-size: 11px; margin-right: 4px; }

/* ★ 数据源 API 集中区（2026-09-05）：每家一条横向长条 —— 表单字段横排、按钮靠右，
   不改各家表单结构，只改排布。 */
.api-strips .card { margin-top: .5rem !important; }
.api-strips .card-body > form { display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: flex-end; }
.api-strips .card-body > form > div { flex: 1 1 260px; margin-bottom: 0 !important; }
.api-strips .card-body > form > div.form-check { flex: 0 0 auto; }
.api-strips .card-body > form > button,
.api-strips .card-body > form > a.btn { flex: 0 0 auto; margin-bottom: 2px; }
.api-strips .card-body > form > div > .form-text { margin-top: 2px; font-size: 11px; }
.api-strips .card-body > .alert { margin-bottom: 6px; }

/* ═══════ 侧边栏图标配色（2026-09-09 现场："图标需要加上颜色，这样看起来太low了"）═══════
   ★ 按**图标类名**上色，不按菜单顺序：顺序是用户可以在「侧边栏设置」里随便调的，
     按位置上色的话，他一调顺序颜色就全乱了。
   ★ 同一类业务用同一个色系（钱=绿、客户=蓝、货=橙、系统=灰蓝…），
     这样颜色是"有意义的分组"，而不是一堆彩色斑点 —— 后者比全灰更 low。
   ★ 2026-09-10 现场补："展开后第二阶级的图标没有颜色" —— 之前只写了一级链接和
     分组标题两种选择器，漏了子项 .sidebar-sublink。三种一起用 :is() 收成一份，
     以后再加新的容器类只要往这个括号里加一次，不必把整张配色表复制一遍。 */
:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i {
  transition: color 0.1s;
}
:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-speedometer, .bi-speedometer2, .bi-grid-1x2, .bi-grid-1x2-fill
) { color: #4f7cff; }                                        /* 仪表盘 / 驾驶舱 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-people, .bi-people-fill, .bi-person-lines-fill, .bi-person-vcard,
  .bi-person-badge, .bi-person-circle, .bi-radar, .bi-megaphone-fill
) { color: #2f8fef; }                                        /* 客户与人 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-cash-coin, .bi-cash-stack, .bi-bank, .bi-receipt, .bi-calculator,
  .bi-pie-chart, .bi-graph-up, .bi-graph-up-arrow, .bi-globe2, .bi-water
) { color: #17a673; }                                        /* 钱与资产 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-box-seam, .bi-boxes, .bi-truck, .bi-upc-scan, .bi-grid-3x3,
  .bi-box-arrow-left, .bi-box-arrow-in-down, .bi-tags
) { color: #e8833a; }                                        /* 货与仓 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-envelope-paper, .bi-envelope-at, .bi-envelope-arrow-down, .bi-mailbox,
  .bi-chat-left-dots, .bi-chat-dots-fill, .bi-send
) { color: #7a6ff0; }                                        /* 邮件与消息 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-file-earmark-text, .bi-file-earmark-text-fill, .bi-file-earmark-ruled,
  .bi-file-earmark-arrow-down, .bi-journal-text, .bi-folder-fill,
  .bi-folder2-open, .bi-book-half, .bi-layout-three-columns
) { color: #b8863b; }                                        /* 文档与档案 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-check2-square, .bi-clipboard-check, .bi-clipboard-data, .bi-list-check,
  .bi-patch-check, .bi-shield-check, .bi-clock-history, .bi-hourglass-split,
  .bi-exclamation-triangle-fill
) { color: #d9534f; }                                        /* 审批与合规 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-robot, .bi-diagram-3, .bi-diagram-3-fill, .bi-diagram-2, .bi-linkedin,
  .bi-search, .bi-rocket-takeoff, .bi-lightbulb
) { color: #00a3a3; }                                        /* AI 与检索 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-gear, .bi-gear-fill, .bi-sliders, .bi-shield-lock, .bi-hdd-network,
  .bi-database, .bi-buildings, .bi-building, .bi-palette, .bi-translate,
  .bi-git, .bi-plus-square, .bi-eraser
) { color: #6b7a99; }                                        /* 系统与管理 */

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-bar-chart, .bi-bar-chart-fill, .bi-bar-chart-line, .bi-calendar-check,
  .bi-calendar2-check, .bi-arrow-left-right, .bi-globe-asia-australia,
  .bi-clock, .bi-list-ul, .bi-geo-alt
) { color: #c2569a; }                                        /* 报表与计划 */


/* ★ 2026-10-07 现场：「后面增加的模块图标颜色没有加上去」—— 外贸工作台 / 路由器监控 / 家庭答题 / 通知等 44 个图标不在配色表里，显示成灰色。
   按原分组补齐；家庭答题自成一组（琥珀色）。以后新加菜单图标没配色，模块覆盖监控会报出来（_coverage_report）。 */
:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-person-gear, .bi-person-bounding-box, .bi-globe-americas
) { color: #2f8fef; }                                        /* 客户与人（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-currency-exchange, .bi-percent
) { color: #17a673; }                                        /* 钱与资产（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-box, .bi-box-arrow-in-right, .bi-box-arrow-up, .bi-upc, .bi-airplane, .bi-train-front, .bi-truck-front, .bi-list-columns, .bi-house-door
) { color: #e8833a; }                                        /* 货与仓（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-chat-dots, .bi-wechat, .bi-inbox, .bi-bell
) { color: #7a6ff0; }                                        /* 邮件与消息（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-book, .bi-journal-bookmark, .bi-journal-check, .bi-journal-x, .bi-images, .bi-file-code, .bi-input-cursor-text
) { color: #b8863b; }                                        /* 文档与档案（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-cpu
) { color: #00a3a3; }                                        /* AI 与检索（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-hdd, .bi-hdd-rack, .bi-key, .bi-terminal, .bi-fingerprint, .bi-layout-sidebar, .bi-router, .bi-cloud-arrow-down, .bi-cloud-upload-fill, .bi-pc-display, .bi-life-preserver, .bi-activity
) { color: #6b7a99; }                                        /* 系统与管理（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-mortarboard, .bi-house-heart, .bi-trophy, .bi-gift, .bi-play-btn, .bi-camera-video, .bi-paperclip
) { color: #f0a020; }                                        /* 家庭答题（学习）（补）*/

:is(.sidebar-link, .nav-group-header, .sidebar-sublink) i:is(
  .bi-droplet
) { color: #0ea5e9; }                                        /* 桶装水订水（2026-10-13）*/

/* ★ 状态色要压在配色之后重新声明：选中/悬停时图标必须跟着变，
   否则选中项的图标还是原色，看不出被选中 —— 那比没上色更糟。 */
:is(.sidebar-link, .nav-group-header, .sidebar-sublink):hover i { color: var(--ink-soft); }
:is(.sidebar-link, .nav-group-header, .sidebar-sublink).active i { color: var(--sidebar-active-text); }

/* ═══════ 基础设置页排版（2026-09-11 现场："全部重新排版，要求好看、美观"）═══════
   ★ 只动**外观**，不动结构与任何表单字段 —— 设置页每张卡都是一个独立表单，
     动结构就可能把某个 name 弄丢，而"某个开关保存不了"是最难被发现的一类故障。
     所以这里全部用 CSS 收：间距、卡片、标题、标签栏、栅格。
   ★ 两个具体毛病先解决：
     ① 卡片挤在一起、标题和正文一样重，一屏看下来全是灰字，找不到落点；
     ② 标签栏滚动后就看不见了，切换得先滚回顶部。 */

/* 标签栏吸顶：切换不用滚回去 */
#settingsTabs { position: sticky; top: 0; z-index: 21; }
#secTabs { position: sticky; top: 52px; z-index: 20; }
#secTabs, #settingsTabs {
  background: var(--surface, #fff);
  padding: 8px 0; margin-bottom: 4px;
  border-bottom: 1px solid var(--border, #e5e7eb);
}
#settingsTabs .nav-link, #secTabs .nav-link {
  border-radius: 8px; font-size: 13px; padding: 6px 12px;
  color: var(--ink-soft, #5b6472); transition: background .12s, color .12s;
}
#settingsTabs .nav-link:hover, #secTabs .nav-link:hover { background: var(--bg-soft, #f4f6f8); }
#settingsTabs .nav-link.active, #secTabs .nav-link.active {
  background: var(--brand-soft, #e8f3ee); color: var(--brand, #17a673); font-weight: 600;
}

/* 分组标题：加一条主色竖线，扫一眼就知道分了几块 */
.settings-sec > .d-flex > h6,
.settings-wrap > .d-flex > h6 {
  padding-left: 10px; border-left: 3px solid var(--brand, #17a673);
  line-height: 1.2;
}

/* 卡片：轻边框 + 悬停微抬，标题与正文拉开层次 */
.settings-masonry .card {
  /* Grid 自己有 gap，卡片再带 mb-3 会和 gap 叠加、行距不均，看着两张不齐 */
  margin-bottom: 0 !important;
  border: 1px solid var(--border, #e8eaed);
  border-radius: 12px; box-shadow: none;
  transition: box-shadow .14s, border-color .14s, transform .14s;
}
.settings-masonry .card:hover {
  border-color: var(--brand-soft, #cfe7dc);
  box-shadow: 0 2px 10px rgba(16, 24, 40, .06);
}
.settings-masonry .card > .card-header {
  background: transparent; border-bottom: 1px solid var(--border, #eef0f2);
  font-weight: 600; font-size: 13.5px; padding: 12px 16px;
  display: flex; align-items: center; gap: 6px;
}
.settings-masonry .card > .card-body { padding: 14px 16px; }
.settings-masonry .card .form-text { font-size: 11.5px; line-height: 1.5; }
.settings-masonry .card .form-label { font-size: 12.5px; margin-bottom: 2px; }

/* 栅格：宽屏放三列 —— 设置卡大多是一两行输入，两列会拖得很长 */
@media (min-width: 1400px) {
  .settings-masonry { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
}

/* 危险操作那一片：底色提醒，别让它和普通卡长一个样 */
#tab-danger .card { border-color: var(--red-soft, #f3c9c4); }
#tab-danger .card > .card-header { color: var(--red, #d9534f); }

/* 保存按钮统一贴底对齐，卡片高度不齐时不会东一个西一个 */
.settings-masonry .card .btn-primary { align-self: flex-start; }


/* ═══════ 图表定高容器（2026-09-11 现场："曲线图下面空出一截，跟饼图要匹配"）═══════
   ★ 老写法是 <canvas height="110">：那是**按比例撑开**，一行里放两张图时
     各撑各的，谁都对不齐，矮的那张下面就空一截。
   ★ 改成容器定高 + 图表填满（配 maintainAspectRatio:false），
     同一行的图从此天然等高；窄屏自动降低，不至于把手机屏占满。 */
.chart-box { position: relative; height: 300px; }
.chart-box > canvas { position: absolute; inset: 0; width: 100% !important; height: 100% !important; }
.chart-box.chart-sm { height: 200px; }
.chart-box.chart-lg { height: 360px; }
@media (max-width: 991px) {
  .chart-box { height: 240px; }
  .chart-box.chart-sm { height: 180px; }
  .chart-box.chart-lg { height: 280px; }
}

/* ★ 2026-09-20：AI 设置页从固定两栏改用 .settings-masonry 等宽 Grid。
   原来的两个列容器（.ai-cards-a / .ai-cards-b）若各算一个格位，卡片仍会堆成两摞 ——
   用 display:contents 让它们“透明”，里面的卡片直接成为 Grid 子项，
   于是按标签隐藏后剩下的卡片会自动重排、均匀铺满，不再一边堆一边空。 */
.settings-masonry > .ai-cards-a,
.settings-masonry > .ai-cards-b { display: contents; }
.settings-masonry > .ai-cards-a > .card,
.settings-masonry > .ai-cards-b > .card { margin-bottom: 0; }

/* ★ 2026-09-20：基础设置"有的竖着、有的空一片" ——
   容器是 CSS Grid(.settings-masonry)，但卡片外层沿用 Bootstrap 栅格类。
   Grid 里 col-* 的宽度不起作用，于是 col-12(本该整行的宽卡)被压成半宽、
   与半宽卡混排就忽宽忽窄、留下空块。这里让"整行卡"在 Grid 中真正跨两列，
   其余卡等宽等高铺满，与 AI 设置页观感一致。 */
.settings-masonry > .col-12,
.settings-masonry > .col-lg-12 { grid-column: 1 / -1; }
.settings-masonry > [class*="col-"] { display: flex; }      /* 让内部 .card 撑满该格 */
.settings-masonry > [class*="col-"] > .card { width: 100%; margin-bottom: 0; }
@media (max-width: 991px) {
  .settings-masonry > .col-12,
  .settings-masonry > .col-lg-12 { grid-column: auto; }
}

/* ★ 2026-09-20 现场："AI智能体的运维与其它 / 行为与权限 / 模型服务商，需要横着放、铺满整行"。
   模型配置卡字段多，压在两列 Grid 的半幅里右边总留一大块白。
   这里让 AI 设置页里**标了 data-wide 的卡**跨满整行；其余卡照旧两列。 */
/* ★ 2026-09-20 现场修正："横着放铺满整行太大了，那就 2 个卡片放 1 排、大小一样"。
   所以**不再让 data-wide 的卡跨整行** —— 保持两列，每行两张、等宽等高（容器本身
   grid-template-columns: repeat(2,1fr) + align-items:stretch 已保证）。
   data-wide 标记保留，只用来统一卡内字段尺寸（见下），不再控制跨列。
   例外：确实放不下的超宽表格类卡片可按需单独加 .sm-full。 */
.settings-masonry > .sm-full,
.settings-masonry > .ai-cards-a > .card.sm-full,
.settings-masonry > .ai-cards-b > .card.sm-full { grid-column: 1 / -1; }

/* 模型卡内部的字段统一尺寸：主模型与备用原来一个用大控件、一个用小控件，宽窄不一。 */
.card[data-wide] .form-control, [data-wide] .card .form-control,
.card[data-wide] .form-select, [data-wide] .card .form-select { font-size: .8125rem; padding: .25rem .5rem; min-height: 31px; }
.card[data-wide] .form-label, [data-wide] .card .form-label { font-size: .8125rem; margin-bottom: .15rem; }

/* ★ 2026-09-20："轻量模型的位置挪到费用查询凭证下面"。
   不动 DOM（模板里那块结构复杂，硬搬容易切坏），用 Grid 的 order 调显示顺序：
   额度卡（含费用查询凭证）order:-1 排最前，轻量卡紧随其后，其余保持默认。 */
.settings-masonry > .ai-cards-a > .card[data-light] { order: -1; }

/* ★ 2026-09-20：个别页面（如网页版邮箱）卡片少且含宽表格，两列会把表挤没 —— 单列铺满。 */
.settings-masonry.sm-one { grid-template-columns: minmax(0, 1fr); }


/* ============================================================
   排版 v2（2026-09-21）：宽度只取 12 份里的固定几档，一行拼满；同一行等高
   ============================================================ */
.span-reflow, .span-row { align-items: stretch !important; }
.span-reflow > .span-tuned { width: auto !important; }
.span-reflow > .span-tuned[data-span="3"]  { flex: 0 0 calc(25% - 12px) !important;       max-width: calc(25% - 12px) !important; }
.span-reflow > .span-tuned[data-span="4"]  { flex: 0 0 calc(33.3333% - 10.6667px) !important; max-width: calc(33.3333% - 10.6667px) !important; }
.span-reflow > .span-tuned[data-span="6"]  { flex: 0 0 calc(50% - 8px) !important;        max-width: calc(50% - 8px) !important; }
.span-reflow > .span-tuned[data-span="8"]  { flex: 0 0 calc(66.6667% - 5.3333px) !important; max-width: calc(66.6667% - 5.3333px) !important; }
.span-reflow > .span-tuned[data-span="9"]  { flex: 0 0 calc(75% - 4px) !important;        max-width: calc(75% - 4px) !important; }
.span-reflow > .span-tuned[data-span="12"] { flex: 0 0 100% !important; max-width: 100% !important; }
.span-row > .span-tuned { width: auto !important; }
.span-row > .span-tuned[data-span="3"]  { flex: 0 0 25% !important;      max-width: 25% !important; }
.span-row > .span-tuned[data-span="4"]  { flex: 0 0 33.3333% !important; max-width: 33.3333% !important; }
.span-row > .span-tuned[data-span="6"]  { flex: 0 0 50% !important;      max-width: 50% !important; }
.span-row > .span-tuned[data-span="8"]  { flex: 0 0 66.6667% !important; max-width: 66.6667% !important; }
.span-row > .span-tuned[data-span="9"]  { flex: 0 0 75% !important;      max-width: 75% !important; }
.span-row > .span-tuned[data-span="12"] { flex: 0 0 100% !important;     max-width: 100% !important; }
.span-tuned > .card, .span-tuned > .stat-card { height: 100%; }
@media (max-width: 768px) {                      /* 手机上一律整行，不挤 */
  .span-reflow > .span-tuned, .span-row > .span-tuned { flex-basis: 100% !important; max-width: 100% !important; }
}
.layout-span-edge { position: absolute; right: -6px; top: 50%; transform: translateY(-50%); width: 10px; height: 48px;
  border-radius: 5px; background: var(--primary); cursor: ew-resize; z-index: 6; box-shadow: 0 0 0 3px var(--surface); }
.layout-chip { position: absolute; left: 8px; top: -11px; z-index: 6; display: inline-flex; align-items: center; gap: 4px;
  background: var(--primary); color: #fff; border-radius: 6px; font-size: 11.5px; line-height: 20px; padding: 0 4px 0 8px;
  box-shadow: 0 2px 6px rgba(0,0,0,.15); }
.layout-chip button { border: 0; background: rgba(255,255,255,.22); color: #fff; border-radius: 4px; font-size: 11px;
  line-height: 18px; padding: 0 6px; cursor: pointer; }
.layout-gap-hint { border: 2px dashed #e8930c; border-radius: 12px; background: #fff8ec; color: #a15c00; font-size: 12.5px;
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 10px; min-height: 70px; }
.layout-editing .layout-draggable { cursor: grab; }

/* 排版 v2 · CSS 网格容器（设置页 / AI 设置页等，2026-09-21）：改成 12 栏，卡片按份跨列 */
.span-grid { grid-template-columns: repeat(12, minmax(0, 1fr)) !important; align-items: stretch; }
.span-grid > .span-tuned[data-span="3"]  { grid-column: span 3 !important; }
.span-grid > .span-tuned[data-span="4"]  { grid-column: span 4 !important; }
.span-grid > .span-tuned[data-span="6"]  { grid-column: span 6 !important; }
.span-grid > .span-tuned[data-span="8"]  { grid-column: span 8 !important; }
.span-grid > .span-tuned[data-span="9"]  { grid-column: span 9 !important; }
.span-grid > .span-tuned[data-span="12"] { grid-column: 1 / -1 !important; }
.span-grid > .layout-gap-hint { grid-column: auto / span var(--gs, 1); }
@media (max-width: 768px) { .span-grid > .span-tuned { grid-column: 1 / -1 !important; } }


/* ============================================================
   今日待办提醒（2026-09-21）：跟主题走，行用卡片色 + 左侧色带，不再整行深红深橙
   ============================================================ */
#dueAlertModal .due-badge { font-weight: 600; border-radius: 999px; }
#dueAlertModal .due-badge-red { background: color-mix(in srgb, #e5484d 16%, var(--surface)); color: #b42318; }
#dueAlertModal .due-badge-amber { background: color-mix(in srgb, #f59e0b 20%, var(--surface)); color: #92400e; }
#dueAlertModal .due-sec > td { background: var(--surface) !important; border-bottom: 0; padding-top: 14px !important; font-size: 13px; }
#dueAlertModal .due-sec-red > td { color: #c0392b !important; }
#dueAlertModal .due-sec-amber > td { color: #b45309 !important; }
#dueAlertModal .due-head > th { background: var(--bg) !important; color: var(--ink-soft); font-weight: 500; border-bottom: 1px solid var(--border); }
#dueAlertModal .due-row > td { background: var(--surface) !important; border-color: var(--border); vertical-align: middle; }
#dueAlertModal .due-row:hover > td { background: color-mix(in srgb, var(--primary) 6%, var(--surface)) !important; }
#dueAlertModal .due-row-red > td:first-child { box-shadow: inset 3px 0 0 #e5484d; }
#dueAlertModal .due-row-amber > td:first-child { box-shadow: inset 3px 0 0 #f59e0b; }
#dueAlertModal .due-row-muted > td:first-child { box-shadow: inset 3px 0 0 var(--border); }
#dueAlertModal .due-row-muted > td { color: var(--ink-soft); }
#dueAlertModal .due-more > td { background: var(--surface) !important; }
#dueAlertModal .due-amber-text { color: #b45309 !important; }


/* ============================================================
   Bootstrap 组件跟主题色（2026-09-21 现场："系统其余模块必须跟选择的主题匹配"）
   Bootstrap 5.3 这些组件的颜色写死在组件内部变量里（不读全局主色），逐个指到主题色。
   全局变量（链接色等，需 RGB 数字）由 app._ui_css_vars 按当前主题算好注入。
   ============================================================ */
.nav-pills { --bs-nav-pills-link-active-bg: var(--primary); --bs-nav-pills-link-active-color: #fff; }
.nav { --bs-nav-link-color: var(--primary); --bs-nav-link-hover-color: var(--primary-dark); }
.nav-tabs { --bs-nav-tabs-link-active-color: var(--primary-dark); }
.dropdown-menu { --bs-dropdown-link-active-bg: var(--primary); --bs-dropdown-link-active-color: #fff; }
.list-group { --bs-list-group-active-bg: var(--primary); --bs-list-group-active-border-color: var(--primary); }
/* ★ 2026-09-27 现场截图：可搜索下拉里「当前选中」那一项是空白的 —— 选中底色 = 主题色，而主题把链接文字也染成主题色，蓝底蓝字看不见。
   选中 / 键盘高亮项的文字一律白色（全站所有可搜索下拉生效）。 */
.list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus, .ss-item.active { color: #fff !important; }
.pagination { --bs-pagination-color: var(--primary); --bs-pagination-hover-color: var(--primary-dark);
  --bs-pagination-active-bg: var(--primary); --bs-pagination-active-border-color: var(--primary);
  --bs-pagination-focus-color: var(--primary-dark); }
.btn-link { --bs-btn-color: var(--primary); --bs-btn-hover-color: var(--primary-dark); --bs-btn-active-color: var(--primary-dark); }
.text-bg-primary { background-color: var(--primary) !important; color: #fff !important; }
.border-primary { border-color: var(--primary) !important; }
.link-primary { color: var(--primary) !important; }
.alert-primary { --bs-alert-color: var(--primary-dark); --bs-alert-bg: var(--primary-soft); --bs-alert-border-color: var(--primary-muted); }
.bg-primary-subtle { background-color: var(--primary-soft) !important; }
.text-primary-emphasis { color: var(--primary-dark) !important; }
.border-primary-subtle { border-color: var(--primary-muted) !important; }
.form-range::-webkit-slider-thumb { background: var(--primary); }
.form-range::-moz-range-thumb { background: var(--primary); }
.form-switch .form-check-input:checked, .form-check-input:checked { background-color: var(--primary); border-color: var(--primary); }

/* 2026-09-21：提示正文支持换行（授权变更这类多行信息分行显示） */
.flash-msg { white-space: pre-line; }


/* ============================================================
   排版 v3（2026-09-21）：控件画在覆盖层；边拖边落地；拖不下时说原因
   ============================================================ */
#layoutPanel, #layoutAppear { flex: 0 0 100% !important; max-width: 100% !important; width: 100%; }
#layoutPanel .btn, #layoutAppear .btn { white-space: nowrap; }
#layoutOverlay { position: absolute; left: 0; top: 0; width: 0; height: 0; z-index: 1030; }
.lo-chip { position: absolute; display: inline-flex; align-items: center; gap: 4px; background: var(--primary); color: #fff;
  border-radius: 6px; font-size: 11.5px; line-height: 20px; padding: 0 4px 0 8px; box-shadow: 0 2px 6px rgba(0,0,0,.18); white-space: nowrap; }
.lo-chip button { border: 0; background: rgba(255,255,255,.22); color: #fff; border-radius: 4px; font-size: 11px; line-height: 18px; padding: 0 6px; cursor: pointer; }
.lo-grip { position: absolute; width: 12px; height: 48px; border-radius: 6px; background: var(--primary); cursor: ew-resize;
  box-shadow: 0 0 0 3px var(--surface), 0 2px 6px rgba(0,0,0,.2); touch-action: none; }
.layout-editing .lo-target { cursor: grab; outline: 1px dashed color-mix(in srgb, var(--primary) 35%, transparent); outline-offset: 2px; }
.layout-editing .lo-target.lo-drop-ok { outline: 2px dashed var(--primary); outline-offset: 3px; }
.layout-editing .lo-target.lo-dragging { opacity: .55; cursor: grabbing; outline: 2px solid var(--primary); }
.lo-noselect, .lo-noselect * { user-select: none !important; }
.lo-tip { position: fixed; z-index: 2000; pointer-events: none; background: #1f2937; color: #fff; font-size: 12px; border-radius: 6px; padding: 4px 8px; max-width: 320px; }
.lo-tip-ok { background: var(--primary); }
.lo-tip-bad { background: #c0392b; }
.lo-toast { position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%); z-index: 2000; background: #1f2937; color: #fff;
  font-size: 13px; border-radius: 10px; padding: 10px 16px; box-shadow: 0 8px 24px rgba(0,0,0,.25); max-width: 560px; transition: opacity .4s; }
.lo-toast.lo-out { opacity: 0; }

/* ============================================================
   Outlook 式写信（2026-09-21）：上排操作 + 字段行 + 格式工具栏 + 正文
   ============================================================ */
.ol-top { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
.ol-send { padding: 6px 18px; font-weight: 600; }
.ol-imp.active { background: var(--primary-soft); border-color: var(--primary); }
.ol-fields { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 8px; }
.ol-row { display: flex; align-items: center; border-bottom: 1px solid var(--border); }
.ol-row:last-of-type { border-bottom: 0; }
.ol-row > label { flex: 0 0 72px; padding: 0 12px; font-size: 13px; color: var(--ink-soft); }
.ol-row > input { border: 0; border-radius: 0; box-shadow: none !important; padding: 9px 8px; background: transparent; }
.ol-toggles { display: flex; gap: 10px; padding: 0 12px; font-size: 12.5px; white-space: nowrap; }
.ol-atts { display: flex; flex-wrap: wrap; gap: 6px; }
.ol-atts:not(:empty) { padding: 8px 12px; border-top: 1px solid var(--border); }
.ol-att { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: 8px; padding: 3px 8px; font-size: 12.5px; background: var(--bg); }
.ol-att small { color: var(--ink-muted); }
.ol-att a { color: var(--ink-soft); text-decoration: none; font-weight: 700; }
.me-wrap { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); position: relative; }
.me-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; padding: 6px 8px; border-bottom: 1px solid var(--border); background: var(--bg); border-radius: 10px 10px 0 0; }
.me-bar select { height: 28px; border: 1px solid var(--border); border-radius: 6px; font-size: 12.5px; background: var(--surface); color: var(--ink); padding: 0 4px; }
.me-font { width: 130px; } .me-size { width: 62px; margin-left: 4px; }
.me-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 28px; border: 0; border-radius: 6px;
  background: transparent; color: var(--ink); cursor: pointer; margin: 0; }
.me-btn:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.me-btn.on { background: color-mix(in srgb, var(--primary) 20%, transparent); color: var(--primary-dark); }
.me-color input[type=color] { position: absolute; opacity: 0; width: 100%; height: 100%; left: 0; top: 0; cursor: pointer; }
.me-swatch { position: absolute; left: 7px; right: 7px; bottom: 3px; height: 3px; border-radius: 2px; }
.me-sep { width: 1px; height: 18px; background: var(--border); margin: 0 5px; }
.me-edit { min-height: 280px; max-height: 60vh; overflow: auto; padding: 14px 16px; outline: none; color: #1f2937; background: #fff; border-radius: 0 0 10px 10px; line-height: 1.6; }
.me-edit table td { border: 1px solid #bfbfbf; padding: 4px 6px; }
.me-edit img { max-width: 100%; }
.me-tblwrap { position: relative; display: inline-block; }
.me-grid { position: absolute; top: 32px; left: 0; z-index: 20; display: grid; grid-template-columns: repeat(8, 16px); gap: 2px; padding: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 20px rgba(0,0,0,.15); }
.me-grid[hidden] { display: none; }
.me-grid span { width: 16px; height: 16px; border: 1px solid var(--border); border-radius: 2px; cursor: pointer; }
.me-grid span.on { background: var(--primary-soft); border-color: var(--primary); }
.me-grid-n { grid-column: 1 / -1; text-align: center; font-size: 12px; color: var(--ink-soft); padding-top: 4px; }
.me-pop { position: absolute; top: 40px; right: 12px; z-index: 20; display: flex; gap: 6px; padding: 8px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 20px rgba(0,0,0,.15); width: 360px; max-width: 90%; }
.me-pop[hidden] { display: none; }

/* ============================================================
   Outlook 式三栏收发中心（2026-09-21）
   ============================================================ */
.ol-shell { display: grid; grid-template-columns: 190px minmax(280px, 380px) minmax(0, 1fr); height: calc(100vh - var(--thead-top, 88px) - 150px);
  min-height: 520px; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface); margin-bottom: 16px; }
@media (max-width: 992px) { .ol-shell { grid-template-columns: 1fr; height: auto; } .ol-folders { display: none; } .ol-read { min-height: 480px; } }
.ol-folders { border-right: 1px solid var(--border); padding: 10px 6px; background: var(--bg); overflow: auto; }
.ol-acct { font-weight: 700; padding: 6px 10px 8px; }
.ol-f { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 8px; color: var(--ink); text-decoration: none; font-size: 13.5px; }
.ol-f:hover { background: color-mix(in srgb, var(--primary) 8%, transparent); }
.ol-f.on { background: color-mix(in srgb, var(--primary) 16%, transparent); font-weight: 600; }
.ol-unread-n { margin-left: auto; color: var(--primary-dark); }
.ol-list { border-right: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; }
.ol-list-head { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.ol-list-head [data-f] { text-decoration: none; color: var(--ink-soft); font-size: 13.5px; padding-bottom: 2px; border-bottom: 2px solid transparent; }
.ol-list-head [data-f].on { color: var(--primary-dark); border-bottom-color: var(--primary); font-weight: 600; }
.ol-list-head input { flex: 1; min-width: 80px; }
.ol-items { overflow: auto; flex: 1; }
.ol-grp { padding: 5px 10px; font-size: 12.5px; color: var(--ink-soft); background: var(--bg); border-bottom: 1px solid var(--border); cursor: pointer; position: sticky; top: 0; z-index: 1; }
.ol-grp.shut .bi { transform: rotate(-90deg); display: inline-block; }
.ol-item { padding: 9px 12px 9px 14px; border-bottom: 1px solid var(--border); cursor: pointer; position: relative; outline: none; }
.ol-item:hover { background: color-mix(in srgb, var(--primary) 5%, transparent); }
.ol-item.sel { background: color-mix(in srgb, var(--primary) 14%, transparent); }
.ol-item.unread::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--primary); }
.ol-i1 { display: flex; align-items: center; gap: 6px; }
.ol-from { font-size: 14px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ol-item.unread .ol-from, .ol-item.unread .ol-subj { font-weight: 700; }
.ol-i2 { display: flex; gap: 8px; font-size: 12.5px; }
.ol-subj { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ol-item.unread .ol-subj { color: var(--primary-dark); }
.ol-time { color: var(--ink-soft); white-space: nowrap; }
.ol-i3 { font-size: 12px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ol-tag { display: inline-block; font-size: 11px; border-radius: 4px; padding: 0 5px; background: var(--primary-soft); color: var(--primary-dark); }
.ol-tag-bad { background: #fde8e8; color: #b42318; }
.ol-read { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.ol-empty { margin: auto; text-align: center; color: var(--ink-muted); }
.ol-empty .bi { font-size: 42px; display: block; margin-bottom: 6px; }
.ol-r-head { padding: 14px 18px 10px; border-bottom: 1px solid var(--border); }
.ol-r-subj { font-size: 18px; margin: 0 0 10px; }
.ol-r-meta { display: flex; gap: 12px; align-items: flex-start; }
.ol-ava { width: 44px; height: 44px; flex: 0 0 44px; border-radius: 50%; display: grid; place-items: center; font-weight: 700;
  background: color-mix(in srgb, var(--primary) 18%, var(--surface)); color: var(--primary-dark); }
.ol-r-who { flex: 1; min-width: 0; font-size: 13.5px; }
.ol-r-who .small { overflow: hidden; text-overflow: ellipsis; }
.ol-r-side { flex: 0 0 auto; text-align: right; }
.ol-r-status { margin-top: 8px; }
.ol-atts-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; margin-top: 10px; max-height: 140px; overflow: auto; }
.ol-att-tile { display: flex; align-items: center; gap: 10px; border: 1px solid var(--border); border-radius: 8px; padding: 6px 10px; text-decoration: none; color: var(--ink); background: var(--surface); }
.ol-att-tile:hover { border-color: var(--primary); }
.ol-att-tile > .bi { font-size: 24px; }
.ol-att-tile > span { min-width: 0; display: flex; flex-direction: column; }
.ol-att-n { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ol-att-tile small { color: var(--ink-muted); font-size: 11.5px; }
.ol-r-body { flex: 1; min-height: 0; display: flex; }
.ol-frame { flex: 1; border: 0; width: 100%; background: #fff; }
.ol-text { flex: 1; margin: 0; padding: 14px 18px; white-space: pre-wrap; overflow: auto; font-family: inherit; font-size: 14px; }

/* ============================================================
   Outlook 式功能区 + 视图选项（2026-09-22）
   ============================================================ */
.ol-ribbon { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 10px; }
.ol-rtabs { display: flex; gap: 18px; padding: 6px 14px 0; border-bottom: 1px solid var(--border); }
.ol-rtabs a { padding: 4px 2px 6px; color: var(--ink-soft); text-decoration: none; font-size: 13.5px; border-bottom: 2px solid transparent; }
.ol-rtabs a.on { color: var(--primary-dark); border-bottom-color: var(--primary); font-weight: 600; }
.ol-rpane { display: flex; flex-wrap: wrap; align-items: stretch; padding: 6px 8px; }
.ol-rpane[hidden] { display: none; }
.ol-rg { display: flex; flex-direction: column; padding: 0 10px; border-right: 1px solid var(--border); }
.ol-rg:last-child { border-right: 0; }
.ol-rb-row { display: flex; gap: 2px; flex: 1; align-items: flex-start; }
.ol-rb-col { display: flex; flex-direction: column; gap: 1px; flex: 1; }
.ol-rb-grid { display: grid; grid-template-columns: repeat(3, auto); gap: 1px 6px; flex: 1; }
.ol-rg-n { font-size: 11px; color: var(--ink-muted); text-align: center; padding-top: 3px; }
.ol-rb { display: inline-flex; align-items: center; gap: 4px; border: 0; background: transparent; color: var(--ink); font-size: 12.5px; padding: 3px 8px;
  border-radius: 6px; text-decoration: none; cursor: pointer; white-space: nowrap; line-height: 1.25; }
.ol-rb:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--ink); }
.ol-rb.on { background: color-mix(in srgb, var(--primary) 20%, transparent); }
.ol-rb.big { flex-direction: column; justify-content: flex-start; text-align: center; min-width: 58px; padding: 4px 6px; }
.ol-rb.big > .bi { font-size: 22px; color: var(--primary-dark); margin-bottom: 1px; }
.ol-rb > .bi { color: var(--primary-dark); }
.ol-flag { display: none; color: #c0392b; font-size: 12px; }
.ol-item.flagged .ol-flag { display: inline; }
.ol-shell.ol-compact .ol-item { padding-top: 5px; padding-bottom: 5px; }
.ol-shell.ol-nopreview .ol-i3 { display: none; }
.ol-shell.pane-bottom { grid-template-columns: 190px minmax(0, 1fr); grid-template-rows: minmax(0, 42%) minmax(0, 58%); }
.ol-shell.pane-bottom .ol-folders { grid-row: 1 / 3; }
.ol-shell.pane-bottom .ol-list { border-right: 0; border-bottom: 1px solid var(--border); }
.ol-shell.pane-bottom .ol-read { grid-column: 2; }
.ol-shell.pane-off { grid-template-columns: 190px minmax(0, 1fr); }
.ol-shell.pane-off .ol-read { display: none; }
@media (max-width: 992px) { .ol-shell.pane-bottom, .ol-shell.pane-off { grid-template-columns: 1fr; grid-template-rows: none; } }


/* ============================================================
   设置类卡片网格：瀑布流（2026-09-22）—— 行高 4px、按卡片实际高度跨行，间距用下边距
   （JS 在 base.html：列宽统一、接到最短列、段内底边对齐、成对等高）
   ============================================================ */
.settings-masonry.sm-mason { grid-auto-rows: 4px; row-gap: 0 !important; grid-auto-flow: row dense; align-items: stretch; }
.settings-masonry.sm-mason .sm-mi { margin-bottom: var(--sm-gap, 14px) !important; }
.settings-masonry.sm-mason .sm-mi > .card, .settings-masonry.sm-mason .card.sm-mi { height: 100%; }

/* ============================================================
   层次版主题：渐变底上的小字看不清（2026-09-22 现场截图：服务器管理统计卡说明文字）
   ============================================================ */


/* ============================================================
   对比度体检修正（2026-09-22，tools/contrast_audit.py 实测）
   ============================================================ */
/* 「使用引导」按钮：白底深字胶囊（原来只在引导弹出过后才注入样式，没弹过的页面按钮在渐变顶栏上看不清） */
.pt-help.btn { background: #fff !important; color: #1f2937 !important; border: 1px solid rgba(15,23,42,.14) !important;
  border-radius: 999px; padding: 1px 10px; box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.pt-help.btn:hover { background: #f3f4f6 !important; color: #111827 !important; }
.pt-help.btn .bi { color: #1f2937 !important; }
/* 普通主题的统计卡：标题 / 说明原来用最浅的灰（对比度 2.2~2.9），改次要灰。层次版主题的白字规则在其后注入，不受影响 */
.stat-card .stat-label, .stat-card .stat-hint, .stat-card .form-text { color: var(--ink-soft) !important; opacity: 1; }
/* 主色填充的元素：底色 / 字色按主题自动算（app._ui_css_vars：--primary-fill / --on-primary）——
   默认主题的翠绿太亮，白字压不住（2.5:1）；深色主题的亮主色上白字更糟。层次版的渐变在其上层，不受影响 */
.btn-primary { background-color: var(--primary-fill, var(--primary)) !important; border-color: var(--primary-fill, var(--primary)) !important; color: var(--on-primary, #fff) !important; }
.page-tab.active { background-color: var(--primary-fill, var(--primary)); color: var(--on-primary, #fff); }
.page-tab.active .tab-title, .page-tab.active .tab-close { color: var(--on-primary, #fff); }
.sidebar-brand .brand-icon { background: var(--primary-fill, var(--primary)); color: var(--on-primary, #fff); }
.topbar .avatar { background: var(--primary-fill, var(--primary)) !important; color: var(--on-primary, #fff) !important; }
.sidebar-sublink.active { background-color: var(--primary-fill, var(--primary)) !important; color: var(--on-primary, #fff) !important; }
/* 深色侧栏（极光 / 海洋 / 暮光）：系统名、版本号用浅色 */
.sidebar-brand .brand-name { color: var(--sidebar-strong, var(--ink)); }
.sidebar-build, .sidebar-build * { color: var(--sidebar-text, var(--ink-soft)) !important; opacity: .85; }
/* 主色当文字（链接、文字按钮、主色字、描边主按钮）：主色在卡片底上不够清楚时用深一档（--primary-text） */
a:not(.btn):not(.nav-link):not(.sidebar-link):not(.sidebar-sublink):not(.dropdown-item):not(.page-tab) { color: var(--primary-text, var(--primary)); }
.btn-link, .text-primary, .link-primary { color: var(--primary-text, var(--primary)) !important; }
.btn-outline-primary { color: var(--primary-text, var(--primary)); border-color: var(--primary-text, var(--primary)); }
.btn-outline-primary:hover, .btn-outline-primary.active { color: var(--on-primary, #fff); background-color: var(--primary-fill, var(--primary)); }
.card-header .form-text, .card-header .text-muted { color: var(--ink-soft) !important; }
/* 保存后停在原处：刚保存的卡片短暂高亮（static/keep_scroll.js） */
.ks-flash { animation: ksFlash 1.8s ease-out; }
@keyframes ksFlash { 0%, 35% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 55%, transparent); } 100% { box-shadow: 0 0 0 0 transparent; } }

/* 表头拖动换列顺序（2026-09-22，base.html colOrderDrag） */
thead th[draggable="true"] { cursor: grab; }
thead th.co-dragging { opacity: .45; }
thead th.co-before { box-shadow: inset 3px 0 0 var(--primary); }
thead th.co-after { box-shadow: inset -3px 0 0 var(--primary); }

/* ============================================================
   节约纵向空间（2026-09-22 现场："抬头往上靠着导航栏，列表往上靠，抬头固定下面内容滑动；全局"）
   ============================================================ */
.page-tabs-wrap { height: 0; }                                   /* 这层不再占文档流高度 */
.page-tabs-menu { position: fixed; top: 52px; right: 6px; height: 34px; display: flex; align-items: center; z-index: 891; }
.page-tabs-bar { padding-right: 40px; }                          /* 给右端的「⋮」让位 */
/* 表头固定：调列宽给表头加的内联 position 不许盖掉固定 */
.table thead th { position: sticky !important; }
/* 卡片里的表格原来一律"不固定表头"（为收款规则这类小表格定的），可现在几乎所有列表的主表都在卡片里 ——
   全系统表头都不固定。改为：表头固定逻辑（layout_guard.js）判定需要固定的，卡片里也固定 */
.card .table-responsive.thead-free > .table > thead th {
  position: sticky !important; top: var(--thead-top, 88px) !important; z-index: 10 !important;
  box-shadow: 0 1px 0 var(--border-strong);
}
.card .table-responsive.thead-box > .table > thead th {
  position: sticky !important; top: var(--tb-off, 0px) !important; z-index: 10 !important;
  box-shadow: 0 1px 0 var(--border-strong);
}


/* ============================================================
   内容贴着标签栏、工具条和表头固定（2026-09-22 现场：订单列表等"抬头往上靠、抬头固定不动，节约空间"）
   --bars-h 由 layout_guard.js 按顶栏 + 标签栏的实际下沿写入（原来写死 52+34+8px，实际高度一不同就多出空白）
   ============================================================ */
.content { padding-top: calc(var(--bars-h, 86px) + 8px) !important; }
@media (min-width: 769px) {
  .page-toolbar { position: sticky; top: var(--bars-h, 86px); z-index: 25; margin-bottom: 8px;
    box-shadow: 0 2px 6px rgba(0,0,0,.06); }
}

/* ═══════════════ 平整规范（2026-10-09「检查系统全部模块…系统增加规则，后续所有模块都必须做到平整」）═══════════════
   全站一套规则，新模块不用自己写：
   ① 控件同高：普通 34px、小号 30px —— 按钮、下拉、输入框、input-group 同一档，放一排一样高（原来小按钮 27px、小下拉 30px，一排高矮不一）
   ② 按钮文字不折行（「发送」「全选」不会断成两行）；整宽按钮（w-100 / d-grid）照常可换行
   ③ 卡片抬头：说明文字能收窄换行，右边的控件组整块右对齐、放不下换到下一行，不挤成一团
   ④ 表单一行里：没有标题的那一格（按钮、勾选框、说明框）跟输入框底边对齐，不再顶到标题那一层（截图「租户名称」旁边的说明框）
   另有 static/layout_guard.js 在每个页面加载后体检：同一行标题行数不同 → 标题补齐高度让输入框对齐；表格超出卡片 → 自动套上横向滚动；
   查到的问题记进「排版监听记录」。开发时用 tools/layout_sweep.py 把全部页面扫一遍，0 问题才发版（见 CLAUDE.md「页面平整规范」）。 */
.btn { padding-top: 7px; padding-bottom: 7px; line-height: 1.4; white-space: nowrap; }
.btn-sm, .btn-group-sm > .btn, .input-group-sm > .btn { padding-top: 5px; padding-bottom: 5px; line-height: 1.45; }
.btn.w-100, .d-grid > .btn, .btn.text-wrap { white-space: normal; }
.form-control-sm, .form-select-sm, .input-group-sm > .form-control, .input-group-sm > .form-select, .input-group-sm > .input-group-text { min-height: 30px; }
.input-group-sm > .input-group-text { padding-top: 4px; padding-bottom: 4px; }
.card-header.d-flex { flex-wrap: wrap; row-gap: 6px; }
.card-header.d-flex > .small, .card-header.d-flex > .text-secondary, .card-header.d-flex > .text-muted { flex: 1 1 200px; min-width: 0; }
.card-header .ms-auto.d-flex { flex-wrap: wrap; justify-content: flex-end; row-gap: 6px; }
.card-header .input-group { flex-wrap: nowrap; width: auto; }
.row:has(> [class*="col"] > .form-label) > [class*="col"]:not(:has(.form-label, .form-text, textarea, table, .card, select, input:not([type=checkbox]):not([type=radio]):not([type=hidden]))) { align-self: flex-end; }
.row:has(> [class*="col"] > .form-label) > [class*="col"]:not(:has(.form-label, .form-text, textarea, table, .card, select, input:not([type=checkbox]):not([type=radio]):not([type=hidden]))) > .alert { margin-bottom: 0; }
.row:has(> [class*="col"] > .form-label) > [class*="col"]:not(:has(.form-label, .form-text, textarea, table, .card, select, input:not([type=checkbox]):not([type=radio]):not([type=hidden]))) > .form-check { min-height: 34px; display: flex; align-items: center; gap: .4em; margin-bottom: 0; padding-left: 0; }
.row:has(> [class*="col"] > .form-label) > [class*="col"]:not(:has(.form-label, .form-text, textarea, table, .card, select, input:not([type=checkbox]):not([type=radio]):not([type=hidden]))) > .form-check > .form-check-input { margin-top: 0; float: none; margin-left: 0; }
.row:has(> [class*="col"] > .form-label):has(.form-control-sm, .form-select-sm) > [class*="col"]:not(:has(.form-label, .form-text, textarea, table, .card, select, input:not([type=checkbox]):not([type=radio]):not([type=hidden]))) > .form-check { min-height: 30px; }
.lg-wrap-x { overflow-x: auto; max-width: 100%; }
/* ⑤ 筛选条 / 抬头里的「紧凑按钮」（py-0）跟同一排的下拉、输入框一样高 */
:is(div, span, td, th, form, li):has(> .form-select-sm, > .form-control-sm, > .input-group-sm) > .btn-sm.py-0,
:is(div, span, td, th, li):has(> .form-select-sm, > .form-control-sm, > .input-group-sm) > form > .btn-sm.py-0 { padding-top: 5px !important; padding-bottom: 5px !important; }
/* ⑥ 主按钮原来 border:none，比同排的描边按钮矮 2px → 留 1px 透明边，高度一致、样子不变 */
.btn-primary { border: 1px solid transparent; }
/* ⑦ 三个以上按钮排在一起的那一组：放不下就换行（按钮不折字以后，原来挤着的一组会撑出页面） */
.d-flex:not(.flex-nowrap):has(> .btn ~ .btn ~ .btn), .d-flex:not(.flex-nowrap):has(> a.btn ~ a.btn ~ a.btn) { flex-wrap: wrap; row-gap: 6px; }
.ms-auto.d-flex:has(> .btn ~ .btn ~ .btn) { justify-content: flex-end; min-width: 0; }
/* ⑧ 长表单按主题分组（2026-10-09「台账设置放在一起，其余相关设置放在一起」）：<fieldset class="form-sec"><legend class="form-sec-h">标题<span class="form-sec-hint">说明</span></legend> … */
.form-sec { border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 10px; padding: 8px 14px 12px; margin: 0 0 12px; background: var(--surface, transparent); }
.form-sec + .form-sec { margin-top: 0; }
legend.form-sec-h { float: none; width: auto; font-size: 13px; font-weight: 700; margin: 0 0 6px; padding: 0 6px; display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; color: var(--ink, inherit); }
.form-sec-hint { font-weight: 400; color: var(--ink-muted, #888); font-size: 12px; }
/* ⑧b 栅格表单（.row 里）不方便包 fieldset 时，插一行小标题分组：<div class="col-12 form-sec-title">航线<span class="form-sec-hint">说明</span></div> */
.form-sec-title { font-size: 13px; font-weight: 700; color: var(--ink, inherit); border-bottom: 1px solid var(--border, rgba(0,0,0,.1)); padding: 2px 0 4px; margin-top: 6px !important; display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.form-sec-title:first-child { margin-top: 0 !important; }
.form-sec-title .form-sec-hint { font-weight: 400; }
/* ⑨ 列表客户端分页（static/ui_std.js）：分页藏起来的行 */
tr.ot-pg-off { display: none !important; }
/* ⑩ 标签栏（2026-10-09「关闭全部后上面没有任何信息」「满了应该折叠或者换行」）：
   最左固定「首页」标签；放不下时 .tabs-fold = 收进右端「更多 N」下拉，.tabs-wrap = 换行（栏变高，正文跟着让位：layout_guard 量 --bars-h） */
.page-tabs-bar.tabs-fold { overflow: visible; }
.page-tabs-bar.tabs-wrap { flex-wrap: wrap; overflow: visible; padding-top: 4px; padding-bottom: 4px; row-gap: 4px; }
.page-tab.page-tab-home { padding: 0 10px; }
.page-tab.page-tab-home .bi { font-size: 13px; }
.page-tab-more { flex-shrink: 0; margin-left: auto; }
.page-tab-more > .page-tab { border: 1px dashed var(--border); }
.page-tab-more-menu { max-height: 60vh; overflow-y: auto; min-width: 220px; }
.page-tab-more-menu .ptm-x { font-size: 11px; padding: 2px; border-radius: 50%; }
.page-tab-more-menu .ptm-x:hover { background: rgba(0,0,0,.08); color: var(--ink) !important; }
/* ⑪ 空表格统一提示（ui_std.js）：3D 图标开着时用立体空状态图，关着用线框 inbox */
html.icons-3d .ot-empty-row .ot-empty-ico { display: none; }
.ot-empty-row .i3d-lg { display: none; }
html.icons-3d .ot-empty-row .i3d-lg { display: block; margin: 0 auto 6px; }
.ot-empty-row:hover > td { background: transparent !important; }
/* ⑫ 侧栏对齐（2026-10-09 截图「仪表盘 / 智能体 / 数字员工需要跟下面的对齐」）：
   单独的一级链接原来贴着侧栏边（左 8px），分组标题在 .nav-group 里（左 16px）—— 图标、文字差一截。一级链接也缩进到同一条线上 */
/*   2026-10-09 补：AI 智能体 / 数字员工这类一级链接没带 top-link 类，上一版没管到 → 改成凡是侧栏里的一级链接（不在分组、不在挂靠区里）都缩进 */
.sidebar-nav > a.sidebar-link,
.sidebar-nav > div:not(.nav-group):not(.nav-attached) > a.sidebar-link { margin-left: 8px; margin-right: 8px; padding-left: 14px; }
/* ⑬ 查询弹窗统一的「下拉 + 搜索」控件（base.html gsCombo） */
.gs-combo { position: relative; }
.gs-combo .gs-cb { padding-right: 26px; }
.gs-combo .gs-cb-caret { position: absolute; right: 9px; top: 50%; transform: translateY(-50%); font-size: 11px; color: var(--ink-muted, #888); pointer-events: none; }
.gs-cb-dd { display: none; position: absolute; left: 0; right: 0; top: calc(100% + 2px); z-index: 1065; max-height: 240px; overflow-y: auto;
  background: var(--surface, #fff); border: 1px solid var(--border, #ddd); border-radius: 8px; box-shadow: 0 6px 18px rgba(0,0,0,.12); font-size: 12.5px; }
.gs-cb-it { padding: 6px 10px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gs-cb-it:hover, .gs-cb-it.on { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.gs-cb-all { color: var(--ink-muted, #888); border-bottom: 1px solid var(--border, #eee); }
.gs-cb-none { padding: 6px 10px; color: var(--ink-muted, #888); }

/* ═══ 登录后系统底色（2026-10-10「登入后系统的底色也是一样，做成可以选择的」）═══
   body[data-bg]：管理员定全员默认，每人可在右上角「主题」里换。颜色全部从当前主题的主色（--primary）和底色（--bg）调出来，
   换哪套主题皮肤都协调；纹理压得很淡、固定不随滚动，只当底子，不抢表格和卡片。 */
body[data-bg="glow"], .ui-bg-sw[data-bg="glow"] {
  background: radial-gradient(1100px 620px at 100% -10%, color-mix(in srgb, var(--primary, #2563eb) 16%, transparent), transparent 62%),
              radial-gradient(900px 560px at -10% 110%, color-mix(in srgb, var(--primary, #2563eb) 11%, transparent), transparent 60%), var(--bg, #f4f6fa);
}
body[data-bg="mesh"], .ui-bg-sw[data-bg="mesh"] {
  background: radial-gradient(800px 500px at 12% 0%, color-mix(in srgb, var(--primary, #2563eb) 14%, transparent), transparent 60%),
              radial-gradient(700px 520px at 92% 18%, rgba(45, 212, 191, .12), transparent 60%),
              radial-gradient(760px 520px at 70% 100%, rgba(139, 92, 246, .10), transparent 60%), var(--bg, #f4f6fa);
}
body[data-bg="grid"], .ui-bg-sw[data-bg="grid"] {
  background: radial-gradient(1000px 420px at 50% -8%, color-mix(in srgb, var(--primary, #2563eb) 12%, transparent), transparent 70%),
              linear-gradient(color-mix(in srgb, var(--primary, #2563eb) 7%, transparent) 1px, transparent 1px) 0 0 / 32px 32px,
              linear-gradient(90deg, color-mix(in srgb, var(--primary, #2563eb) 7%, transparent) 1px, transparent 1px) 0 0 / 32px 32px, var(--bg, #f4f6fa);
}
body[data-bg="dots"], .ui-bg-sw[data-bg="dots"] {
  background: radial-gradient(color-mix(in srgb, var(--primary, #2563eb) 20%, transparent) 1.1px, transparent 1.4px) 0 0 / 22px 22px, var(--bg, #f4f6fa);
}
body[data-bg="route"], .ui-bg-sw[data-bg="route"] {
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1600' height='900' viewBox='0 0 1600 900'><g fill='none' stroke='%2364748b' stroke-opacity='.10'><ellipse cx='800' cy='450' rx='760' ry='380'/><ellipse cx='800' cy='450' rx='520' ry='380'/><ellipse cx='800' cy='450' rx='260' ry='380'/><path d='M40 450H1560M120 260H1480M120 640H1480'/></g><g fill='none' stroke='%2364748b' stroke-opacity='.22' stroke-dasharray='2 8'><path d='M180 560Q520 180 980 330'/><path d='M980 330Q1250 240 1430 420'/></g></svg>") center / cover no-repeat,
              radial-gradient(900px 500px at 85% 0%, color-mix(in srgb, var(--primary, #2563eb) 10%, transparent), transparent 60%), var(--bg, #f4f6fa);
}
body[data-bg="plain"], .ui-bg-sw[data-bg="plain"] { background: var(--bg, #f4f6fa); }
body[data-bg]:not([data-bg="plain"]) { background-attachment: fixed; }
body[data-bg]:not([data-bg="plain"]) .topbar { background: color-mix(in srgb, var(--bg, #f4f6fa) 80%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.ui-bg-sw { background-size: auto; }

/* ★ 2026-10-12 全系统手机端体检：
   ① 顶栏右边的语言 / 主题 / 排版 / 通知 / 用户按钮在手机上挤出屏幕（用户菜单点不到）→ 手机上只留图标、标题过长省略、排版按钮（手机上用不着）不显示；
   ② 「使用引导」按钮比左边按钮矮一截 → 同高、只留图标；
   ③ 卡片头里的一排控件不换行把页面撑宽 → 允许换行。宽表格由 base.html 里的脚本自动套横向滚动。 */
@media (max-width: 768px) {
  .topbar { padding: 0 8px; gap: 6px; }
  .topbar > .d-flex:first-child { flex-shrink: 1 !important; min-width: 0; }
  .topbar h1 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  #topbarActions { gap: 4px !important; }
  .topbar .theme-btn { font-size: 0 !important; padding: 0 7px !important; gap: 0 !important; min-width: 30px; justify-content: center; }
  .topbar .theme-btn > i { font-size: 15px; }
  .topbar .theme-btn.dropdown-toggle::after { display: none; }
  .topbar #layoutToggle { display: none !important; }
  .topbar .pt-help { font-size: 0 !important; height: 30px; min-width: 30px; padding: 0 !important; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
  .topbar .pt-help > i { font-size: 15px; }
  .card-header.d-flex, .card-header > .d-flex, .card-body > .d-flex { flex-wrap: wrap; row-gap: 6px; }
  .card-header > .d-flex { max-width: 100%; min-width: 0; }
  .card-header .input-group { flex-wrap: nowrap; max-width: 100%; min-width: 0; flex: 1 1 auto; }
  .card-header .input-group > .form-select, .card-header .input-group > .form-control { min-width: 0; }
  .card-header .ms-auto { margin-left: 0 !important; }
}
/* 短按钮文字不折成两行（全系统体检：「一键编译SOP」「加一条」在窄屏折行） */
.btn-sm, .card-header .btn, .btn-group > .btn, .page-toolbar .btn { white-space: nowrap; }
/* ★ 2026-10-13 全系统手机端：表单里写死的窄栏（col-2 / col-3 / col-4 等，没写 sm/md 断点）在手机上挤成一指宽，文字折成好几行、和旁边对不齐 →
   手机上表单的窄栏改成半行，col-4 / col-5 / col-7 / col-8 改成整行（col-6、col-12 不变；写了断点的照断点走） */
@media (max-width: 576px) {
  .content form .row > .col-1, .content form .row > .col-2, .content form .row > .col-3 { flex: 0 0 auto; width: 50%; }
  .content form .row > .col-4, .content form .row > .col-5, .content form .row > .col-7, .content form .row > .col-8, .content form .row > .col-9 { flex: 0 0 auto; width: 100%; }
}
