/* ============================================================
   进销存开票系统 · 「账房青」设计系统
   纸灰绿底 / 墨色文字 / 靛青主色 / 印章红（仅危险与品牌）
   ============================================================ */
:root {
  --paper: #F1F3F0;          /* 纸：灰绿调底色，久看不刺眼 */
  --card: #FFFFFF;
  --ink: #1F2A2E;            /* 墨：正文 */
  --ink-soft: #3D4D4F;
  --muted: #687874;
  --faint: #8E9B97;
  --border: #E3E8E5;
  --border-strong: #CBD4CF;
  --primary: #33518C;        /* 靛青：蓝字发票 */
  --primary-dark: #274069;
  --primary-wash: #EDF1F7;
  --chop: #C03A2B;           /* 印红：只用于危险、欠款、品牌章 */
  --chop-wash: #FAF0EE;
  --ok: #2E7D5B;             /* 松绿 */
  --ok-wash: #EDF4F0;
  --warn: #A9701C;           /* 赭 */
  --warn-wash: #F9F3E8;
  --sidebar: #1D2D33;        /* 墨松 */
  --sidebar-ink: #A9BBB9;
  --radius: 12px;
  --radius-s: 8px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
  background: var(--paper); color: var(--ink); font-size: 14px; line-height: 1.6;
}
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px; }

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

.layout { display: flex; min-height: 100vh; }

/* ============ 侧栏 ============ */
.sidebar {
  width: 216px; flex-shrink: 0;
  background: var(--sidebar); color: var(--sidebar-ink);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
.brand {
  display: flex; align-items: center; gap: 11px;
  padding: 20px 18px 18px;
  border-bottom: 1px solid rgba(255,255,255,.07);
}
.seal {
  width: 32px; height: 32px; flex: 0 0 auto;
  background: var(--chop); color: #fff;
  border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 700; line-height: 1;
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.28);
}
.brand-name { color: #F2F5F4; font-size: 14.5px; font-weight: 600; letter-spacing: .5px; line-height: 1.3; }
.brand-sub { font-size: 10.5px; color: #6E817F; letter-spacing: 2px; }

/* ---- 品牌锁定（商标 + 「云进销存系统」）----
   标记只有一份：templates/_brand.html（侧栏 / 登录页 / 设密码两页共用）。
   logo.svg 是「Kaidy 凯涤」横版锁定，宽高比 165.73 : 33.54 ≈ 4.94 : 1，
   主色是藏青 #222552 —— 贴墨松侧栏（--sidebar #1D2D33）上几乎看不见
   （2026-09-21 实测：只剩绿色 K 徽标，藏青的「Kaidy」与「凯涤」块全糊进背景），
   所以**深底垫白牌、白底用原色**。白牌由祖先选择器决定，标记里不写死。 */
.brand-plate { display: inline-flex; align-items: center; }
.brand-logo { display: block; width: 100%; height: auto; }
.brand-tag { line-height: 1.3; }

/* 侧栏：白牌 + 商标，「云进销存系统」与 LEDGER & INVOICE 依次竖排 */
.brand { flex-direction: column; align-items: flex-start; gap: 9px; }
.brand .brand-plate { background: #fff; border-radius: 9px; padding: 7px 9px; }
.brand .brand-logo { width: 156px; }
.brand .brand-tag { color: #F2F5F4; font-size: 13px; font-weight: 600; letter-spacing: .5px; }

/* 登录 / 设置密码卡（白底）：原色直接用，不垫牌。
   ⚠️ 这里只管「元素」的尺寸与颜色；容器的 flex-direction / align-items 写在
   下面 `.login-brand` 那一行（同一份文件里位置更靠后的那条才会赢）。 */
.login-brand .brand-logo { width: 210px; }
.login-brand .brand-tag { color: var(--ink); font-size: 15.5px; font-weight: 600; letter-spacing: .5px; }

.sidebar nav { flex: 1; padding: 10px 12px; overflow-y: auto; }
.nav-group { font-size: 11px; color: #5F7270; padding: 14px 10px 5px; }
.nav-group:first-child { padding-top: 4px; }
.sidebar nav a {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; margin-bottom: 2px;
  color: var(--sidebar-ink); font-size: 13.5px;
  border-radius: var(--radius-s); position: relative;
  transition: background .12s, color .12s;
}
.sidebar nav a:hover { background: rgba(255,255,255,.06); color: #E5ECEA; text-decoration: none; }
.sidebar nav a.active { background: rgba(255,255,255,.09); color: #fff; }
.sidebar nav a.active::before {
  content: ''; position: absolute; left: -12px; top: 7px; bottom: 7px;
  width: 3px; border-radius: 0 3px 3px 0; background: #7FA3D8;
}
.sidebar nav a svg { width: 16px; height: 16px; flex: 0 0 auto; opacity: .85; }
.sidebar nav a.active svg { opacity: 1; color: #9FBCDF; }

.sidebar-foot {
  padding: 14px 18px; font-size: 11px; color: #5F7270;
  border-top: 1px solid rgba(255,255,255,.07); line-height: 1.8;
}

/* ============ 主区 ============ */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar {
  background: var(--card); padding: 13px 28px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  position: sticky; top: 0; z-index: 10;
}
.page-title { font-size: 16px; font-weight: 600; letter-spacing: .3px; }
.topbar-user { display: flex; gap: 18px; align-items: center; }
.topbar-user a { color: var(--muted); font-size: 13px; }
.topbar-user a:hover { color: var(--primary); }
.whoami { font-size: 13px; color: var(--ink-soft); font-weight: 600; }
.whoami em {
  font-style: normal; font-size: 11px; color: var(--faint); font-weight: 500;
  border: 1px solid var(--border-strong); border-radius: 999px;
  padding: 1px 8px; margin-left: 7px;
}
/* 双账套切换器：下拉选择，选中即整页重载
   （原来是一排按钮，看着像页面内的 tab；账套影响几乎每个查询，用下拉更明确"换一套数据"） */
.biz-switch {
  display: inline-flex; align-items: center; background: var(--primary-wash);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 4px 2px 11px; gap: 2px;
}
.biz-select {
  border: 0; background: transparent; font-family: inherit;
  font-size: 13px; font-weight: 600; color: var(--primary);
  padding: 4px 2px; cursor: pointer; max-width: 11em;
}
.biz-select:focus { outline: 2px solid var(--primary); outline-offset: 1px; border-radius: 6px; }
.biz-tab {
  border: 0; background: transparent; font-size: 13px; font-weight: 600;
  color: var(--muted); padding: 4px 14px; border-radius: 999px; cursor: pointer;
}
.biz-tab.active { background: var(--primary); color: #fff; }
.biz-lock { color: var(--primary); font-size: 12px; font-weight: 700; }
.content { padding: 22px 28px 40px; max-width: 1320px; width: 100%; margin: 0 auto; }

/* ============ 卡片 ============ */
.card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px 22px; margin-bottom: 18px;
}
.card.narrow { max-width: 560px; }
.card h2 { font-size: 15.5px; font-weight: 600; margin-bottom: 12px; letter-spacing: .3px; }
.card-head {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: 10px; margin-bottom: 12px;
}
.card-head h2 { margin-bottom: 0; }
/* 标题 + 主操作（如「添加客户」）同属左半区，右边留给筛选与批量操作 */
.card-title { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ============ 表格 ============ */
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td {
  padding: 10px 12px; border-bottom: 1px solid #EDF0EE;
  text-align: left; vertical-align: middle;
}
th {
  color: var(--faint); font-weight: 500; font-size: 12px;
  background: transparent; white-space: nowrap;
  border-bottom: 1px solid var(--border-strong); padding-top: 6px; padding-bottom: 7px;
}
tr:hover td { background: #F7F9F8; }
td { color: var(--ink-soft); }
td a { font-weight: 500; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.total-row td {
  background: var(--primary-wash); border-top: 1px solid var(--border-strong);
  color: var(--ink); border-bottom: none;
}
.row-disabled td { color: var(--faint); }
.actions { display: flex; gap: 6px; align-items: center; }

/* ============ 徽章：小圆点 + 文字（降噪） ============ */
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--ink-soft); font-weight: 500;
  white-space: nowrap; background: none; padding: 0;
}
.badge::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--faint); flex: 0 0 auto;
}
.st-draft::before { background: var(--warn); }
.st-confirmed::before { background: var(--ok); }
.st-cancelled { color: var(--chop); }
.st-cancelled::before { background: var(--chop); }
.inv-pending::before { background: var(--warn); }
.inv-invoiced::before { background: var(--ok); }
.inv-pending_confirm::before { background: var(--primary); }
.inv-queued::before { background: #7c3aed; }
.inv-issuing::before { background: #0ea5e9; }
.inv-issue_failed { color: var(--chop); }
.inv-issue_failed::before { background: var(--chop); }
.settle-unreconciled { color: var(--muted); }
.settle-uninvoiced::before { background: var(--warn); }
.settle-invoiced::before { background: var(--primary); }
.settle-paid { color: var(--ok); }
.settle-paid::before { background: var(--ok); }

/* ============ 表单 ============ */
label { display: block; margin-bottom: 10px; font-size: 13px; color: var(--muted); }
input, select, textarea {
  padding: 8px 11px; border: 1px solid var(--border-strong); border-radius: var(--radius-s);
  font-size: 14px; background: #fff; color: var(--ink);
  font-family: inherit; width: auto;
  transition: border-color .12s, box-shadow .12s;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(51,81,140,.12);
}
label input, label select, label textarea { display: block; margin-top: 4px; width: 100%; }
.inline-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.inline-form input, .inline-form select { width: auto; flex: 0 1 auto; }
.inline-form input[name=name] { min-width: 200px; }
.search-form input, .search-form select { width: auto; }
.search-form input[type=date] { color: var(--ink-soft); }
.w60 { width: 60px !important; } .w70 { width: 70px !important; }
.w90 { width: 90px !important; } .w120 { width: 120px !important; } .w40 { width: 40px; }
.form-row { display: flex; gap: 12px; flex-wrap: wrap; }
.form-row label { min-width: 180px; }
.form-row .grow { flex: 1; min-width: 220px; }
.form-actions { margin-top: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
input[type="file"] { border: none; background: none; }

/* ============ 按钮 ============ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 16px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-s);
  background: #fff; color: var(--ink-soft); font-size: 13.5px; font-weight: 500;
  font-family: inherit; cursor: pointer; text-decoration: none !important;
  transition: background .12s, border-color .12s, color .12s;
}
.btn:hover { background: #F6F8F7; border-color: var(--faint); color: var(--ink); }
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
.btn-danger { background: #fff; border-color: #E3B0A8; color: var(--chop); }
.btn-danger:hover { background: var(--chop-wash); border-color: var(--chop); color: var(--chop); }
.btn-sm { padding: 5px 11px; font-size: 12.5px; }
.btn-block { width: 100%; }
.inline { display: inline; }

/* ============ 提示 ============ */
.alert {
  padding: 11px 15px; border-radius: 10px; margin-bottom: 14px; font-size: 13px;
  border: 1px solid transparent;
}
.alert-ok { background: var(--ok-wash); color: #1F5B41; border-color: #C4DBD0; }
.alert-error { background: var(--chop-wash); color: #8A2A1E; border-color: #E5C2BB; }
/* 提示级（不阻断）：开票预检的「提示」项、通道告警等用暖色，与错误区分开 */
.alert-warn { background: var(--warn-wash, #FDF6E7); color: #8A6216; border-color: #EBD9AC; }

/* ============ 统计卡 ============ */
.stat-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-bottom: 20px; }
.stat-grid.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat-card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 20px 16px;
}
.stat-num {
  font-size: 26px; font-weight: 700; letter-spacing: -.3px;
  font-variant-numeric: tabular-nums; line-height: 1.25;
}
.stat-label { color: var(--muted); font-size: 12.5px; margin: 4px 0 10px; }
.stat-link { font-size: 12.5px; }
/* 语义仅靠小圆点，不再刷彩条 */
.stat-card.warn .stat-label::before,
.stat-card.danger .stat-label::before {
  content: ''; display: inline-block; width: 7px; height: 7px;
  border-radius: 50%; margin-right: 6px; vertical-align: 1px;
}
.stat-card.warn .stat-label::before { background: var(--warn); }
.stat-card.danger .stat-label::before { background: var(--chop); }
.stat-card.danger .stat-num { color: var(--chop); }
/* `ok` 一直是个空类（全站只在 templates/reconcile.html 的「已开票」卡上用过），
   于是同一排里 warn / danger 都带小圆点、只有它没有，看着像漏了。补上松绿点，
   与 warn 对称 —— danger 额外把数字刷红，ok 不动数字（避免一排里两处抢眼）。 */
.stat-card.ok .stat-label::before {
  content: ''; display: inline-block; width: 7px; height: 7px;
  border-radius: 50%; margin-right: 6px; vertical-align: 1px;
  background: var(--ok);
}

/* ============ 详情 ============ */
.detail-grid { display: flex; flex-wrap: wrap; gap: 8px 32px; padding: 10px 0 16px; font-size: 13.5px; }
.detail-grid .grow { flex: 1 1 100%; }
.invoice-summary {
  display: flex; gap: 32px; flex-wrap: wrap; margin-top: 14px; padding: 13px 16px;
  background: #F8FAF9; border-radius: 10px; font-size: 13px;
  border: 1px solid var(--border);
}
.issue-bar {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  background: var(--primary-wash); border: 1px solid #D3DDEB;
  border-radius: 10px; padding: 11px 14px; margin-bottom: 12px;
}
.pager { display: flex; gap: 12px; align-items: center; justify-content: flex-end; margin-top: 12px; color: var(--muted); font-size: 13px; flex-wrap: wrap; }
.pager-jump { display: flex; gap: 6px; align-items: center; margin: 0; }
.pager-jump input { width: 66px; text-align: center; }
.muted { color: var(--muted); font-size: 13px; }
.danger-text { color: var(--chop); font-weight: 600; }
.in-text { color: var(--ok); font-weight: 600; }

/* ============ 登录页：纸面 + 印章 ============ */
.login-page {
  display: flex; align-items: center; justify-content: center; min-height: 100vh;
  background: var(--paper);
  background-image: linear-gradient(var(--border) 1px, transparent 1px),
                    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 36px 36px;
}
.login-card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: 16px; padding: 36px 36px 28px; width: 380px;
  box-shadow: 0 12px 40px rgba(31,42,46,.07);
}
/* 竖排（商标 / 云进销存系统 / 副标题），左对齐 —— 与下面的表单标签同一条基线。
   注意：这里**必须**由本规则给出 flex-direction/align-items，别指望文件前面那段
   「品牌锁定」里的同名规则 —— 它在原规则之前，会被这一行盖掉（2026-09-21 实测踩到：
   商标被 align-items:center 居中，和左对齐的表单不是一套）。 */
.login-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; margin-bottom: 12px; }
.login-brand .seal { width: 38px; height: 38px; font-size: 19px; border-radius: 9px; }
.login-card h1 { font-size: 19px; margin-bottom: 4px; }
.login-card .sub { color: var(--muted); font-size: 13px; margin-bottom: 22px; }
.login-card input { width: 100%; }
.login-card .btn { margin-top: 8px; }
.login-card .btn-primary { width: 100%; padding: 10px; font-size: 14px; }

/* ============ 筛选面板（订单列表等） ============ */
.filter-panel { border: 1px solid var(--border); border-radius: 10px; background: #FAFBFA; padding: 12px 14px; margin-bottom: 4px; }
.filter-row { display: flex; flex-wrap: wrap; gap: 10px 12px; align-items: flex-end; }
.filter-row + .filter-row { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
.fld { display: flex; flex-direction: column; gap: 4px; margin: 0; min-width: 0; }
.fld > span { font-size: 11.5px; color: var(--faint); letter-spacing: .3px; }
.fld input, .fld select { width: auto; min-width: 120px; }
.fld.wide input, .fld.wide select { min-width: 200px; }
.fld-x { min-width: 96px; }
.fld-x input { min-width: 0; width: 100%; }
.filter-spacer { flex: 1 1 auto; }
.quick-dates { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; padding: 5px 11px; font-size: 12.5px;
  border: 1px solid var(--border-strong); border-radius: 999px; background: #fff;
  color: var(--ink-soft); cursor: pointer; font-family: inherit; line-height: 1.4;
}
.chip:hover { border-color: var(--faint); color: var(--ink); text-decoration: none; }
.chip.active { border-color: var(--primary); background: var(--primary-wash); color: var(--primary); font-weight: 600; }
a.chip { text-decoration: none !important; }

/* ============ 状态分段控件 ============ */
.seg-bar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 14px 0 0; }
.seg {
  display: inline-flex; align-items: baseline; gap: 6px; padding: 6px 13px;
  border: 1px solid var(--border); border-radius: 999px; background: #fff;
  font-size: 13px; color: var(--ink-soft); text-decoration: none !important;
  transition: border-color .12s, background .12s;
}
.seg:hover { border-color: var(--border-strong); }
.seg.active { border-color: var(--primary); background: var(--primary-wash); color: var(--primary); font-weight: 600; }
.seg .seg-n { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
.seg.active .seg-n { color: var(--primary); }
.seg .seg-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); align-self: center; }
.seg-draft .seg-dot { background: var(--warn); }
.seg-confirmed .seg-dot { background: var(--ok); }
.seg-cancelled .seg-dot { background: var(--chop); }

/* ============ 表格内的小字与行内操作 ============ */
.sub { display: block; font-size: 11.5px; color: var(--faint); margin-top: 1px; }
.row-actions { display: flex; gap: 6px; align-items: center; justify-content: flex-end; white-space: nowrap; }
.row-actions .btn { padding: 3px 8px; font-size: 12px; }
.ord-no { font-weight: 500; }
.empty-state { padding: 34px 16px; text-align: center; color: var(--muted); }
.empty-state .big { font-size: 26px; opacity: .5; margin-bottom: 8px; }
.table-wrap { overflow-x: auto; }

/* ============ 合计/粘性操作条 ============ */
.summary-bar {
  position: sticky; bottom: 0; z-index: 5;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: 16px; padding: 12px 16px;
  background: #fff; border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: 0 -6px 18px rgba(31,42,46,.06);
}
.summary-bar .sum-label { font-size: 12.5px; color: var(--muted); }
.summary-bar .sum-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.summary-bar .sum-spacer { flex: 1 1 auto; }

/* ============ 搜索型选择器 ============ */
.combo { position: relative; }
.combo input[type=text] { width: 100%; padding-right: 28px; }
.combo-clear {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  border: none; background: none; color: var(--faint); cursor: pointer;
  font-size: 15px; line-height: 1; padding: 2px 4px; display: none;
}
.combo.has-value .combo-clear { display: block; }
.combo-list {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; right: 0;
  max-height: 280px; overflow-y: auto; background: #fff;
  border: 1px solid var(--border-strong); border-radius: 10px;
  box-shadow: 0 10px 28px rgba(31,42,46,.14); display: none;
}
.combo.open .combo-list { display: block; }
.combo-item { padding: 7px 12px; cursor: pointer; font-size: 13px; display: flex; justify-content: space-between; gap: 10px; }
.combo-item:hover, .combo-item.hl { background: var(--primary-wash); }
.combo-item .ci-main { color: var(--ink); }
.combo-item .ci-meta { color: var(--faint); font-size: 11.5px; white-space: nowrap; }
.combo-item .ci-sub { display: block; font-size: 11.5px; color: var(--faint); }
.combo-empty { padding: 10px 12px; color: var(--muted); font-size: 12.5px; }

/* ============ 响应式 ============ */
@media (max-width: 860px) {
  .sidebar { display: none; }
  .stat-grid, .stat-grid.four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .content { padding: 14px; }
  table { display: block; overflow-x: auto; }
  .fld { flex: 1 1 140px; }
  .fld input, .fld select { min-width: 0; width: 100%; }
  .summary-bar { position: static; }
}

/* ============ 弹窗（表单型页面） ============
   客户管理的「新增/编辑客户」原来是首屏的一整块卡片，十几个输入框挤三行、
   说明文字堆两大段，把下面的客户列表挤出首屏。改成弹窗后列表回到第一屏，
   表单自己带标题栏 / 滚动区 / 固定操作条，字段一个一个带标签排版。 */
dialog.modal {
  padding: 0; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--card); color: var(--ink);
  width: min(720px, calc(100vw - 28px));
  max-height: min(86vh, 860px);
  box-shadow: 0 24px 64px rgba(31, 42, 46, .24);
  overflow: hidden;
  /* 必须写：全站 reset 的 `* { margin: 0 }` 压掉了 UA 给 dialog 的 margin:auto，
     少了它 showModal 的弹窗会贴着视口左上角（inset:0 但没有 auto 边距） */
  margin: auto;
  /* 布局属性只许写在这一条基规则里（不能写进下面的 [open]）：
     close() 删掉 open 属性时 display 被 allow-discrete 扣在 flex 上（弹窗还在画），
     但写在 [open] 里的 flex-direction 不被扣住、会立刻翻回 row —— 子元素横排、
     高度从内容高顶到 max-height、被裁掉的行露出来（「闪烁 + 变长 + 多一行字」）。
     放这里：关着时 display:none 看不到，开着时与关闭过渡期是同一只盒子。 */
  flex-direction: column;
}
dialog.modal[open] { display: flex; }
dialog.modal::backdrop { background: rgba(29, 45, 51, .40); }
dialog.modal > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

.modal-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 22px 14px; border-bottom: 1px solid var(--border);
}
.modal-head h2 { font-size: 15.5px; font-weight: 600; letter-spacing: .3px; }
.modal-x {
  border: 0; background: none; color: var(--faint); cursor: pointer;
  font-size: 14px; line-height: 1; padding: 6px 9px; border-radius: 8px;
  font-family: inherit; transition: background .12s, color .12s;
}
.modal-x:hover { background: var(--paper); color: var(--ink); }

.modal-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 6px 22px 20px; }
.modal-foot {
  flex: 0 0 auto;
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 13px 22px; border-top: 1px solid var(--border); background: #FBFCFB;
}

/* 分区小标题：一条细线拉满剩余宽度，比加粗大字安静 */
.form-sec {
  display: flex; align-items: center; gap: 10px; margin: 16px 0 12px;
  font-size: 11.5px; color: var(--muted); letter-spacing: .8px; white-space: nowrap;
}
.form-sec:first-child { margin-top: 10px; }
.form-sec::after { content: ''; flex: 1; height: 1px; background: var(--border); }

/* 两栏字段栅格；长说明的字段用 .full 占满整行 */
.fld-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 18px; }
.fld-grid .full { grid-column: 1 / -1; }
.modal .fld { gap: 5px; }
.modal .fld > span { font-size: 12.5px; color: var(--ink-soft); font-weight: 500; letter-spacing: 0; }
.modal .fld input { width: 100%; min-width: 0; padding: 9px 12px; }
.modal .fld input::placeholder { color: #A9B4B0; }
.req { color: var(--chop); font-style: normal; margin-left: 2px; }

/* 字段说明贴着字段走（原来是堆在表单底部的两大段灰字） */
.fld-hint { font-size: 11.5px; color: var(--faint); line-height: 1.6; }
.fld-hint b { color: var(--muted); font-weight: 600; }
.fld-hint code {
  background: var(--paper); border: 1px solid var(--border); border-radius: 5px;
  padding: 0 5px; font-size: 11px; color: var(--ink-soft);
}

@media (max-width: 860px) {
  .fld-grid { grid-template-columns: 1fr; }
  dialog.modal { width: calc(100vw - 20px); max-height: 92vh; }
  .modal-head, .modal-foot { padding-left: 16px; padding-right: 16px; }
  .modal-body { padding-left: 16px; padding-right: 16px; }
}

/* ============ 提交中：按钮锁住 + 转圈 ============
   带 data-busy 的按钮在表单提交瞬间变成「正在添加…」并 disabled，
   按一次就只发一次请求。没有这个反馈时人会以为没点上而连点。 */
.btn.is-busy { opacity: .85; cursor: progress; }
.btn.is-busy::before {
  content: ''; width: 11px; height: 11px; border-radius: 50%; flex: 0 0 auto;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
/* 行内操作列现在最多 5 个按钮（订单/编辑/下单链接/停用/删除）。
   文字一律不许折行：列被挤窄时宁可让表格横向滚动（.table-wrap 已经兜着），
   也不要每个按钮压成一列竖排的单字。 */
.actions { flex-wrap: nowrap; white-space: nowrap; }

/* ============ 服务健康页（/health） ============
   四态徽章 svc-<status>，状态值来自 utils/health_service.py 的
   ok / warn / error / off；配色沿用既有告警色板（--ok/--warn/--chop），
   文字色与 .alert-ok / .alert-warn 同源。 */
.svc-ok::before { background: var(--ok); }
.svc-ok { color: #1F5B41; }
.svc-warn::before { background: var(--warn); }
.svc-warn { color: #8A6216; }
.svc-error::before { background: var(--chop); }
.svc-error { color: var(--chop); }
.svc-off::before { background: var(--faint); }
.svc-off { color: var(--muted); }
.health-note { color: var(--muted); font-size: 12.5px; margin-top: 14px; }

/* 2026-09-19：/health 改成「全链路在线程度」面板后新加的三个类。
   面板自身的样式由 utils/monitor_report.py 的 CSS 注入（全部挂在 .mon 作用域下，
   变量前缀 --mon-*，不会碰到上面的色板）；这里只补**页面骨架**这几个类。 */
.health-section {
  font-size: 15px; font-weight: 600; color: var(--ink);
  margin: 28px 0 12px; padding-top: 18px; border-top: 1px solid var(--line);
}
.health-cmd {
  background: #0F172A; color: #E2E8F0; border-radius: 8px;
  padding: 10px 14px; font-size: 12.5px; overflow-x: auto;
  font-family: Consolas, "Courier New", monospace; margin: 10px 0 0;
}
/* 页面内的路径/文件名：全站没有 .mono（那个只在面板自己的作用域里），
   所以在建站的样式表里补一个等价类，免得路径渲染成普通正文。 */
.health-path {
  font-family: Consolas, "Courier New", monospace;
  font-size: 12px; color: var(--ink); background: var(--soft, #F1F5F9);
  padding: 1px 6px; border-radius: 6px;
}

/* ============ 登录页：只把「不是字段」的文字居中 ============
   用户口径（2026-09-21 原话）："登录界面字体除了登录名和密码都置于中央"。
   于是商标区、「云进销存系统」、副标题「订单 · 库存 · 开票管理」、提示条
   （含 `_password_guard.html` 那个全角提示 `alert pw-tip`）都居中；
   **「用户名」「密码」两行必须留在左边**（label 文本 + 它下面的输入框）——
   那是这条口径里点名的例外。登录按钮本来就是 `.btn-block` + `.btn` 的
   `justify-content: center`，文字天生居中，不用动。

   ⚠️ 刻意**不**写 `.login-card { text-align: center }`：那会把两个 label 也一起
   居中，正好把例外那一半毁掉（label 的文本在输入框上方，居中后像个小标题）。
   所以这里逐个点名要居中的，字段那两行一个字都不碰。

   作用域：登录流程的三张白卡片页 —— login.html / set_password.html /
   password_first.html 的卡片都带 `.login-card-center`（设置密码两页是 2026-09-24
   用户口径：商标与标题同样要居中，与 09-21 那条是**同一条口径**、只是那天只做了登录页）。

   放在文件末尾（而不是紧挨着上面那段 `.login-card`）：这样**不改动**它前面
   任何一行的行号 —— `templates/_password_guard.html` 那句注释引用着
   `static/style.css:400`（`.combo`），插在中间会当场把它撞歪（2026-09-21 真的
   撞歪过一次：上一轮往文件前部插了 28 行，把那个引用从 373 顶到 400）。
   ⚠️ 别指望工具帮你发现：`tools/doc_line_refs.py --code` **不扫 templates/**，
   那句引用没有守卫。往本文件前部插行时，顺手回 `_password_guard.html` 核一眼。 */
.login-card-center .login-brand { align-items: center; }
.login-card-center .sub,
/* `.muted` 是首次强制改密页末尾那段「记不住就写在只有你自己能看到的地方…」——
   它用的是 muted 而不是 sub，不点名的话会变成"整页都居中、就最后一段歪在左边"。 */
.login-card-center .muted,
.login-card-center .alert { text-align: center; }
/* 例外那一半钉在这里：将来谁图省事改成 `.login-card { text-align:center }`，
   这两行仍会把字段拉回左边（守卫 `tests/brand_logo_test.py` 第 7 节也钉着
   "设 text-align:center 的登录卡规则不许碰 label/input/form"）。 */
.login-card-center label { text-align: left; }

/* 设置密码两页比登录页多一行 `h1`（「设置密码」/「先设置你的新密码」），
   上面点名的那几条都不覆盖它 —— 2026-09-24 用户口径（截图里圈住的就是
   商标区 +「云进销存系统」+「设置密码」）。
   按本仓习惯仍挂在 `.login-card-center` 这个**主动加类**的选择器上（不写
   `.login-card h1`）：将来再出现一张白卡片页，它是"不居中"的默认状态，
   想居中得自己加那个类，而不是被动继承登录流程的排版。
   字段那两行照旧不动：`.login-card-center label` 的兜底同样管着这两页。 */
.login-card-center h1 { text-align: center; }

/* ============ 客户列表：收窄 + 「⋯ 更多」弹窗（2026-09-28 TASK customer_more_modal） ============
   本节全部是**追加**（本文件是主站唯一的全局样式表，弹层样式本来就都在这儿；
   客户页的行尾动作收进弹窗，动效要"全部弹窗统一"，只有写在这份全局表里才做得到）。
   既有规则一行没动 —— 上面那些行号因此一个都没漂。 */
/* 客户名 / 送货地址过长截断（悬停看全文由 title 给）。
   原来是写死在 <td style="..."> 上的，现在收成语义 class，列宽锁定不变。 */
.clip-name { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clip-addr { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 停用徽章：灰点灰字。故意不复用 .st-cancelled —— 那是"作废单据"的印章红语义，
   客户停用只是"不参与开单"，不是错误。 */
.st-disabled { color: var(--muted); }
.st-disabled::before { background: var(--faint); }

/* 「⋯ 更多」菜单：图标 + 动作名 + 一行说明 */
dialog.modal.modal-more { width: min(430px, calc(100vw - 28px)); }
dialog.modal.modal-sub { width: min(760px, calc(100vw - 28px)); }
.more-menu { padding: 8px 10px 10px; display: flex; flex-direction: column; gap: 1px; }
.more-item {
  display: flex; align-items: flex-start; gap: 10px; width: 100%;
  padding: 9px 12px; border: 0; border-radius: var(--radius-s);
  background: none; color: var(--ink); font-family: inherit;
  font-size: 13.5px; text-align: left; cursor: pointer; text-decoration: none;
  transition: background .12s;
}
.more-item:hover { background: var(--paper); text-decoration: none; }
.more-ico { flex: 0 0 auto; width: 20px; text-align: center; font-size: 15px; line-height: 1.5; }
.more-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.more-txt b { font-weight: 600; font-size: 13.5px; }
.more-txt small { color: var(--faint); font-size: 11.5px; line-height: 1.5; }
.more-sep { height: 1px; background: var(--border); margin: 6px 4px; }
.more-danger, .more-danger .more-txt b { color: var(--chop); }
.more-danger:hover { background: var(--chop-wash); }
/* 弹窗底部信息行（原「录入人」列的落点）：左对齐，不是操作条 */
.modal-info { justify-content: flex-start; font-size: 12px; color: var(--muted); }

/* 「收款情况」面板：三格数字 + 最近回款 */
.rcpt-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 10px 0 4px; }
.rcpt-cell {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--paper); border: 1px solid var(--border);
  border-radius: var(--radius-s); padding: 10px 12px;
}
.rcpt-cell span { font-size: 11.5px; color: var(--muted); letter-spacing: .5px; }
.rcpt-cell b { font-size: 17px; font-variant-numeric: tabular-nums; }
.rcpt-ok { color: var(--ok); }
.rcpt-chop { color: var(--chop); }
.rcpt-more { margin: 14px 0 2px; }
@media (max-width: 640px) { .rcpt-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ============ 弹层虚化 + 开合动画（全站 .modal 共用，2026-09-28） ============
   用户点名：弹窗外区域要**虚化**（半透明遮罩 + 毛玻璃），并且**加点动画**。
   · 遮罩色仍由上面既有那条 `dialog.modal::backdrop` 定，这段只补 backdrop-filter
     与进场动画 —— 既有规则一个字没改。
   · 出场态就是下面的基态（.18s ease-out）；进场由 [open] 过渡过去，**关闭走反向**
     同一组过渡。display/overlay 的 `allow-discrete` 让 dialog 在关闭的那一瞬仍被
     绘制，否则它会被立刻移出 top layer、根本看不到反向动画（不支持该关键字的浏览器
     只是退化成"直接消失"，功能不受影响）。
   · 减少动效偏好：本文件顶部那条 `@media (prefers-reduced-motion) { *{...} }` 已经
     把所有动效关了；这里再显式钉一次弹层的终态，保证"直接显示"而不是停在出场态。 */
dialog.modal {
  opacity: 0;
  transform: translateY(8px) scale(.982);
  transition: opacity .18s ease-out, transform .18s ease-out,
              display .18s allow-discrete, overlay .18s allow-discrete;
}
dialog.modal[open] { opacity: 1; transform: none; animation: modal-in .18s ease-out; }
@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.982); }
  to { opacity: 1; transform: none; }
}
/* 3px 是"看得见底下、又读不出内容"的那一档虚化 */
dialog.modal::backdrop {
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: modal-backdrop-in .18s ease-out;
  /* 关闭淡出（2026-09-28 修「闪烁」）：出场态 opacity 0，[open] 时过渡到 1。
     少了它遮罩全程 opacity=1，直到 display:none 落地才和弹窗一起硬消失。
     display/overlay 的 allow-discrete 让遮罩在 dialog 离场那 .18s 里仍被绘制。 */
  opacity: 0;
  transition: opacity .18s ease-out, display .18s allow-discrete, overlay .18s allow-discrete;
}
dialog.modal[open]::backdrop { opacity: 1; }
@keyframes modal-backdrop-in { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  dialog.modal,
  dialog.modal[open],
  dialog.modal::backdrop { animation: none !important; transition: none !important; }
  dialog.modal[open] { opacity: 1; transform: none; }
}

/* ============ 列表短值列不折行（2026-09-29 UI 视觉一致性修整 ui_polish_v1） ============
   症状（用户截图走查）：/orders 的订单号 `SO20260920-9001` 在连字符处折成两行、日期
   `2026-09-20` 与「管理员」也逐字折行 —— 一屏读不出这一单是谁、什么时候开的。
   根因不是"内容太长"，而是 `table-layout:auto` 在容器装不下时把每一列压到 min-content
   宽度：中文逐字可断、连字符处可断，于是**短值列被当成可折行列**了。
   改法：给「短值」列打 `.nw`（订单号 / 日期 / 业务员 / 出库仓 / 单数 / 最近下单…），
   再把三类天生的短值收成 nowrap —— `.num`（金额等数字列）、`.badge`（状态徽章）、
   `.ord-no`（订单号链接）。
   ⚠️ **刻意不写 `table { white-space: nowrap }`**：客户名 / 送货地址 / 备注这类长文本列
   要能按剩余宽度折行（`.clip-name` / `.clip-addr` 另带省略号先缩），整表 nowrap 会把它们
   顶成横向溢出，窄屏更糟。窄屏真放不下时由既有的 `.table-wrap { overflow-x:auto }` 兜底。
   本节全部是**追加**：`templates/_password_guard.html` 的注释按 `static/style.css:400`
   引用本文件（`.combo`），往中部插行会当场撞歪那个行号（2026-09-21 踩过一次）。 */
.nw { white-space: nowrap; }
.num, .badge, .ord-no { white-space: nowrap; }

/* ============ 长文本列封顶 2 行（2026-09-29 UI 走查第二轮 ui_polish_v2） ============
   短值列收成 nowrap 之后，`table-layout:auto` 会把剩下的宽度全给长文本列 —— 客户名那种
   又长又没空格的串会一直折下去（实测 /invoices 的「演示-华东餐饮（票款不符场景）」折了
   **5 行**，把整行顶到 133px）。这一节给长文本列一个「折，但最多折 2 行」的口径。
   用法：`<td title="全名"><span class="clamp-2">全名</span></td>` —— `title` 是给截断后的
   补偿（鼠标停住能看全），省略号由 `-webkit-line-clamp` 画。
   ⚠️ 必须套在**内层 span** 上、不能直接打在 `<td>`：`display:-webkit-box` 会把表格单元格
   变成块级盒子，`td` 一变成 box 就不再是 table-cell，整列的边框/对齐全散。
   ⚠️ 本节同样**只能追加在本文件末尾**（理由见上一节末尾：`_password_guard.html` 按
   `static/style.css:400` 引用 `.combo`，往中部插行会撞歪那个行号）。 */
.clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ============ 按钮基线：<a class="btn"> 与 <button class="btn"> 同高（2026-09-29 UI 走查第三轮 ui_polish_v3） ============
   实测（本机真 DOM，/invoices/new 的动作条，一眼能看出不齐）：同一行相邻的
   `<a class="btn">` 高 **40px**、`<button class="btn">` 高 **37px** —— 同一个类、
   同一个页面里差 3px，按钮和链接的下边缘对不齐。
   根因是 `line-height` 的继承差异（不是 padding，两边的 padding 都是 `8px 16px`）：
   `<a>` 继承 body 的 `line-height: 1.6`（13.5px → 21.6px 行盒），`<button>` 拿到的是
   浏览器 UA 的 `line-height: normal`（约 19px），而 `.btn` 自己没写这一条。
   补上之后 `<a>` 一个像素不变（1.6 本来就是它继承到的那个值），`<button>` 长到同高。
   `.btn-sm` 同理（12.5px：A 32px / BUTTON 28px → 都 32px）。
   ⚠️ 打印页族不受影响：`order_print` / `order_batch_print` / `reconcile_print` 三页的
      按钮从来不用 `.btn`（它们自带 `#paper-btns` 那套样式，且打印时被 `.no-print` 藏掉）。
   ⚠️ 本节**只能追加在文件末尾**（理由同上两节末尾：`templates/_password_guard.html`
      按 `static/style.css:400` 引用 `.combo`，往中部插行会撞歪那个行号）。 */
.btn { line-height: 1.6; }

/* ============ 主站窄屏收敛（2026-09-29 低危遗留清扫 low_risk_sweep） ============
   桌面口径（≥768px）一条都不动，这里只在 ≤767.98px 追加窄屏规则（375px 实测的遗留项）：
   ① **顶栏折行收敛**：h1.page-title 允许收缩省略（不再和用户区互相挤着折 2~4 行）、
      .topbar-user 允许换行 —— 顶栏整体变高可以，横向溢出不行；
   ② **侧栏抽屉**：既有规则在 ≤860px 就把 .sidebar 设成 display:none（窄屏没法导航）；
      这里只在 ≤767.98px 把它接管成**可展开的抽屉**—— body 开头的 #nav-toggle 复选框 +
      顶栏 .nav-burger 汉堡（纯 CSS 驱动，不加一行 JS），点开滑出 216px 侧栏，
      抽屉内 ✕（.nav-close）收起。**768~860px 一格不动**：那一档保持既有「无侧栏」原样
      （≥768px 是冻结的桌面口径，本轮只清 375px 的遗留项）。
   ③ 输入框 / 下拉的 **37px 主站口径不随移动端改**（H5 的 ≥40px 触控口径只归 portal 族，
      见 docs 风险 143 的遗留登记）。
   ⚠️ 本段只能追加在文件末尾：`templates/_password_guard.html` 按 `static/style.css:400`
      引用 `.combo`，往中部插行会撞歪那个行号（2026-09-21 踩过一次）。 */
.nav-toggle { position: absolute; opacity: 0; pointer-events: none; }
.nav-burger, .nav-close { display: none; }   /* 桌面端不出现 */

@media (max-width: 767.98px) {
  .nav-burger {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; width: 36px; height: 36px;
    border: 1px solid var(--border); border-radius: var(--radius-s);
    background: #fff; color: var(--ink-soft); cursor: pointer;
  }
  .nav-burger svg { width: 17px; height: 17px; }
  .nav-burger:hover { color: var(--primary); border-color: var(--border-strong); }

  /* 顶栏：标题可收缩省略，用户区可换行 —— 两者不再互相挤压着各自折 2~4 行 */
  .topbar { flex-wrap: wrap; gap: 6px 12px; padding: 10px 14px; }
  .page-title { flex: 1 1 auto; min-width: 0; white-space: nowrap;
                overflow: hidden; text-overflow: ellipsis; }
  .topbar-user { flex-wrap: wrap; gap: 8px 12px; justify-content: flex-end; }

  /* 侧栏抽屉：覆盖既有 860px 的 display:none；默认滑出屏幕，点汉堡展开 */
  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 60;
    display: flex; height: 100vh;
    transform: translateX(-100%);
    transition: transform .2s ease-out;
    box-shadow: 0 0 28px rgba(15, 23, 26, .35);
  }
  .nav-toggle:checked ~ .layout .sidebar { transform: translateX(0); }
  .nav-close {
    display: inline-flex; align-items: center; justify-content: center;
    position: absolute; top: 12px; right: 12px;
    width: 34px; height: 34px; border-radius: 8px;
    border: 0; background: transparent;
    color: var(--sidebar-ink); font-size: 16px; cursor: pointer;
  }
  .nav-close:hover { background: rgba(255, 255, 255, .08); color: #fff; }
}

/* ============ 表单控件不许撑破容器（2026-09-29 全站 UI 增量巡检 ui_site_sweep） ============
   巡检口径：1280 / 900 / 375 三档真浏览器 DOM，判据 `documentElement.scrollWidth > clientWidth`。
   **900 档**的整页横滚全部来自"裸表"（没有 `.table-wrap` 兜底的那批）—— 修法按既有约定写在
   模板侧（包一层 `.table-wrap`），本节不碰。本节只收 **375 档**这类根因（与表格无关）：
   表单控件按 max-content 撑开自己的容器 —— `/invoices`、`/stock` 的筛选下拉（选项里带长客户名 /
   长商品名）实测 366~368px、`/push-settings` 的 `select` 554px（那一页整页顶出 216px）、
   开票界面的 `.inv-head` 同理。
   为什么一条 `max-width` 就够：`static/style.css:234` 的 `.search-form select { width: auto }`
   与各页行内 `style="width:420px"` 在窄屏都是"写多大就多大"；而 `max-width` 在层叠里**压得住
   行内 width**（最终宽度 = min(width, max-width)），所以不必去改那些行内宽度。
   `<label>` 那半条是配套的：`.inline-form` 的 label 是 flex 项，默认最小尺寸 = max-content
   （= select 里最宽的那个选项），不给 `min-width: 0` 就缩不下去。
   ⚠️ **只在 ≤767.98px 追加**，桌面口径（≥768px）一格不动：1280 / 768 两档逐元素计算样式比对过。
   ⚠️ 本节只能追加在文件末尾（理由同上：`templates/_password_guard.html` 按 `static/style.css:400`
      引用 `.combo`，往中部插行会撞歪那个行号）。 */
@media (max-width: 767.98px) {
  input, select, textarea { max-width: 100%; }
  .inline-form > label { min-width: 0; max-width: 100%; }
  .toolbar, .search-form { min-width: 0; max-width: 100%; }
}

/* ============ 统计卡窄屏不许被大数字撑破（2026-09-29 ui_site_sweep，接租赁那轮的同一课） ============
   `static/style.css:433` 那条 ≤860px 回落写的是 `1fr 1fr`，而 `1fr` = `minmax(auto, 1fr)`，
   **auto 下限就是 min-content**：26px 的 `¥2,461,833.21`（≈190px）比格子宽，整排卡片于是
   把整页顶出横向滚动条。375px 实测：/invoices/reconcile 溢出 10px、/invoices/reconcile/settle
   溢出 74px（归因到 `.stat-card` / `.stat-num`）。
   口径与 dashboard / rentals 两页一致（`static/pages/dashboard.css` / `rentals.css` 先例）：
   允许格子收缩 + 最窄收成单列（单列时 347px 全宽，七位数也放得下）。
   选择器只用**一个类**，页内那几份页样式（`.stat-grid.four` / `.five` / `.stat-grid-4`）
   特异性更高、且在本文件之后加载 ⇒ 它们各自的口径不受影响（本文件只兜住没写页样式的那几页）。
   ⚠️ 这一段覆盖到 768~860px（既有回落写的就是 ≤860）：档位不变（仍是两列），只是把
      "格子不许缩到 min-content 以下"放开成可收缩 —— 内容放得下时逐像素同旧版。 */
@media (max-width: 860px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .stat-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============ 统计卡中档回落 + 裸 `1fr` 收口（2026-09-29 stat_grid_guard） ============
   背景：`1fr` 不是"能收缩的一格"，它是 `minmax(auto, 1fr)`，**auto 下限 = min-content** ——
   格子里一个 `¥10,974,915.35`（26px ≈ 183px，本机测试库 /rentals/due 的真实值）就把整排
   格子按 min-content 撑开，顶出**整页**横向滚动。风险 147（五连）/ 149（四连）各修过一处，
   但**共享样式里还剩两处裸 `1fr`**（本文件上面的 `.stat-grid` / `.stat-grid.four`，已就地改
   成 `minmax(0, 1fr)`，值替换、行数不变、行号不漂），本段补它们缺的两档回落。

   实测（本机真浏览器 iframe 载真路由、登录态；把 `.stat-num` 换成最坏值 `¥10,974,915.35`
   后量 `documentElement.scrollWidth - clientWidth`，改前 → 改后）：
     · /health   900px **286 → 0**、1180px **6 → 0**（共享 `.four` 在 861~1200 没有回落档）
     · /stock/levels 900px **46 → 0**（共享 `.stat-grid` 三列、每格 ~199px 装不下 183px 金额）
     · /invoices/reconcile/settle 900px **69 → 0**（同一页第二个 `.stat-grid` 三连卡）
   档位口径照既有先例（`static/pages/dashboard.css` / `rentals.css` 的 ≤1120 两列、
   `static/pages/reports.css` 的 ≤1200 两列 + ≤560 单列）。

   ⚠️ 选择器只用**一个/两个类**，且本段在文件末尾：页内那几份页样式（`.stat-grid.four` /
   `.five` / `.stat-grid-4`）**后加载、同特异性后者胜** ⇒ rentals / reports / reconcile
   三页各自的档位一个字不受影响（只有 health 这种"没写页样式"的页面吃到本节）。
   ⚠️ 永久判据 `tests/template_script_json_test.py` 第 12 节：任何 `stat-grid` 类的列定义
      出现裸 `1fr` 即判红（值级扫描 + 非统计卡网格双向登记表 + 回落档单调性 + 注入式反证）。 */
@media (max-width: 1120px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 1200px) {
  .stat-grid.four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .stat-grid.four { grid-template-columns: minmax(0, 1fr); }
}
