/* ─────────────────────────────────────────────────────────────
   年费推送 · 后台样式
   只放后台外壳与业务组件；配色、排版全部走 CSS 变量
   ───────────────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box}
:root{
  /* 色板：与原型一致（tech-blue） */
  --h:248;
  --brand:oklch(48% .10 var(--h));
  --brand-ink:oklch(40% .11 var(--h));
  --brand-soft:oklch(96% .015 var(--h));
  --brand-line:oklch(88% .03 var(--h));
  --on-brand:#fff;

  /* 中性色由主色相派生（纯灰是"死"的） */
  --bg:oklch(97.6% .004 var(--h));
  --surface:oklch(99.4% .002 var(--h));
  --fill:oklch(96.2% .005 var(--h));
  --line:oklch(91.5% .006 var(--h));
  --line2:oklch(94.5% .005 var(--h));
  --ink-1:oklch(26% .01 var(--h));
  --ink-2:oklch(50% .012 var(--h));
  --ink-3:oklch(60% .012 var(--h));

  /* 语义色：实色做底，-ink 做字 */
  --ok:oklch(58% .13 150); --ok-ink:oklch(42% .12 150); --ok-soft:oklch(96% .02 150);
  --warn:oklch(72% .14 75); --warn-ink:oklch(46% .11 65); --warn-soft:oklch(96.5% .03 80);
  --err:oklch(58% .17 25); --err-ink:oklch(44% .15 25); --err-soft:oklch(96% .02 25);

  --r-1:4px; --r-2:6px; --r-3:8px;
  --e-1:0 1px 2px oklch(0% 0 0/.05);
  --e-2:0 2px 8px oklch(0% 0 0/.07),0 0 0 1px oklch(0% 0 0/.03);
  --e-3:0 8px 32px oklch(0% 0 0/.12),0 0 0 1px oklch(0% 0 0/.04);

  --f-sans:"PingFang SC","Microsoft YaHei",-apple-system,"Segoe UI",sans-serif;
  --f-mono:"SFMono-Regular",Consolas,"Liberation Mono",monospace;
  --fs-11:11px; --fs-12:12px; --fs-13:13px; --fs-18:18px; --fs-24:24px; --fs-34:34px;
  --fw-500:500;
  /* 侧边栏宽度 216 → 324（×1.5）：中文菜单项加上图标后 216px 偏挤，
     加宽后「客户与邮箱」这类长标签不再贴边。 */
  --h-top:56px; --w-nav:324px;
}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--ink-1);font-family:var(--f-sans);
  font-size:14px;line-height:1.72;-webkit-font-smoothing:antialiased;font-synthesis:none}
[hidden]{display:none !important}
h1,h2,h3,h4{margin:0;font-weight:var(--fw-500)}
button{font-family:inherit;cursor:pointer}
input,select,textarea{font-family:inherit;font-size:inherit}
.mono{font-family:var(--f-mono)}
.num{font-variant-numeric:tabular-nums;text-align:right}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}

/* ── 登录页 ── */
.auth{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--bg);padding:24px}
.auth-card{width:100%;max-width:420px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-3);padding:36px 32px;box-shadow:var(--e-2)}
.auth-card .mark{width:44px;height:44px;border-radius:10px;background:var(--brand);color:#fff;
  display:flex;align-items:center;justify-content:center;margin-bottom:18px}
.auth-card h1{font-size:20px;margin-bottom:6px}
.auth-card .sub{color:var(--ink-2);font-size:13px;margin-bottom:24px}
/* ── 登录按钮 ─────────────────────────────────────────────────
   图标 + 文字的实心主按钮，是整页唯一的视觉焦点。

   ⚠️ 必须显式处理 :focus。浏览器给按钮的默认焦点环是
      outline + 一圈浅灰底（Chromium 的 :focus-visible 行为），
      点击后按钮外面套一层灰框，看起来像「按钮坏了 / 被禁用」。
      这里改成与主色一致的光晕，既保留键盘可达性提示，又不破坏外观。
*/
.auth-card .btn-login{width:100%;height:46px;font-size:14.5px;justify-content:center;
  gap:9px;border-radius:9px;border:1px solid var(--brand);background:var(--brand);
  color:var(--on-brand);display:flex;align-items:center;
  box-shadow:0 2px 8px oklch(48% .10 var(--h) / .22);
  transition:transform .14s,box-shadow .14s,opacity .14s,background .14s}
.auth-card .btn-login:hover{transform:translateY(-1px);background:var(--brand-ink);
  box-shadow:0 6px 16px oklch(48% .10 var(--h) / .28);opacity:1}
.auth-card .btn-login:active{transform:translateY(0);
  box-shadow:0 1px 4px oklch(48% .10 var(--h) / .2)}
/* 覆盖基础 .btn 的灰色 hover 与浏览器默认焦点环 */
.auth-card .btn-login:focus,.auth-card .btn-login:focus-visible{
  outline:none;background:var(--brand);
  box-shadow:0 0 0 3px oklch(48% .10 var(--h) / .22),
             0 2px 8px oklch(48% .10 var(--h) / .22)}
.auth-card .btn-login:focus:hover,.auth-card .btn-login:focus-visible:hover{
  background:var(--brand-ink)}
.auth-card .btn-login .ico{display:flex;align-items:center;justify-content:center;
  width:20px;height:20px;opacity:.92}

/* ── 备用登录入口 ─────────────────────────────────────────────
   只在「拿不到 token」时出现：给用户一条能自己走出去的路，
   而不是把失败原因丢给他、让他困在这一页。
*/
.auth-alt{margin-top:14px;padding:11px 13px;border:1px solid var(--line);
  border-radius:var(--r-2);background:var(--fill);font-size:12px;line-height:1.7}
.auth-alt .alt-t{display:block;color:var(--ink-3);font-size:11.5px}
.auth-alt .alt-a{display:inline-block;margin-top:2px;color:var(--brand-ink);
  font-weight:var(--fw-500);text-decoration:none}
.auth-alt .alt-a:hover{text-decoration:underline}
.auth-alt .alt-h{display:block;color:var(--ink-3);font-size:11px;margin-top:1px}
.auth-err{margin-top:14px;padding:10px 12px;border-radius:var(--r-2);font-size:12.5px;
  background:var(--err-soft);color:var(--err-ink);line-height:1.6}
.auth-tip{margin-top:20px;padding-top:16px;border-top:1px solid var(--line2);
  font-size:11.5px;color:var(--ink-3);line-height:1.7}

/* ── 外壳 ── */
.app{display:flex;height:100vh;overflow:hidden}
.side{width:var(--w-nav);flex:none;background:var(--surface);border-right:1px solid var(--line);
  display:flex;flex-direction:column}
.side-brand{height:var(--h-top);display:flex;align-items:center;gap:12px;padding:0 20px;
  border-bottom:1px solid var(--line);font-weight:var(--fw-500);font-size:15.5px;flex:none}
.side-brand .mark{width:30px;height:30px;border-radius:7px;background:var(--brand);color:#fff;
  display:flex;align-items:center;justify-content:center;flex:none;font-size:15px}
.side-scroll{flex:1;overflow-y:auto;padding:12px 12px 20px}
.side-group{font-size:12.5px;color:var(--ink-3);letter-spacing:.04em;padding:17px 12px 7px}
.side-item{display:flex;align-items:center;gap:12px;height:45px;padding:0 12px;border-radius:var(--r-2);
  color:var(--ink-2);font-size:14.5px;cursor:pointer;transition:background .16s,color .16s;
  border-left:2px solid transparent;
  /* 行高 36 → 45（×1.25）：字号提到 14.5 后 36px 行高太紧，
     45px 让上下留白与字号比例保持在原来的水平。 */
  margin-bottom:2px;
  /* 行高必须和容器等高：默认 line-height 会让中文文字盒达到 22px，
     flex 的 align-items:center 是按「盒子」居中，而中文字形的视觉重心
     偏下 —— 实测图标中心 15、文字中心 18，差 3px，看着就是没对齐。
     设 line-height:1 后文字盒 ≈ 字号 13px，两者中心都落到 18。 */
  line-height:1}
.side-item>span{display:flex;align-items:center;line-height:1}
.side-item:hover{background:var(--fill);color:var(--ink-1)}
.side-item.on{background:var(--brand-soft);color:var(--brand-ink);border-left-color:var(--brand);
  font-weight:var(--fw-500)}
/* 图标 19px：字放大到 14.5 后，19px 与文字视觉重量才匹配。
   translateY(-2px)：中文字形的视觉中心比 flex 盒子的几何中心偏下，
   图标按盒子居中的话会显得矮半格；上移 2px 让两者视觉中线重合。 */
.side-item svg{flex:none;opacity:.9;width:19px;height:19px;
  transform:translateY(-2px)}
.side-item .badge{margin-left:auto;font-size:12.5px;background:var(--err-soft);color:var(--err-ink);
  padding:0 7px;border-radius:10px;height:19px;line-height:19px;font-variant-numeric:tabular-nums}
.side-foot{flex:none;border-top:1px solid var(--line);padding:12px}

/* ── 身份卡片 ─────────────────────────────────────────────────
   一行放三样：头像 / 姓名+角色 / 退出按钮。
   退出做成图标按钮而不是整行文字按钮 —— 退出是低频且需谨慎的操作，
   给一整行反而抢了身份信息的视觉重心，也让底部显空。
*/
.me-card{display:flex;align-items:center;gap:11px;padding:9px 10px;border-radius:var(--r-2);
  transition:background .16s}
.me-card:hover{background:var(--fill)}
.me-card .av{width:36px;height:36px;border-radius:50%;background:var(--brand-soft);
  color:var(--brand-ink);display:flex;align-items:center;justify-content:center;
  font-size:13.5px;flex:none;font-weight:var(--fw-500);letter-spacing:0}
.me-card .meta{min-width:0;flex:1;line-height:1.35}
.me-card .meta b{display:block;font-size:14px;font-weight:var(--fw-500);
  color:var(--ink-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.me-card .meta span{display:block;font-size:12.5px;color:var(--ink-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 退出：默认低调，hover 才泛红，避免误点也避免刺眼 */
.me-out{flex:none;width:28px;height:28px;border-radius:6px;border:1px solid transparent;
  background:transparent;color:var(--ink-3);display:flex;align-items:center;
  justify-content:center;transition:background .15s,color .15s,border-color .15s}
.me-out:hover{background:var(--err-soft);color:var(--err-ink);border-color:transparent}

/* ── 开发期角色切换（默认收起）─────────────────────────────── */
.devrole-wrap{margin-top:6px;padding-top:8px;border-top:1px dashed var(--line2)}
.devrole-toggle{width:100%;height:24px;display:flex;align-items:center;justify-content:space-between;
  gap:6px;padding:0 8px;border:none;background:transparent;border-radius:5px;
  font-size:10.5px;color:var(--ink-3);font-family:inherit;letter-spacing:.02em}
.devrole-toggle:hover{background:var(--fill);color:var(--ink-2)}
.devrole-toggle svg{transition:transform .18s}
.devrole-toggle.on svg{transform:rotate(180deg)}
.devrole{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-top:6px}
.devrole button{height:26px;padding:0 6px;border:1px solid var(--line);background:var(--surface);
  border-radius:5px;font-size:11px;color:var(--ink-2);font-family:inherit;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  transition:background .14s,border-color .14s,color .14s}
.devrole button:hover{border-color:var(--brand);color:var(--brand-ink);background:var(--brand-soft)}
.devrole button.on{background:var(--brand);border-color:var(--brand);color:#fff}

.main{flex:1;min-width:0;display:flex;flex-direction:column}
.top{height:var(--h-top);flex:none;display:flex;align-items:center;gap:14px;padding:0 24px;
  border-bottom:1px solid var(--line);background:var(--surface)}
.top h1{font-size:15px;letter-spacing:-.01em}
.crumb{display:flex;gap:8px;font-size:12px;color:var(--ink-3)}
.ml-auto{margin-left:auto}
.scroll{flex:1;overflow-y:auto;padding:24px}
/* 后台页面本身**铺满**，不加全局最大宽度。
   限宽只用在概览页的图表网格里（见 .ov-grid）——
   把留白塞给整个后台是错的：表格类页面列多、铺满才好用，
   用户要的是「数据看得全」，不是「卡片居中好看」。 */

/* ── 基础件 ── */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:34px;padding:0 14px;
  border:1px solid var(--line);border-radius:var(--r-2);background:var(--surface);color:var(--ink-1);
  font-size:13px;transition:background .16s,border-color .16s,transform .08s}
.btn:hover{background:var(--fill)}
.btn:active{transform:translateY(1px)}
.btn:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.btn-main{background:var(--brand);border-color:var(--brand);color:var(--on-brand)}
.btn-main:hover{background:var(--brand-ink);border-color:var(--brand-ink)}
.btn-ghost{background:transparent}
.btn-quiet{background:transparent;border-color:transparent;color:var(--ink-2);padding:0 8px}
.btn-quiet:hover{background:var(--fill);color:var(--ink-1)}
.btn-sm{height:28px;padding:0 10px;font-size:12px}
.btn:disabled,.btn.is-disabled{opacity:.45;cursor:not-allowed;transform:none}
.inp,.sel{height:34px;padding:0 10px;border:1px solid var(--line);border-radius:var(--r-2);
  background:var(--surface);color:var(--ink-1);font-size:13px;width:100%}
.inp:focus,.sel:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
textarea.inp{height:auto;padding:8px 10px;line-height:1.6;resize:vertical}
.field{display:flex;flex-direction:column;gap:6px}
.field .lb{font-size:12px;color:var(--ink-2)}

.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-3);overflow:hidden}
.panel + .panel{margin-top:16px}
/* ⚠️ 网格内的面板是「兄弟」关系，也会命中 .panel+.panel 的 margin-top，
   导致同一行右面板整体下移 16px（实测 392 vs 376、416 vs 400）。
   同行用 gap 控制间距，这里必须清零。 */
.chart-grid>.panel + .panel,
.chart-grid>.panel{margin-top:0}
.panel-h{display:flex;align-items:center;gap:12px;min-height:50px;padding:0 18px;
  border-bottom:1px solid var(--line2)}
.panel-h h3{font-size:14.5px;white-space:nowrap}
/* 说明文字单行截断：换行会把标题条撑高，同排面板的标题条就不一样高了。
   这里必须显式清掉 .note 的蓝底与内边距 —— .note 在样式表里定义更靠后，
   单靠 .note-plain 的同等优先级盖不住，会变成一块淡蓝色小标签。 */
.panel-h .note{flex:1;min-width:0;font-size:11.5px;color:var(--ink-3);
  background:transparent;padding:0;border-radius:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.panel-b{padding:18px}
.panel-b.flush{padding:0}

.tb{width:100%;border-collapse:collapse;font-size:13px}
.tb th{text-align:left;font-weight:400;font-size:11.5px;color:var(--ink-3);padding:9px 14px;
  border-bottom:1px solid var(--line);white-space:nowrap;background:var(--bg)}
.tb td{padding:10px 14px;border-bottom:1px solid var(--line2);color:var(--ink-2)}
.tb tr:last-child td{border-bottom:none}
.tb tbody tr:hover{background:var(--brand-soft)}
.tb td.num,.tb th.num{text-align:right;font-variant-numeric:tabular-nums}
/* 数字/编号类单元格禁止折行。
   日期 2026-10-22 会优先在连字符处断开，变成「2026-10-2 / 2」，
   读起来像两个值；金额、单号被折断同理。
   （案件名称、案件阶段这类中文长文本仍允许换行，否则表格会撑得过宽） */
.tb td.num,.tb td.mono{white-space:nowrap}
.tb-empty{padding:40px;text-align:center;color:var(--ink-3);font-size:13px}

/* ── 客户明细展开 ────────────────────────────────────────────
   「明细」列的数字做成胶囊按钮，点开在下一行插入明细表。
   同时只允许展开一个客户：客户动辄几十户，多开会把列表推得很长。
*/
.row-toggle{cursor:pointer}
.row-toggle:hover td{background:var(--brand-soft)}
.row-toggle.on td{background:var(--brand-soft)}
.row-toggle td:first-child{white-space:nowrap}
.exp-caret{display:inline-block;width:14px;color:var(--ink-3);font-size:10px;
  margin-right:2px}
/* 业务人员列：多数客户 1 人，少数 2 人 */
.cli-recv{white-space:nowrap;color:var(--ink-1);max-width:180px;
  overflow:hidden;text-overflow:ellipsis}
.cli-recv .more{color:var(--ink-3);font-size:11px}
.cli-recv .dim{color:var(--ink-3)}

/* ── 明细展开按钮 ────────────────────────────────────────────
   原来是个白底细边框的圆角块，看着像输入框/标签，看不出能点。
   改成「淡蓝底 + 数字 + 下拉箭头」的小胶囊：
     · 淡蓝底 = 可交互的暗示（和表格里的纯文字区分开）
     · 箭头 = 明确「会展开」
     · 展开时实心主色，状态一眼可辨
*/
.exp-btn{display:inline-flex;align-items:center;gap:5px;height:24px;
  padding:0 9px;border:none;border-radius:12px;
  background:var(--brand-soft);color:var(--brand-ink);
  font-size:12px;font-family:inherit;cursor:pointer;
  font-variant-numeric:tabular-nums;line-height:1;
  transition:background .15s,color .15s,transform .1s}
.exp-btn .n{font-weight:var(--fw-500)}
.exp-btn .chev{display:flex;align-items:center;justify-content:center;
  width:10px;height:10px;opacity:.6;
  transition:transform .18s,opacity .15s}
.exp-btn:hover{background:var(--brand);color:var(--on-brand)}
.exp-btn:hover .chev{opacity:1}
.exp-btn:active{transform:translateY(1px)}
/* 展开态：实心 + 箭头翻转 */
.exp-btn.on{background:var(--brand);color:var(--on-brand)}
.exp-btn.on .chev{transform:rotate(180deg);opacity:1}
.exp-btn:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.exp-row>td{padding:0 !important;background:var(--bg)}
.exp-cell{border-bottom:1px solid var(--line);padding:0 !important}
/* 展开容器：由 JS 控制 max-height 做高度过渡。
   overflow:hidden 是必须的 —— 否则过渡过程中内容会溢出到下一行。
   过渡结束后 JS 会把 max-height 设成 none，让翻页换内容时不被截断。
   transition 的时长要和 app.js 的 EXP_MS 对齐（220ms）。 */
.exp-wrap{overflow:hidden;max-height:0;
  transition:max-height .22s cubic-bezier(.4,0,.2,1);will-change:max-height}
@media(prefers-reduced-motion:reduce){
  .exp-wrap{transition:none}
}
.exp-inner{background:var(--bg)}
.exp-loading{padding:18px;text-align:center;color:var(--ink-3);font-size:12px}
/* 内层小表：比主表紧一点，且左侧留出缩进表示「从属」 */
.exp-tb{font-size:12px;background:var(--surface);
  border-left:3px solid var(--brand-soft)}
.exp-tb th{padding:8px 12px;background:var(--bg);font-size:11px}
.exp-tb td{padding:8px 12px}
/* ── 反馈中心：推进状态 + 处理方式分布 ────────────────────────
   目的：即使一条反馈都没有，页面也要能回答「为什么没有」——
   是没推送、没打开、还是打开了没提交。
*/
/* ── 花名册同步抽屉 ────────────────────────────────────────── */
.hr-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}
.hr-stat{display:flex;flex-direction:column;gap:2px;padding:10px 12px;
  border-radius:var(--r-2);background:var(--fill)}
.hr-stat .k{font-size:11px;color:var(--ink-3)}
.hr-stat .v{font-size:19px;line-height:1.2;color:var(--ink-1);
  font-variant-numeric:tabular-nums;font-weight:var(--fw-500)}
.hr-stat.ok{background:var(--ok-soft)}
.hr-stat.ok .v{color:var(--ok-ink)}
.hr-stat.warn{background:var(--warn-soft)}
.hr-stat.warn .v{color:var(--warn-ink)}
.hr-h{margin:18px 0 8px;font-size:12.5px;color:var(--ink-2);
  font-weight:var(--fw-500)}
.hr-tb{font-size:12px;border:1px solid var(--line);border-radius:var(--r-2);
  overflow:hidden}
.hr-tb th{padding:7px 10px;font-size:11px}
.hr-tb td{padding:6px 10px}
/* 歧义清单：只读展示，提醒去 HR 系统确认 */
.hr-amb{border:1px solid var(--line);border-radius:var(--r-2);overflow:hidden;
  max-height:200px;overflow-y:auto}
.hr-amb-row{display:flex;gap:10px;padding:7px 10px;font-size:11.5px;
  border-bottom:1px solid var(--line2)}
.hr-amb-row:last-child{border-bottom:none}
.hr-amb-row .nm{flex:none;width:64px;color:var(--ink-1);font-weight:var(--fw-500)}
.hr-amb-row .cands{flex:1;min-width:0;color:var(--ink-3);font-family:var(--f-mono);
  word-break:break-all}

.fb-funnel{margin:0 0 16px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-3);padding:16px 18px;box-shadow:var(--e-1)}
.fb-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
@media(max-width:900px){.fb-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}
.fb-step{display:flex;flex-direction:column;gap:1px;min-width:0}
.fb-step .k{font-size:11.5px;color:var(--ink-3)}
.fb-step .v{font-size:20px;color:var(--ink-1);line-height:1.25;
  font-variant-numeric:tabular-nums;font-weight:var(--fw-500)}
.fb-step .v .unit{font-size:11.5px;color:var(--ink-3);margin-left:2px;font-weight:400}
.fb-step .h{font-size:10.5px;color:var(--ink-3)}

/* 五种处理方式：横向小条，0 也显示（让人知道有哪些分类） */
.fb-types{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:8px 18px;margin-top:14px;padding-top:14px;border-top:1px solid var(--line2)}
.fb-type{display:grid;grid-template-columns:66px minmax(0,1fr) 28px;gap:8px;
  align-items:center;font-size:11.5px}
.fb-type .nm{color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fb-type .track{height:5px;border-radius:3px;background:var(--fill);overflow:hidden}
.fb-type .track i{display:block;height:100%;border-radius:3px;background:var(--brand);
  transition:width .4s ease}
.fb-type .n{text-align:right;color:var(--ink-1);font-variant-numeric:tabular-nums;
  font-weight:var(--fw-500)}

.exp-foot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:8px 12px 10px;background:var(--surface);
  border-top:1px solid var(--line2);border-left:3px solid var(--brand-soft)}
.exp-sum{font-size:11.5px;color:var(--ink-3);
  font-variant-numeric:tabular-nums;flex:1;min-width:0}
.exp-sum b{color:var(--ink-1);font-weight:500}
/* 精简分页：嵌在展开区里，去掉每页条数与跳页 */
.pg-simple{padding:0;border-top:none;gap:8px;margin-left:auto}
.pg-simple .pg-info{font-size:11px;color:var(--ink-3)}

.tag{display:inline-flex;align-items:center;gap:5px;height:21px;padding:0 8px;border-radius:var(--r-1);
  font-size:11.5px;white-space:nowrap}
.tag::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;flex:none}
.tag-ok{background:var(--ok-soft);color:var(--ok-ink)}
.tag-warn{background:var(--warn-soft);color:var(--warn-ink)}
.tag-err{background:var(--err-soft);color:var(--err-ink)}
.tag-info{background:var(--brand-soft);color:var(--brand-ink)}
.tag-plain{background:var(--fill);color:var(--ink-2)}

.hero{display:flex;align-items:flex-end;gap:24px;padding-bottom:20px;flex-wrap:wrap}
.hero .lead{flex:1;min-width:260px}
.hero .eyebrow{font-size:11px;letter-spacing:.1em;color:var(--brand-ink);margin-bottom:6px}
.hero h1{font-size:var(--fs-24);line-height:1.3;letter-spacing:-.01em}
.hero .sub{margin-top:6px;font-size:12.5px;color:var(--ink-2)}
.hero .acts{display:flex;gap:8px;flex-wrap:wrap}

.metrics{display:grid;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-3);
  padding:16px 20px;margin-bottom:16px}
.metric{display:flex;flex-direction:column;gap:2px;padding:0 18px}
.metric:first-child{padding-left:0}
.metric:last-child{padding-right:0}
.metric + .metric{border-left:1px solid var(--line2)}
.metric .k{font-size:11.5px;color:var(--ink-3)}
.metric .v{font-size:22px;font-variant-numeric:tabular-nums;color:var(--ink-1);line-height:1.3}
.metric .v .unit{font-size:12px;color:var(--ink-3);margin-left:3px}
.metric .d{font-size:11px;color:var(--ink-3)}

/* ── 概览页栅格 ────────────────────────────────────────────────
   间距规范（全页统一，别再散落魔数）：
     左右内距：交给 #view 的 24px，这里**一律不再加横向 margin**
               （原来 KPI/图表各自 margin:0 24px，和 #view 叠加成 48px，
                 比蓝色抬头多缩进 24px，看着就是「没对齐」）
     块间距：16px（卡与卡之间 12px）
     行内：gap 16px，等宽等高
*/
/* 四列栅格：gap 必须与 .chart-grid 一致（14）。
   原来这里 12、图表那边 14，两排四列的右边界差 2px，
   在 1920 宽屏下肉眼能看出「上下没对齐」。 */
.kpi-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;
  margin:16px 0 0}
.kpi{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-3);
  padding:18px;box-shadow:var(--e-1);
  /* 结构与 .panel-b 保持一致（padding:18px + 垂直居中），
     而不是 space-between 把内容撑到上下两端 ——
     那样标签贴着卡片顶、说明贴着卡片底，中间空一大块。
     现在整块内容在卡片里居中，上下留白对称。 */
  display:flex;flex-direction:column;justify-content:center;gap:10px;
  min-height:148px}
/* 上半区：文字左、环右 */
.kpi-body{display:flex;align-items:center;justify-content:space-between;gap:12px}
.kpi-main{display:flex;flex-direction:column;min-width:0}
.kpi-ring{flex:none;opacity:.95}
/* 环里的百分比字（gauge 生成的 .gg-v）在 92px 尺寸下要小一点 */
.kpi-ring .gg-v{font-size:17px}
.kpi-k{font-size:11.5px;color:var(--ink-3);letter-spacing:.02em;line-height:1.4}
.kpi-v{margin-top:8px;font-size:30px;line-height:1.1;color:var(--ink-1);
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;font-weight:var(--fw-500);
  display:flex;align-items:baseline;gap:5px;flex-wrap:wrap}
.kpi-v .unit{font-size:12.5px;color:var(--ink-3);font-weight:400;letter-spacing:0}
/* 金额单独一行：原来和件数挤成「1,369件 · 51万」，
   两个不同字号的数字挨着，一眼分不清哪个是件数、哪个是钱。 */
.kpi-money{margin-top:5px;font-size:13.5px;color:var(--ink-2);
  font-variant-numeric:tabular-nums}
.kpi-d{margin-top:6px;font-size:11.5px;color:var(--ink-3);line-height:1.45}
/* ── 推进情况：紧凑的四格指标 ──────────────────────────────
   原来 4 个数字均分整行（每个占约 320px），数字之间离得太远，
   视线要横跳 900px 才看完。改成靠左排布 + 细分隔线，一眼扫完。
*/
.funnel{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
/* 四格按内容宽度排（每格约 200px），整组占约 2/3 宽，右边留白。
   为什么不等分：等分时每格 270px，而「已推送 125 人」实际只占 90px，
   剩下 180px 全是空 —— 数字之间离得远，读起来要横跳。
   按内容排 + 竖分隔线，四格连成一个紧凑的指标条。 */
/* 四格等分：靠内容宽度排会留下右侧 400px 空白，看着像没做完；
   等分后分隔线均匀，视觉上是完整的一条指标带。
   内容仍然左对齐（不是居中），数字不会被推离标签。 */
/* 网格内不再需要竖向分隔线：四列已被 gap 自然分开，
   再加一条线会和下方图表卡片的上边框堆在一起显脏。 */
.funnel-step{min-width:0}
/* 概览的推进情况：环 + 文字。
   用 grid 固定「环列 52px + 间距 14px」，四格的环起点完全一致 ——
   用 flex 居中时，文字宽度不同（"125 人" vs "3 人"）会让每格的
   环左右位置各不相同，看着像「飘」。 */
/* 文字列固定 124px：四格的环起点就完全一致。
   用 auto 时，文字宽度随数字变化（"125 人" vs "3 人"），
   整组居中的结果就是每格环的 x 坐标差 5px 左右 —— 看着像「飘」。 */
.funnel-step.fs-ring{display:grid;grid-template-columns:52px 124px;
  align-items:center;justify-content:center;gap:14px;min-height:88px}
.fs-text{display:flex;flex-direction:column;min-width:0}
.funnel-step .k{display:block;font-size:12px;color:var(--ink-3);margin-bottom:3px}
/* 数字放大：22 → 27。用户反馈「推进情况的内容展示太小」 */
.funnel-step .v{display:flex;align-items:baseline;gap:4px;
  font-size:27px;line-height:1.1;color:var(--ink-1);
  font-variant-numeric:tabular-nums;font-weight:var(--fw-500);
  letter-spacing:-.01em}
.funnel-step .v .unit{font-size:12px;color:var(--ink-3);font-weight:400}
.funnel-step .h{display:block;margin-top:2px;font-size:11px;color:var(--ink-3)}
.funnel-step .k{display:block;font-size:11.5px;color:var(--ink-3);margin-bottom:3px}
.funnel-step .v{display:flex;align-items:baseline;gap:4px;
  font-size:24px;line-height:1.15;color:var(--ink-1);
  font-variant-numeric:tabular-nums;font-weight:var(--fw-500)}
.funnel-step .v .unit{font-size:11.5px;color:var(--ink-3);font-weight:400}
.funnel-step .h{display:block;margin-top:2px;font-size:10.5px;color:var(--ink-3)}
/* 有进展用主色强调、0 值保持灰 —— 一眼看出卡在哪一环 */
.funnel-step.done .v{color:var(--brand-ink)}
.funnel-step.zero .v{color:var(--ink-3)}

/* 底部占比条：margin-top:auto 把它压到卡片最底，
   没有条的卡也靠它占位，四张卡的下沿才对得齐。 */
.kpi-foot{margin-top:auto;padding-top:12px;display:flex;align-items:center;gap:8px;
  min-height:15px}
.kpi-bar{flex:1;height:3px;border-radius:2px;background:var(--fill);overflow:hidden}
.kpi-bar i{display:block;height:100%;border-radius:2px;background:var(--brand);
  transition:width .5s ease}
.kpi-foot .pct{flex:none;font-size:10.5px;color:var(--ink-3);
  font-variant-numeric:tabular-nums}
/* 可点击的卡：能点进明细逐条核对，所以要有可点的视觉暗示 */
.kpi-link{cursor:pointer;transition:border-color .15s,box-shadow .15s,transform .15s}
.kpi-link:hover{border-color:var(--brand);box-shadow:var(--e-2);transform:translateY(-1px)}
.kpi-link:hover .kpi-k::after{content:' →';color:var(--brand)}
.kpi-link:active{transform:translateY(0)}

/* 口径自检条：把「三桶相加 = 全部」这条等式显式写在页面上。
   数字对不上时变红报警 —— 藏着不显示等于没做自检。 */
.kpi-recon{margin:10px 0 0;padding:8px 12px;border-radius:var(--r-2);
  background:var(--brand-soft);color:var(--ink-2);font-size:11.5px;
  line-height:1.6;font-variant-numeric:tabular-nums}
.kpi-recon b{color:var(--ink-1);font-weight:500}
.kpi-recon .rc-ok{color:var(--ok-ink);font-weight:500;margin-right:6px}
.kpi-recon .rc-sep{color:var(--ink-3);margin:0 4px}
.kpi-recon.bad{background:var(--err-soft);color:var(--err-ink)}
.kpi-recon.bad .rc-bad{font-weight:500;margin-right:6px}

/* 图表行：等宽等高，两行结构一致。
   右栏 360px 是量出来的下限：环形图 + 图例要放得下
   「实用新型 824 60%」这样的一整行，340px 时图例会被截成「实用…」。 */
/* ── 图表网格：列数跟着可用宽度走，不靠缩窄页面 ─────────────
   这是解决「留白太多」的正解：后台本身铺满（表格页要宽），
   但看板的卡片不能无限拉宽 —— 5 根柱子摊到 800px 就是几根细线。
   做法是把「列数」做成断点：
     ≥1520px → 4 列（每块约 380px，图表密度最舒服）
     ≥1000px → 2 列
     <1000px → 1 列
   每块宽度始终在 320~420 之间，不会出现空旷感。
   用 auto-fit + minmax 而不是固定列数：4 列时如果只剩 3 块，
   会自动拉宽补满，不会留下一个空位。
*/
/* 列数用固定断点，不用 auto-fit ——
   auto-fit 在 1560px 下会排成 3 列，4 块图变成「3 + 1」，
   最后一块单独占一行、宽度还和上面三块一样，看着很残。
   固定成 4 / 2 / 1 三档，4 块图永远能被整除铺满。 */
.chart-grid{display:grid;gap:14px;margin:14px 0 0;align-items:stretch;
  grid-template-columns:repeat(4,minmax(0,1fr))}
@media(max-width:1400px){.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:820px){.chart-grid{grid-template-columns:minmax(0,1fr)}}
.chart-grid>.panel{display:flex;flex-direction:column;min-width:0}
/* 内容区撑满剩余高度，同排底部对齐 */
.chart-grid>.panel>.panel-b{flex:1;display:flex;flex-direction:column;
  justify-content:center;min-width:0;padding:14px 18px 16px}

/* KPI 卡：4 列在 1280 以下会挤成 260px，改 2 列；
   1920 下 4 列各 400px 偏长，用 auto-fit 让卡片自己定宽度上限。 */
@media(max-width:1280px){
  .kpi-row{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:640px){
  .kpi-row{grid-template-columns:minmax(0,1fr)}
  .chart-grid{grid-template-columns:minmax(0,1fr)}
}
.chart-foot{padding:0 18px 14px;font-size:11px;color:var(--ink-3);line-height:1.6}
.chart-empty{padding:44px 18px;text-align:center;font-size:12.5px;color:var(--ink-3)}

/* SVG 内的文字（不能靠父级 CSS 继承，统一在这里定义） */
.bars text{font-family:var(--f-sans)}
.bars .ax-y{font-size:10px;fill:oklch(60% .012 248)}
.bars .ax-x{font-size:11px;fill:oklch(45% .012 248)}
.bars .ax-sub{font-size:9.5px;fill:oklch(62% .012 248)}
.bars .ax-top{font-size:10.5px;fill:oklch(30% .012 248);font-weight:500}
.bars rect{transition:opacity .15s}
.bars rect:hover{opacity:.78}

/* ── 环形图 ─────────────────────────────────────────────────
   与横向排名条用同一套内距（14px 18px），两块面板的图区高度才对得上。
*/
.donut-wrap{display:flex;align-items:center;justify-content:center;gap:28px;
  padding:6px 0;flex-wrap:wrap;min-width:0;
  /* 撑满面板内容区：面板变宽时（弹性布局下 533~673）如果这里不拉伸，
     圆环+图例这一组只会占 400px，剩下的空白全堆在右边 ——
     看着就是「图偏左」。width:100% 让 justify-content 真正居中。 */
  width:100%;height:100%}
.donut{flex:none}
.donut .dn-cv{font-size:20px;font-weight:600;fill:oklch(26% .01 248);
  font-variant-numeric:tabular-nums}
.donut .dn-cl{font-size:10.5px;fill:oklch(60% .012 248)}
.donut-legend{flex:0 1 240px;min-width:0;display:flex;flex-direction:column;gap:9px;
  font-size:12.5px}
/* 图例不抢剩余空间：flex:1 会让圆环贴最左、数字甩到最右，
   中间空出 200+px，看着像两个不相干的块。限宽后整体居中更紧凑。 */
.lg-row{display:grid;grid-template-columns:8px minmax(0,1fr) auto 38px;gap:8px;
  align-items:center;font-size:12px;line-height:1.5}
.lg-row.lg-zero .lg-name,.lg-row.lg-zero .lg-val{color:var(--ink-3)}
.lg-dot{width:8px;height:8px;border-radius:2px}
.lg-name{color:var(--ink-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lg-val{color:var(--ink-1);font-variant-numeric:tabular-nums;font-weight:var(--fw-500)}
.lg-pct{color:var(--ink-3);font-variant-numeric:tabular-nums;text-align:right;font-size:11px}

/* ── 横向排名条 ─────────────────────────────────────────────
   统一行高 20px + 9px 间隔，8 行总高固定，两块排名面板等高。
*/
.hbars{display:flex;flex-direction:column;gap:9px;padding:2px 0}
.hb-row{display:grid;grid-template-columns:18px minmax(72px,124px) minmax(0,1fr) auto 58px;
  gap:10px;align-items:center;font-size:12px;min-height:20px}
.hb-rank{width:18px;height:18px;border-radius:4px;background:var(--fill);
  color:var(--ink-3);font-size:10.5px;display:flex;align-items:center;
  justify-content:center;font-variant-numeric:tabular-nums}
.hb-name{color:var(--ink-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hb-track{height:8px;border-radius:4px;background:var(--fill);overflow:hidden}
.hb-track i{display:block;height:100%;border-radius:4px;transition:width .5s ease}
.hb-val{font-variant-numeric:tabular-nums;color:var(--ink-1);
  font-weight:var(--fw-500);min-width:34px;text-align:right}
.hb-money{font-variant-numeric:tabular-nums;color:var(--ink-3);font-size:11px;
  text-align:right;white-space:nowrap}

/* 旧版推进漏斗（.funnel grid + .fn-cell + .gauge）已删除 ——
   它定义在后面，会把上面新的 flex 版覆盖成四等分网格，
   表现是「四格间距仍然巨大」。改版时残留的重复定义最坑，必须清掉。
   gauge() 函数仍保留在 charts.js，将来要画环形进度可直接用。
*/

.note-plain{background:transparent;color:var(--ink-3);padding:0;font-size:11px}

.note{display:flex;gap:10px;padding:11px 14px;border-radius:var(--r-2);background:var(--brand-soft);
  color:var(--brand-ink);font-size:12.5px;line-height:1.65}
.note b{font-weight:var(--fw-500);display:block;margin-bottom:2px}
.note-warn{background:var(--warn-soft);color:var(--warn-ink)}
.note-err{background:var(--err-soft);color:var(--err-ink)}
.note-ok{background:var(--ok-soft);color:var(--ok-ink)}
.note svg{flex:none;margin-top:2px}

.filterbar{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:12px 18px;
  border-bottom:1px solid var(--line2);background:var(--surface)}
.filterbar .inp,.filterbar .sel{width:auto;min-width:140px}
/* 搜索控件：输入框 + 内嵌搜索按钮，做成**一个**控件。
   原来按钮是 btn-quiet（透明无边框、28px 高），和 34px 的输入框并排，
   高度不齐、边界全无 —— 看着像浮在旁边的标签，既不像按钮也不像输入框的一部分。
   现在把两者合成一个圆角框：focus 高亮整个框，按钮是框内右侧的主色小块。 */
.filterbar .find{position:relative;display:flex;align-items:center;flex:0 1 320px;
  height:34px;padding-left:30px;border:1px solid var(--line);border-radius:var(--r-2);
  background:var(--surface);transition:border-color .15s,box-shadow .15s}
.filterbar .find:focus-within{border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-soft)}
.filterbar .find>.ico{position:absolute;left:10px;top:50%;transform:translateY(-50%);
  display:flex;align-items:center;color:var(--ink-3);pointer-events:none}
.filterbar .find .inp{flex:1;min-width:0;height:32px;border:none;background:transparent;
  padding:0 8px 0 0;box-shadow:none}
.filterbar .find .inp:focus{outline:none;border:none;box-shadow:none}
/* 内嵌搜索按钮：主色小块，明确「可点」 */
.filterbar .find-btn{flex:none;height:26px;margin:0 3px 0 2px;padding:0 12px;
  border:none;border-radius:6px;background:var(--brand);color:var(--on-brand);
  font-size:12px;font-family:inherit;letter-spacing:.02em;
  display:flex;align-items:center;justify-content:center;
  transition:background .15s}
.filterbar .find-btn:hover{background:var(--brand-ink)}
.filterbar .find-btn:active{transform:translateY(1px)}
.sp{width:1px;height:18px;background:var(--line)}
.pager{display:flex;align-items:center;gap:4px}
.pager button{min-width:28px;height:28px;border:1px solid var(--line);background:var(--surface);
  border-radius:var(--r-1);font-size:12px;color:var(--ink-2);cursor:pointer;font-family:inherit}
.pager button:hover:not(:disabled){border-color:var(--brand);color:var(--brand-ink)}
.pager button.on{background:var(--ink-1);border-color:var(--ink-1);color:#fff}
.pager button:disabled{opacity:.35;cursor:not-allowed}
.pg-gap{padding:0 2px;color:var(--ink-3);font-size:12px}
.pg-info{font-variant-numeric:tabular-nums}
.pg-right{display:flex;align-items:center;gap:14px;margin-left:auto;flex-wrap:wrap}
.pg-size,.pg-jump{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--ink-3)}
.tb-foot{display:flex;align-items:center;gap:12px;padding:10px 18px;border-top:1px solid var(--line2);
  font-size:12px;color:var(--ink-3);flex-wrap:wrap}

/* 旧的 .side-user / .act 三段堆叠样式已删除，改用 .me-card（见上方「身份卡片」） */

/* 可排序表头 */
.th-sort{cursor:pointer;user-select:none;white-space:nowrap}
.th-sort:hover{color:var(--ink-1)}
.th-sort.on{color:var(--brand-ink)}
.th-sort .sort-ar{display:inline-block;margin-left:4px;font-size:10px;opacity:.45}
.th-sort.on .sort-ar{opacity:1}
.sort-label{font-size:12px;color:var(--ink-3);white-space:nowrap}

.dl{display:grid;grid-template-columns:96px 1fr;gap:0}
.dl dt{padding:7px 0;font-size:12px;color:var(--ink-3);border-bottom:1px solid var(--line2)}
.dl dd{margin:0;padding:7px 0;font-size:12.5px;color:var(--ink-1);border-bottom:1px solid var(--line2)}
.dl dt:last-of-type,.dl dd:last-of-type{border-bottom:none}

/* 抽屉 */
.mask{position:fixed;inset:0;background:oklch(20% .02 var(--h) / .32);z-index:40}
.drawer{position:fixed;top:0;right:0;bottom:0;width:480px;max-width:92vw;background:var(--surface);
  z-index:41;display:flex;flex-direction:column;box-shadow:var(--e-3);
  animation:drawerIn .22s cubic-bezier(.16,1,.3,1)}
@keyframes drawerIn{from{transform:translateX(100%)}to{transform:none}}
.drawer-h{flex:none;display:flex;align-items:center;gap:12px;padding:16px 20px;
  border-bottom:1px solid var(--line2)}
.drawer-h h3{font-size:15px}
.drawer-b{flex:1;overflow-y:auto;padding:20px}
.drawer-f{flex:none;display:flex;justify-content:flex-end;gap:8px;padding:14px 20px;
  border-top:1px solid var(--line2)}

#toast{position:fixed;top:16px;left:50%;transform:translateX(-50%) translateY(-8px);z-index:60;
  padding:8px 16px;border-radius:var(--r-2);background:oklch(26% .01 var(--h));color:#fff;
  font-size:12.5px;opacity:0;pointer-events:none;transition:opacity .18s,transform .18s;max-width:80vw}
#toast.on{opacity:1;transform:translateX(-50%) translateY(0)}

.bar{height:6px;border-radius:3px;background:var(--fill);overflow:hidden}
.bar i{display:block;height:100%;background:var(--brand);border-radius:3px;transition:width .3s}

@media(max-width:900px){
  .side{position:fixed;left:0;top:0;bottom:0;z-index:30;transform:translateX(-100%);transition:transform .2s}
  .side.open{transform:none}
  .scroll{padding:16px}
}

/* 客户查看列：标签 + 时间小字 */
.view-at{display:block;font-size:10px;color:var(--ink-3);margin-top:2px}
.cli-dim,.dim{color:var(--ink-3)}

/* ── 重名待确认：必须醒目 ────────────────────────────────────
   这是「系统不敢自动匹配」的状态，靠人去看才会修。
   藏在抽屉里等于没提示，所以做成行内红标签 + 整行淡红底。
*/
.amb-tag{display:inline-block;margin-left:6px;padding:0 6px;height:17px;
  line-height:17px;border-radius:8px;font-size:10.5px;vertical-align:middle;
  background:var(--warn-soft);color:var(--warn-ink);
  border:1px solid oklch(80% .08 75);cursor:help;font-weight:var(--fw-500)}
tr.row-warn td{background:oklch(98.5% .015 75)}
tr.row-warn:hover td{background:oklch(96.5% .02 75)}
.metric-warn .v{color:var(--warn-ink)}
/* 兜底人后面标注「上级」，说明这个值是花名册带过来的、不是手填 */
.bk-hint{margin-left:5px;font-size:10px;color:var(--ink-3);
  border:1px solid var(--line);border-radius:6px;padding:0 4px;white-space:nowrap}

/* mock 占位值标记：看着像已配置、实际推不出去，必须显眼 */
.mock-tag{display:inline-block;margin-left:4px;padding:0 4px;height:15px;
  line-height:15px;border-radius:6px;font-size:9.5px;vertical-align:middle;
  background:var(--err-soft,oklch(96% .04 25));color:var(--err-ink,oklch(50% .16 25));
  cursor:help}
.metric-err .v{color:var(--err-ink,oklch(50% .16 25))}

/* ── 未登录兜底提示 ────────────────────────────────────────────
   只在「没配 SSO」或「认证中心不可达」时出现。
   正常走 SSO 时直接跳转，用户看不到这个卡片。
*/
.login-notice{position:fixed;left:50%;top:22%;transform:translateX(-50%);
  z-index:9000;width:min(420px,calc(100vw - 48px));padding:24px 26px;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-3);
  box-shadow:var(--e-3,0 12px 40px rgba(0,0,0,.16));text-align:center}
.login-notice .ln-t{font-size:15px;font-weight:var(--fw-500);color:var(--ink-1);
  margin-bottom:8px}
.login-notice .ln-m{font-size:12.5px;color:var(--ink-3);line-height:1.65;
  word-break:break-word}
.login-notice .ln-a{margin-top:18px}

/* ── 静默加载占位 ──────────────────────────────────────────────
   本系统内置在桌面端应用里，用户看到的第一屏就是这个。
   正常情况下几百毫秒内就被主应用替换，所以做得极简：
   一个标记 + 一行字，不画进度条（进度条反而让等待显得更长）。
*/
.boot{position:fixed;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:14px;background:var(--bg)}
.boot-mark{color:var(--brand);opacity:.85;animation:bootPulse 1.8s ease-in-out infinite}
@keyframes bootPulse{0%,100%{opacity:.35}50%{opacity:.9}}
.boot-t{font-size:12.5px;color:var(--ink-3);letter-spacing:.02em}
.boot-t.boot-err{color:var(--err-ink);font-size:13px;line-height:1.7;
  max-width:420px;text-align:center}
@media(prefers-reduced-motion:reduce){.boot-mark{animation:none;opacity:.8}}

/* ── ECharts 容器 ─────────────────────────────────────────────
   ECharts 必须拿到有确定宽高的容器，否则初始化时算出 0 宽，
   图会缩成一条线（这是最常见的「图表不显示」原因）。
*/
.echart{width:100%;height:240px}
/* 图表面板高度 280 → 440：
   实测 280px 时图表区底部在 1019，视口 1250 还空 231px。
   440px 后卡片 362 → 522、底部到约 1179，与内容区底 1194 基本齐平。 */
.chart-grid>.panel>.panel-b .echart{height:440px}
/* 环形图面板：加高到 250，给放大的圆环留空间 */
.chart-grid>.panel>.panel-b .echart[data-echart="donut"]{height:440px}
/* KPI / 推进情况的小环：尺寸由 JS 内联给，这里只保证不塌 */
.ring-box{flex:none}
.kpi-ring{flex:none;display:flex;align-items:center}

/* ── 概览汇总表（案件总览 / 推进情况）──────────────────────────
   两张表共用：同一指标纵向对齐，扫一列就能横向比较。
   原来用卡片时，件数/金额/占比散在四个位置，对比要横跳 1200px。
*/
.tb-sum{font-size:12.5px}
.tb-sum th{padding:8px 16px;font-size:11px;letter-spacing:.02em}
.tb-sum td{padding:11px 16px;border-top:1px solid var(--line2);vertical-align:middle}
/* 首列（状态/环节）稍宽，给色点和中文留位置 */
.tb-sum th:first-child,.tb-sum td:first-child{width:150px;padding-left:18px}
.tb-sum .num{text-align:right;font-variant-numeric:tabular-nums}
/* 主数字：比正文大一档、加重，但不抢标题 */
.tb-sum .strong{font-size:14px;font-weight:var(--fw-500);color:var(--ink-1)}
.tb-sum .unit{font-size:11px;color:var(--ink-3);margin-left:3px;font-weight:400}
.tb-sum .dim{color:var(--ink-3)}
.tb-sum .hint{color:var(--ink-3);font-size:11.5px}
/* 可点的行：整行浅色高亮 + 指针，提示能进明细 */
.tb-sum tr.clickable{cursor:pointer;transition:background .14s}
.tb-sum tr.clickable:hover{background:var(--fill)}
/* 最后一行不画下边框，避免和 panel 底边叠成两条线 */
.tb-sum tbody tr:last-child td{border-bottom:none}

/* 状态色点 */
.st-dot{display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:8px;vertical-align:1px}

/* 占比条：细横条 + 数字。横条比环省空间，还能直接比长短 */
.ratio{display:flex;align-items:center;gap:9px}
.ratio .bar{flex:1;max-width:110px;height:6px;border-radius:3px;
  background:var(--fill);overflow:hidden}
.ratio .bar i{display:block;height:100%;border-radius:3px;
  background:var(--brand);transition:width .5s ease}
.ratio .n{flex:none;min-width:42px;text-align:right;font-size:11.5px;
  color:var(--ink-2);font-variant-numeric:tabular-nums}

/* panel-h 右侧的说明小字 */
.sub-note{font-size:12.5px;color:var(--ink-3)}

/* ── 概览：案件总览 + 推进情况并排一行 ─────────────────────────
   单列时条形图横跨 1656px，1,369 的条要拉满整屏才结束，
   长短反而看不出比例；两列各约 820px 后比例感更清楚。
   高度保持与原来两行相当，多出的空间用来放大条高和字号。
*/
.ov-duo{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:14px;margin:0;
  /* 必须显式 stretch：左边面板多了自检条、内容更高，
     不 stretch 时右边只有 403px、比左边矮 16px，两列底边不齐。 */
  align-items:stretch}
@media(max-width:1180px){.ov-duo{grid-template-columns:minmax(0,1fr)}}

/* 两个面板等高，内容区 flex 撑满 */
/* ⚠️ 不能写 height:100%：grid item 的高度百分比会参考 grid area，
   而 area 又由 item 内容决定 —— 循环引用，实测把面板撑到 3323px。
   正确做法是 min-height:0（解除 flex/grid item 的 min-height:auto），
   stretch 就能正常把两列拉成等高。 */
/* ⚠️ 必须清掉 .panel+.panel 的 margin-top：
   并排的两个面板是兄弟关系，会命中那条规则，
   导致右边整体下移 16px、可用高度少 16px（实测 459 vs 443）。 */
.ov-duo>.panel{margin-top:0;display:flex;flex-direction:column;
  min-width:0;min-height:0}
.ov-duo>.panel>.panel-b{flex:1;display:flex;flex-direction:column;
  justify-content:stretch;padding:6px 8px 10px;min-height:0}
/* 条形图占满剩余高度：min-height 兜底，防止初始化时算出 0 高。
   340 是为了让「并排一行」的总高接近原来「上下两行」的 476px ——
   否则并排后整体变矮，页面底部会多出一大块空白。 */
.ov-duo .echart.ov-bar{flex:1;min-height:340px;height:auto}

/* 自检条放进面板底部：它校验的就是「案件总览」的口径 */
.ov-duo>.panel>.kpi-recon{margin:0 18px 14px;padding:10px 13px;font-size:13px}

/* ── 花名册同步的加载态 ───────────────────────────────────────
   同步要跑 3~50 秒（取决于 VPN 和数据库负载），原来点完按钮
   界面上一点反馈都没有，用户会以为卡死而重复点击。
   给一个带步骤说明的进度条，比单纯转圈更能安抚等待。
*/
.hr-loading{padding:28px 20px 32px;text-align:center}
.hl-spin{width:26px;height:26px;margin:0 auto 14px;border-radius:50%;
  border:2.5px solid var(--fill);border-top-color:var(--brand);
  animation:hl-rot .8s linear infinite}
@keyframes hl-rot{to{transform:rotate(360deg)}}
.hl-title{font-size:14px;color:var(--ink-1);font-weight:var(--fw-500);margin-bottom:6px}
.hl-step{font-size:12px;color:var(--ink-3);margin-bottom:14px}
.hl-bar{height:4px;border-radius:2px;background:var(--fill);
  overflow:hidden;max-width:320px;margin:0 auto}
.hl-bar i{display:block;height:100%;width:8%;border-radius:2px;
  background:var(--brand);transition:width .55s ease}
.hl-hint{margin-top:14px;font-size:11.5px;color:var(--ink-3);line-height:1.6}

/* ── 人员映射：工号为主显示 ───────────────────────────────────
   流程人员只认工号（D200509），看不懂企业微信 userid（一串 8 位数字）。
   所以工号放主位、加粗；userid 退到下方小输入框，只在排查时看。
*/
.wc-cell{display:flex;flex-direction:column;gap:3px;align-items:flex-start}
.wc-main{font-size:13px;font-weight:var(--fw-500);color:var(--ink-1);
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
/* 没同步到工号：用告警色提示「这里缺东西」，而不是静默留空 */
.wc-main.wc-miss{font-weight:400;color:var(--warn-ink, #b45309);font-size:12px}
.wc-cell .inp{width:150px!important;height:24px!important;font-size:11.5px!important;
  color:var(--ink-3)}
.wc-cell .mock-tag{font-size:10px;color:var(--err-ink);background:var(--err-soft);
  padding:0 5px;border-radius:3px;line-height:15px}

/* ── 人员映射表：列宽收敛 ───────────────────────────────────
   实测 1440 宽时各列 77/174/205/205/59/246/102 ——
   兜底人吃掉 246px，导致「qiaozhengkai@dgg.net」被截成 @dgg.ne。
   兜底人只是个姓名，不需要那么宽；把空间还给邮箱和工号。
*/
#view table.tb th:nth-child(6),
#view table.tb td:nth-child(6){width:150px;max-width:150px}
#view table.tb th:nth-child(6) .inp,
#view table.tb td:nth-child(6) .inp{width:100px!important}
/* 邮箱列加宽，避免长邮箱被截 */
#view table.tb th:nth-child(4),
#view table.tb td:nth-child(4){width:220px}

/* 邮箱输入框 150 → 210：'qiaozhengkai@dgg.net' 需要约 200px，
   150 会把末尾截成 "@dgg.ne"，看起来像邮箱拼错了。 */
#view table.tb td:nth-child(4) .inp{width:210px!important}
/* 工号下方的企业微信账号输入框也放宽一点 */
.wc-cell .inp{width:160px!important}

/* ── 危险操作按钮（重新推送、删除等）─────────────────────────
   用红色而不是主色：这类操作不可撤销，颜色本身就是一道确认。
*/
.btn-danger{background:var(--err-ink);border-color:var(--err-ink);color:#fff}
.btn-danger:hover{background:#c03434;border-color:#c03434}

/* ── 应用内确认框（替代原生 confirm）──────────────────────────
   原生 confirm 会显示 "192.168.15.89:8788 显示" 这种带域名的标题，
   按钮是系统默认样式、不能定制红色危险态 —— 在后台系统里
   看着像网页被劫持了。这个组件的外观和抽屉/卡片保持一致。
*/
.mask.mask-center{display:flex;align-items:center;justify-content:center;
  z-index:50;padding:20px}
.confirm{position:relative;z-index:51;width:440px;max-width:100%;
  background:var(--surface);border-radius:var(--r-3);box-shadow:var(--e-3);
  animation:cfIn .18s cubic-bezier(.16,1,.3,1)}
@keyframes cfIn{from{opacity:0;transform:translateY(6px) scale(.98)}
  to{opacity:1;transform:none}}
.cf-h{display:flex;align-items:center;gap:10px;padding:18px 20px 0}
.cf-h h3{font-size:15px;font-weight:var(--fw-500);color:var(--ink-1)}
/* 图标用浅色圆底托一下，比裸图标更像「提示」而不是「报错」 */
.cf-ic{width:30px;height:30px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  background:var(--brand-soft);color:var(--brand-ink)}
.cf-ic-danger{background:var(--err-soft);color:var(--err-ink)}
.cf-b{padding:10px 24px 2px 60px;font-size:13px;color:var(--ink-2);
  line-height:1.7}
.cf-f{display:flex;justify-content:flex-end;gap:10px;padding:22px 24px 20px}

/* ── 权限管理：表单栅格 ─────────────────────────────────────
   左标签右控件。用 grid 而不是 flex 是因为标签宽度要对齐，
   flex 每个 label 宽度不同会参差不齐。
*/
.form-grid{display:grid;grid-template-columns:88px 1fr;gap:10px 12px;
  align-items:center}
.form-grid>label{font-size:12.5px;color:var(--ink-2);text-align:right}
.form-grid>label+*{min-width:0}
/* 停用状态的行做半透明，一眼看出这条不生效 */
.tb tbody tr[style*="opacity"] .tag{opacity:1}

/* ── 侧栏品牌：名称 + 版本号 ────────────────────────────────
   改成「专利年费推送管理平台 v0.01」后 13 个字更长，
   原字号在 324px 侧栏里会顶到右边缘，所以把名称压到 14.5px、
   版本号用小一号灰字挂在后面 —— 一行排下，不换行不省略。
*/
.side-brand{gap:10px}
.side-brand .brand-name{font-size:14.5px;letter-spacing:-.2px;white-space:nowrap}
.side-brand .brand-ver{font-size:10.5px;font-weight:400;color:var(--ink-3);
  padding:1px 5px;border:1px solid var(--line);border-radius:4px;
  flex:none;letter-spacing:0}
